Declare and change data scopes¶
Grant, change, or revoke an app's data access by editing spec.dataScopes
on its App CR. Enforcement follows at the data gateway within five minutes,
with zero redeploys — the network perimeter does not change.
Preconditions¶
- An onboarded app (Onboard a new app).
- For a non-synthetic scope: the scope name must be mapped to a real target
by platform configuration (
deploy/data-gateway/sources-configmap.yaml, delivered via GitOps). Declaring a scope grants the app permission at the gateway; it does not create the source, and apps never hold data-source credentials — the gateway does.
Steps¶
1. Add a scope¶
Each scope is {name, sourceType, access} with sourceType one of
synthetic | postgres | s3 and access one of read | readwrite:
kubectl patch app expense-helper -n tend-system --type merge -p '{
"spec": {"dataScopes": [
{"name": "synthetic", "sourceType": "synthetic", "access": "readwrite"},
{"name": "orders-db", "sourceType": "postgres", "access": "read"}
]}}'
The patch replaces the whole list
A JSON merge patch replaces the dataScopes array. Always include every
scope the app should keep, not just the one you are adding.
2. Wait for enforcement — no redeploy happens¶
The gateway evaluates the App CR's declared scopes per request, so the change is enforced within five minutes (in practice near-immediately). The app pods are not restarted and the perimeter objects are unchanged — the gateway decision moved, not the network:
kubectl get pods -n tend-app-expense-helper # same pods, no restarts
kubectl get ciliumnetworkpolicies -n tend-app-expense-helper # same three objects
Verify it worked¶
make gateway-request sends a request from inside the app's namespace with
the app's own identity:
make gateway-request speaks only the synthetic route
MODE=read always issues GET /v1/sources/{name}/rows, which is the
synthetic connector's route. A postgres scope is read at
POST /v1/sources/{name}/query and an s3 scope at /objects, so pointing
this target at orders-db returns 404 source not configured rather than
200 — the scope is fine, the route is wrong. The deny cases below are exact
for every source type, because the scope check runs before the connector is
looked up.
make gateway-request APP=expense-helper SOURCE=synthetic MODE=read
# [gateway-request] status: 200 — declared, allowed
make gateway-request APP=expense-helper SOURCE=payroll-db MODE=read
# [gateway-request] status: 403
# {"error":"undeclared-source"} — not declared, denied
make gateway-request APP=expense-helper SOURCE=orders-db MODE=write
# [gateway-request] status: 403
# {"error":"insufficient-access"} — declared read-only, write denied
Every decision (allow and deny) is logged. Check the per-app summary via the gateway's platform query API — it needs a Keycloak OIDC token with a platform role (app tokens are refused on this surface):
kubectl -n tend-system port-forward svc/data-gateway 18081:8080 &
curl -s -H "Authorization: Bearer $PLATFORM_TOKEN" \
"http://localhost:18081/v1/logs/summary?app=tend-system/expense-helper" | jq
# {"allows": …, "denies": …, "lastDeny": {…}}
The same decisions appear in the app's evidence record under dataAccess
(Query an app's evidence). Full endpoint semantics are
in the data gateway API reference.
Revoke a scope¶
Patch the list without the scope you are revoking:
kubectl patch app expense-helper -n tend-system --type merge -p '{
"spec": {"dataScopes": [
{"name": "synthetic", "sourceType": "synthetic", "access": "readwrite"}
]}}'
Then watch the denial appear — again with no redeploy:
make gateway-request APP=expense-helper SOURCE=orders-db MODE=read
# [gateway-request] status: 403
# {"error":"undeclared-source"}
Troubleshooting¶
- A declared request returns 503
{"error":"evaluator-error"}— the gateway fails closed when it cannot evaluate or cannot log; check thedata-gatewaydeployment intend-system. - A declared postgres/s3 scope is denied — confirm the scope name matches a target in the gateway's sources configuration; an unmapped name cannot be served.