Operator

Roles and permissions

Authorisation is expressed in permissions, the unit every check tests. Roles are fixed bundles of permissions defined in the service, not composed by an operator, so what a role confers cannot drift from what the code enforces. This chapter is the complete table of both, and the two things that sit beside them: root and maintainers.

What changed in 2.6.0

Maintainers also manage the webhooks of their Producers and of the Consumers of the same name.

Permissions#

Permission What it allows Held by
manage_users List, approve and delete user accounts; create service accounts and issue, see and revoke their API tokens. admin, user_manager
manage_roles Grant and revoke roles, manage groups and their roles and members, assign maintainers. admin, user_manager
manage_producers Administer Producers and their version lines: delete a version (the escape hatch from GA immutability), delete a Producer, edit its metadata, manage its webhooks, rename and delete sanshain-branches. admin; maintainer within its Producers
manage_consumers Delete a Consumer and its recorded dependencies. admin
view_audit Read the audit log and timeline, export it as CSV. admin, viewer
manage_settings Cleanup ages, the cache, registration and auto-approve, the webhook allow-list and global webhooks, the debug flags. admin
manage_auth_config Read and change the authentication mode and the LDAP and OIDC configuration. The only accounts that sign in while the mode is Off or Dev. admin
view_observability Read logs, stats and the debug configuration. Defined and reported in a user's effective permissions; in 2.6.0 no route checks it, the observability routes admit every signed-in user. admin, viewer
run_destructive_operations The bulk deletions of the Danger Zone and the whole-database snapshot download. admin
release_ga Publish with stability: ga (a fresh GA, a promotion, an idempotent GA re-provide), cut a sanshain-branch. admin, releaser

Root holds all ten and any permission a later release adds. GET /admin/roles returns this catalogue; GET /auth/me returns the caller's effective permissions, which is what the web UI gates on.

Roles#

Role Confers Granted how
admin Every permission. Instance-wide; admin-guarded.
user_manager manage_users, manage_roles. Instance-wide.
viewer view_audit, view_observability. Instance-wide.
maintainer manage_producers, within a set of Producers. Never instance-wide: assigned to a Producer. Granting it as a role answers 400 Role 'maintainer' is scoped to specific Producers and cannot be granted instance-wide.
releaser release_ga. Instance-wide; admin-guarded.

admin and releaser are admin-guarded: only an admin or root may grant or revoke them, or put them on or take them off a group; a user_manager who tries gets a bare 403. For admin that closes self-escalation; for releaser it keeps manage_roles from being a side door to release rights.

Publishing GA without the permission answers 403 with the remedy (publishing GA for 'orders' requires the 'releaser' role — publish as a snapshot, or ask an administrator to grant the role) and writes a VERSION_REJECTED audit entry with reason ga_requires_releaser. Grant releaser to whatever performs releases, typically the CI user whose token sets the ga switch. Holders also get a Promote to GA button on snapshot entries; the promoting account is named in the VERSION_PROMOTED entry while the original provider stays credited.

How routes are guarded#

Every /admin/* route declares one of three guards, and a build-time test keeps the router and maintenance.yaml in step (the generated Maintenance API shows the guard of every route):

Admin routes take a session token only. An API token answers 401, a read_only one on the backup route (the only admin route that admits API tokens) answers 403 with this API token has scope 'read_only' and may not call administrative routes; use a token with scope 'full' of an account holding the permission.

Maintainers#

A maintainer is responsible for a set of Producers. It is an assignment, not a role, because it means nothing without the Producers it is over. A user or a group can be assigned; group assignment is what scales, so responsibility follows membership. A Producer-scoped action admits either the global permission or maintainership of that Producer, so an administrator acts on any Producer and a maintainer on theirs, and is refused on everybody else's.

In the Maintainers section of the admin page, pick a Producer, then tick the users and groups responsible for it; each tick or untick is saved at once. The same kind of picker ticks members into a Sanshain group, picks the Focus of the graph, and picks the account a token is issued For: type to narrow the list, no accept button.

What maintaining a Producer covers in 2.6.0:

Maintainers do not release: a GA provide stays the pipeline's job.

Route Guard Effect
GET /admin/maintainers manage_roles Every Producer with its maintainers.
GET /admin/producers/{name}/maintainers manage_producers, Producer-scoped Users and groups maintaining it; a maintainer may read their own.
POST /admin/producers/{name}/maintainers manage_roles Body {"user_id": 3} or {"group_id": 1}, exactly one.
DELETE /admin/producers/{name}/maintainers/users/{user_id}, …/groups/{group_id} manage_roles Unassign.
GET /admin/users/{id}/maintains manage_roles The Producers a user is responsible for.

Groups#

A group is a set of users that roles attach to. Groups carry a source:

A directory group is added by searching for it: type a part of its name under Add a directory group in the Groups section, or in the Maintainers picker, and Sanshain searches the directory through the service account (group, groupOfNames, groupOfUniqueNames or posixGroup entries whose cn contains the text, at most 20). Picking a hit stores the group under its DN, the form memberOf answers. So a team cn=carla,ou=teams,… becomes the maintainer of penguin with one tick, and whoever joins or leaves the team in the directory gains or loses that without a change in Sanshain.

A native and a directory group may share a name; the UI shows the origin. The LDAP form's Bootstrap Admin Group is the usual first directory group: saving it creates the group with admin attached. OIDC groups are not mapped here; only the OIDC admin_group grants a role, see Authentication.

Route Effect
GET /admin/roles The role catalogue and what each confers.
GET/POST /admin/users/{id}/roles, DELETE …/roles/{role} Roles granted directly to a user; grant with {"role": "user_manager"}.
GET/POST /admin/groups All groups with origin, roles and members; create a native one with {"name": "…"}.
GET /admin/directory-groups?q=carla Directory groups whose name contains the text, each with its dn, name and the group_id it has in Sanshain, if added.
POST /admin/groups/directory Add a directory group by its DN: {"dn": "cn=carla,ou=teams,dc=corp,dc=com"}.
PUT/DELETE /admin/groups/{id} Rename, replace its roles, or both; delete a group and its grants.
POST /admin/groups/{id}/members, DELETE …/members/{user_id} Membership of a native group; body {"user_id": 3}.

All of them need manage_roles and are audited (GRANT_ROLE, REVOKE_ROLE, CREATE_GROUP, UPDATE_GROUP, DELETE_GROUP, ASSIGN_MAINTAINER, UNASSIGN_MAINTAINER).

Token scopes, from the operator's side#

Users mint their own API tokens on /account.html; an operator cannot mint one for someone else. A token's scope is fixed at creation: full does everything its owner may, read_only reads reports, versions, the update streams and the MCP endpoint and is refused on every route that changes the catalog with 403 this API token has scope 'read_only' and may not change the catalog; create a token with scope 'full' for provide, require and catalog pushes. A token never exceeds its owner: a full token of a user without releaser cannot publish GA. Expired tokens stop validating at once and are removed by the cleanup pass. Deleting a user revokes their tokens. The 2.6.0 UI has no administrative view of other users' tokens.

Checked against v2.6.0.