Roles and access
An operator’s permissions are set through roles, not on the operator directly. A role is a named set of permissions plus an optional scope selector. An operator gains permissions by being bound to roles. The whole RBAC model — roles, their permissions, and operator bindings — lives in PostgreSQL and is managed through the Operator API (REST/MCP/web UI), not by editing a config file.
Permission
Section titled “Permission”A permission is an atomic allowed action. The format is one of two:
*— full access to all operations (equivalent to thecluster-adminrole);<resource>.<action>— exactly two segments: resource and action. Examples:incarnation.create,soul.list,role.grant-operator,operator.revoke.
The action can be a * wildcard within a resource — incarnation.* means “all actions on incarnations”. A wildcard of the form *.create is not supported; full access is the separate * case (with no dot).
A permission maps to a single Operator API operation: an HTTP endpoint and the identically named MCP tool. For example, incarnation.create governs the same action whether it’s called over HTTP, through MCP, or from the web UI.
The permissions catalog is dynamic
Section titled “The permissions catalog is dynamic”The full list of available permission names is a backend catalog, not a table hard-coded into the docs. A name outside the catalog is rejected by Keeper when a role is loaded, with an unknown_permission error. The web UI and tools pull the current set from the API — so what’s given here is the mechanics, not a list.
Permission groups are organized by resource, for example:
- operator management —
operator.*(creation, revocation, token issuance, listing); - role management —
role.*(creation, modification, deletion, binding/unbinding operators); - group management —
synod.*(Synod); - working with incarnations —
incarnation.*(creation, running, reading, history, updating); - the host registry —
soul.*(registration, listing, assigning covens, token issuance); - recurring runs —
cadence.*; - and other families.
The concrete set of actions and their semantics are what the backend catalog returns; check against it, not against a fixed list.
Scope selector
Section titled “Scope selector”A permission can be narrowed with a selector — a filter that limits the action to a subset of souls. Without a selector, a permission applies without restriction.
A selector is written as on <key>=<values> after the permission. Supported keys:
| Key | Limits the action to… |
|---|---|
coven= | …hosts/incarnations with the given Coven tags |
service= | …incarnations of the given service types |
incarnation= | …a specific incarnation by name |
host= | …a specific host (by its SID) |
regex='…' | …hosts whose SID/name matches an RE2 pattern |
soulprint='…' | …hosts whose facts satisfy a CEL predicate over soulprint.self.* |
Multiple values for one key are listed comma-separated with no spaces and combine with OR: coven=db,cache — “coven db or cache”. Values for regex and soulprint are enclosed in single quotes so that commas and spaces inside the pattern/predicate don’t break the value apart.
An example of a role limited to an environment:
name: db-operatorpermissions: - "incarnation.* on service=redis-cluster,vault-cluster" - "soul.list on coven=db"Such an operator can perform any operation on incarnations of the redis-cluster and vault-cluster services and see hosts tagged db; everything else is forbidden by the default-deny policy.
The role’s default scope
Section titled “The role’s default scope”A role can set a default scope once (the default_scope field) that all its permissions inherit. A per-permission selector (on … right in the permission string) overrides the default for that specific permission. This is handy when the whole role works in one environment but one or two actions need to be narrowed or widened individually.
How scope affects which hosts and incarnations an operator sees in listings — the Synod and Purview section.
Binding an operator to a role
Section titled “Binding an operator to a role”An operator gains permissions when its AID is bound to a role (membership). Management is through the Operator API:
- creating a role with a set of permissions — the
role.createpermission; - binding an operator to an existing role — the
role.grant-operatorpermission (by the “role + AID” pair); - unbinding — the
role.revoke-operatorpermission; - changing a role’s permission set — the
role.updatepermission; - deleting a role — the
role.deletepermission; - listing roles with their permissions and bound operators — the
role.listpermission.
Binding is idempotent: binding the same AID to the same role again is a safe no-op.
Invariants in role management
Section titled “Invariants in role management”Role management is subject to two safeguards (see Default-deny):
- Least-privilege. You can’t include in a role, or grant through a role, a permission you don’t hold yourself. An attempt returns
403 forbidden. Full access (*) can be granted only by a holder of*. - Self-lockout. You can’t strip a role’s permissions or unbind an operator in a way that would leave the cluster without an active administrator with full access —
409 would-lock-out-cluster.
The built-in cluster-admin role (builtin, permission *) has extra protection: it can’t be deleted (role.delete) or have its permission set changed (role.update) — an attempt returns 409 role-builtin. Operators can be bound to and unbound from it (with the same self-lockout invariant on unbinding).
Permission application — instant across the cluster
Section titled “Permission application — instant across the cluster”Keeper doesn’t hit the database on every permission check: it keeps a snapshot of roles and bindings in memory. Any mutation of a role, a permission set, or a binding broadcasts an invalidation signal across the cluster, and all instances re-read the snapshot from PostgreSQL. So a permission change takes effect on all instances almost immediately, not “eventually”. As a safety net against a lost signal, there’s a periodic background refresh of the snapshot.
This is separate from the lifetime of an already-issued JWT: revoking a role affects permission checks immediately, but a given operator token stays valid until its exp (Archons → Revocation).
See also
Section titled “See also”- Operator management — an overview of the Archon → roles → permissions model.
- Synod and Purview — operator groups and scoped souls visibility.
- Archons — the operator lifecycle and the last-administrator invariant.