Administering access
The first admin, baseline roles for everyone, delegation, service accounts, break-glass and the audit trail.
Only the admin service changes access: the admin screens, the access API, zaccess bootstrap-admin and your own code calling admin.Service / AdminService all go through it, and it applies every rule to every caller. Never write to the access tables yourself and never add a back door ("if user is admin").
A new org
The first admin
zaccess bootstrap-admin -org org_acme -user u_founderOr admin.Bootstrap(ctx, a, store, org, user) (Go) / bootstrapAdmin(access, store, org, user) (TypeScript) in a seed script. It works only while the org has no active access admin.
A baseline role for everyone
Give every member employee (scope self), one of two ways:
- A group mapping on the IdP's all-staff group. Nothing to run at onboarding; leavers lose it when the IdP removes them from the group.
- Onboarding code calling
createAssignmentas a service account, for products without reliable IdP groups.
Delegate
Managers get line_manager with scope team; HR gets hr_officer with the org_unit IDs they look after. From there, people delegate within their own reach: HR for Finance can make someone a manager in Finance, not in Sales.
Service accounts
A service account is an ordinary subject (svc:onboarding) whose grants come from the database like anyone else's. To let it assign roles, an admin assigns it a role holding access.assignment:manage, plus access.delegation:any if it must grant roles it doesn't hold itself (that one is sensitive and needs a reason). Every assignment it makes is in the change log under its ID.
Rules people meet
| They try to | And get |
|---|---|
| grant a permission at a scope wider than their own | escalation |
| assign themselves a role | escalation (userId) |
| grant a sensitive permission globally, or a break-glass role, without a reason | invalid (reason) |
| assign break-glass without an end, or for more than 24 hours | invalid (validUntil) |
| map a break-glass role to a group | invalid (roleId) |
| revoke the last access admin | conflict |
| delete a role that's assigned or mapped | conflict |
The admin screens preview assignments as the form changes (POST /access/assignments/preview), so these show at the field before anyone submits.
Break-glass
For emergencies, declare a break-glass role in the product manifest:
break_glass:
name: Break glass
breakGlass: true
permissions:
"*": globalAssigning it needs a reason and an end at most 24 hours away, and is logged with high severity. Review high-severity entries (GET /access/changes?severity=high) as part of your incident process.
The audit trail
Two records, for two questions:
- Who changed access? The change log: every role, assignment and group mapping change, with actor, target, reason, severity and before/after. Append-only, enforced by the database. Filter by
kind(assignment,role.created),actor,targetandseverity. - Who looked at what? Cerbos decision logs: every check and list plan, with principal, resource, effect and the org rule that denied it. Ship the sidecar's stdout to your log pipeline. See Deploying.
How fast changes apply
Within seconds. The admin service invalidates its own instance's cache, and every other instance's NOTIFY listener drops the affected entries. Without a listener, the cache TTL (60 seconds by default) bounds it.