Skip to main content

06 โ€” Permissions

Modelโ€‹

Permissions are grants that attach a group to a (server, database, action) with optional constraints.

group โจฏ (server, database) โจฏ action โจฏ constraints โ†’ allowed
PieceMeaning
GroupAn SSO group claim (e.g. backend-engineers, oncall, data-platform). Synced from IdP.
Server / databaseLogical names from the config โ€” never raw hosts.
Actionschema_read, query_read, query_write, history_read.
ConstraintPer-grant overrides: require_reason, row_limit, statement_timeout_ms, allowed_schemas, denied_tables, time_window (e.g. business hours only).

A request is allowed iff some grant matches. Denies are not explicit โ€” absence of a matching grant denies.

Actions in detailโ€‹

ActionWhat it unlocks
schema_readlist_databases, describe_schema, sample_table (size-capped)
query_readrun_query with read-only enforcement, explain, get_query_history
query_writerun_query with write grants (requires the DB role to also have write privileges)
history_readRead your own query history. Other users' history is never readable here โ€” admin-only via state DB

query_read implies schema_read. query_write implies query_read.

Worked examplesโ€‹

Backend engineer on staging: full query, schema-read on prod.

- group: backend-engineers
grants:
- server: staging
database: "*"
action: query_read
- server: prod
database: "*"
action: schema_read

Oncall with audited prod access: query on prod, but reason required and short timeouts.

- group: oncall
grants:
- server: prod
database: "*"
action: query_read
constraints:
require_reason: true
statement_timeout_ms: 5000
row_limit: 1000

Data platform with writes to a single sandbox DB:

- group: data-platform
grants:
- server: analytics
database: sandbox
action: query_write
constraints:
require_reason: true

New hires: no entry โ†’ no access. The default is nothing.

Worker-db rollout (issue #19). Rule: rollout stays contract-only and scan-friendly.

ItemValue
Deployable configconfig/permissions.yml
Zitadel groupsdevs, devops, cto
v1 action scopequery_read only
Shared constraintsstatement_timeout_ms: 30000, row_limit: 10000
Deferred scopePer-dev DB ownership (requires Zitadel group-per-app mapping)

Evaluationโ€‹

For a request (user, groups, server, db, action):

  1. Resolve groups from the session.
  2. Collect every grant matching one of the user's groups and the target (server, db).
  3. If no grant has an action โ‰ฅ the requested action โ†’ forbidden.
  4. Merge constraints across matching grants: take the most restrictive value for each (lowest row_limit, lowest statement_timeout_ms, require_reason = true wins, schema/table allow/deny intersected).
  5. Apply constraints to the call.

Most-restrictive merging means you can't accidentally upgrade your access by being in two groups.

Hot reloadโ€‹

Permission changes land via PR โ†’ merge โ†’ operator runs kill -HUP on the gateway (or rolling restart). Live sessions keep their existing grants snapshot until next login (matches 04-auth-sso).

Admin operationsโ€‹

There is no in-band admin UI. Operator actions are CLI subcommands of the gateway binary, run inside the container, against the state DB:

  • gateway admin revoke-session <user_email>
  • gateway admin list-active-sessions
  • gateway admin replay-audit <query> (for compliance investigations)

Admin commands authenticate via a separate mechanism (Unix socket / shell access), not SSO โ€” they're for whoever has prod shell access.