# Console Admin RBAC Architecture ## 1. Purpose - Provide a unified, Authority-backed admin surface for tenants, users, roles, clients, tokens, and audit. - Expose the same capabilities to UI and CLI while preserving offline-first operation. - Normalize scope and role bundles, including missing Scanner roles, for consistent RBAC across modules. ## 2. Scope - Authority admin APIs and data model used by the Console Admin workspace. - Role and scope taxonomy, including scanner roles. - Audit, fresh-auth, and offline export/import workflow. - UI integration contract (routes, scopes, and API paths). Non-goals: - Replacing external IdP user lifecycle workflows (SAML/OIDC remains primary for enterprise identity). - Exposing privileged mTLS-only admin endpoints directly to the browser. ## 3. Core Architecture ### 3.1 Authority admin tiers - **/admin**: mTLS + authority.admin scope for automation and ops tooling. - **/console/admin**: DPoP + UI scopes for browser and CLI admin flows. Both tiers share the same data model and audit log but enforce different auth policies. ### 3.2 Entities and ownership Authority remains the source of truth for: - **Tenant**: id, display name, status, isolation mode, default roles. - **Installation**: installation id, tenant binding, bootstrap metadata. - **Role**: id, display name, scopes[], audiences[], flags (interactive-only, requires fresh-auth). - **User**: subject, status, display name, tenant assignments, roles per tenant. - **Client**: client id, grant types, auth method, allowed scopes, audiences, tenant hint. - **Token record**: access/refresh/device metadata, revocation status. - **Audit events**: immutable admin and auth events. ### 3.3 Fresh-auth High-risk operations require a fresh-auth window: - Tenant suspend/resume - Token revocation (bulk or admin) - Role bundle edits - Client secret or key rotation - Branding apply Authority uses auth_time + fresh-auth TTL to gate these operations. ## 4. Scope and Role Taxonomy ### 4.1 Console admin scopes New admin scopes (Authority-managed): - `authority:tenants.read`, `authority:tenants.write` - `authority:users.read`, `authority:users.write` - `authority:roles.read`, `authority:roles.write` - `authority:clients.read`, `authority:clients.write` - `authority:tokens.read`, `authority:tokens.revoke` - `authority:audit.read` - `authority:branding.read`, `authority:branding.write` - `ui.admin` (console access for admin views) ### 4.2 Scanner scope and role bundles (missing today) Define scanner scopes and role bundles to align UI, CLI, and API: - Scopes: `scanner:read`, `scanner:scan`, `scanner:export`, `scanner:write` - Role bundles: - `role/scanner-viewer` -> `scanner:read` - `role/scanner-operator` -> `scanner:read`, `scanner:scan`, `scanner:export` - `role/scanner-admin` -> `scanner:read`, `scanner:scan`, `scanner:export`, `scanner:write` Compatibility: - Gateway maps `scanner:read|scan|export|write` to any legacy scanner scope strings until full cutover. ### 4.3 Module role bundle catalog Role bundles are grouped by module and map to existing Authority scopes unless noted. | Module | Role bundle | Scopes | | --- | --- | --- | | Console | `role/console-viewer` | `ui.read` | | Console | `role/console-admin` | `ui.read`, `ui.admin`, `authority:tenants.read`, `authority:users.read`, `authority:roles.read`, `authority:clients.read`, `authority:tokens.read`, `authority:audit.read`, `authority:branding.read` | | Console | `role/console-superadmin` | `ui.read`, `ui.admin`, `authority:tenants.*`, `authority:users.*`, `authority:roles.*`, `authority:clients.*`, `authority:tokens.*`, `authority:audit.read`, `authority:branding.*` | | Scanner | `role/scanner-viewer` | `scanner:read`, `findings:read`, `aoc:verify` | | Scanner | `role/scanner-operator` | `scanner:read`, `scanner:scan`, `scanner:export`, `findings:read`, `aoc:verify` | | Scanner | `role/scanner-admin` | `scanner:read`, `scanner:scan`, `scanner:export`, `scanner:write`, `findings:read`, `aoc:verify` | | Policy | `role/policy-author` | `policy:read`, `policy:author`, `policy:simulate`, `findings:read` | | Policy | `role/policy-reviewer` | `policy:read`, `policy:review`, `policy:simulate`, `findings:read` | | Policy | `role/policy-approver` | `policy:read`, `policy:review`, `policy:approve`, `policy:simulate`, `findings:read` | | Policy | `role/policy-operator` | `policy:read`, `policy:operate`, `policy:run`, `policy:activate`, `policy:publish`, `policy:promote`, `policy:simulate`, `findings:read` | | Policy | `role/policy-auditor` | `policy:read`, `policy:audit`, `findings:read` | | Concelier | `role/concelier-reader` | `advisory:read`, `aoc:verify` | | Concelier | `role/concelier-ingest` | `advisory:ingest`, `advisory:read`, `aoc:verify` | | Concelier | `role/concelier-operator` | `concelier.jobs.trigger`, `advisory:read`, `aoc:verify` | | Concelier | `role/concelier-admin` | `concelier.jobs.trigger`, `concelier.merge`, `advisory:read`, `aoc:verify` | | Excititor | `role/excititor-reader` | `vex:read`, `aoc:verify` | | Excititor | `role/excititor-ingest` | `vex:ingest`, `vex:read` | | Notify | `role/notify-viewer` | `notify.viewer` | | Notify | `role/notify-operator` | `notify.viewer`, `notify.operator` | | Notify | `role/notify-admin` | `notify.viewer`, `notify.operator`, `notify.admin` | | Scheduler | `role/scheduler-viewer` | `scheduler:read` (new) | | Scheduler | `role/scheduler-operator` | `scheduler:read`, `scheduler:operate` (new) | | Scheduler | `role/scheduler-admin` | `scheduler:read`, `scheduler:operate`, `scheduler:admin` (new) | | Orchestrator | `role/orch-viewer` | `orch:read`, `findings:read` | | Orchestrator | `role/orch-operator` | `orch:read`, `orch:operate`, `findings:read` | | Orchestrator | `role/orch-admin` | `orch:read`, `orch:operate`, `orch:quota`, `orch:backfill`, `findings:read` | | Graph | `role/graph-viewer` | `graph:read`, `graph:export` | | Graph | `role/graph-operator` | `graph:read`, `graph:export`, `graph:simulate` | | Graph | `role/graph-admin` | `graph:read`, `graph:export`, `graph:simulate`, `graph:write`, `graph:admin` | | Vuln Explorer | `role/vuln-viewer` | `vuln:view`, `findings:read` | | Vuln Explorer | `role/vuln-investigator` | `vuln:view`, `vuln:investigate`, `findings:read` | | Vuln Explorer | `role/vuln-operator` | `vuln:view`, `vuln:investigate`, `vuln:operate`, `findings:read` | | Vuln Explorer | `role/vuln-auditor` | `vuln:view`, `vuln:audit`, `findings:read` | | Export Center | `role/export-viewer` | `export.viewer` | | Export Center | `role/export-operator` | `export.viewer`, `export.operator` | | Export Center | `role/export-admin` | `export.viewer`, `export.operator`, `export.admin` | | Advisory AI | `role/advisory-ai-viewer` | `advisory-ai:view`, `aoc:verify` | | Advisory AI | `role/advisory-ai-operator` | `advisory-ai:view`, `advisory-ai:operate`, `aoc:verify` | | Advisory AI | `role/advisory-ai-admin` | `advisory-ai:view`, `advisory-ai:operate`, `advisory-ai:admin`, `aoc:verify` | | Signals | `role/signals-viewer` | `signals:read`, `aoc:verify` | | Signals | `role/signals-uploader` | `signals:read`, `signals:write`, `aoc:verify` | | Signals | `role/signals-admin` | `signals:read`, `signals:write`, `signals:admin`, `aoc:verify` | | Evidence Locker | `role/evidence-reader` | `evidence:read` | | Evidence Locker | `role/evidence-creator` | `evidence:read`, `evidence:create` | | Evidence Locker | `role/evidence-legal` | `evidence:read`, `evidence:hold` | | Observability | `role/observability-viewer` | `obs:read`, `timeline:read`, `attest:read` | | Observability | `role/observability-investigator` | `obs:read`, `timeline:read`, `timeline:write`, `evidence:read`, `evidence:create`, `attest:read` | | Observability | `role/observability-incident-commander` | `obs:read`, `obs:incident`, `timeline:read`, `timeline:write`, `evidence:read`, `evidence:create`, `attest:read` | | Issuer Directory | `role/issuer-directory-viewer` | `issuer-directory:read` | | Issuer Directory | `role/issuer-directory-operator` | `issuer-directory:read`, `issuer-directory:write` | | Issuer Directory | `role/issuer-directory-admin` | `issuer-directory:read`, `issuer-directory:write`, `issuer-directory:admin` | | Task Packs | `role/packs-viewer` | `packs.read` | | Task Packs | `role/packs-operator` | `packs.read`, `packs.run` | | Task Packs | `role/packs-publisher` | `packs.read`, `packs.write` | | Task Packs | `role/packs-approver` | `packs.read`, `packs.approve` | | Airgap | `role/airgap-viewer` | `airgap:status:read` | | Airgap | `role/airgap-operator` | `airgap:status:read`, `airgap:import` | | Airgap | `role/airgap-admin` | `airgap:status:read`, `airgap:import`, `airgap:seal` | | Exceptions | `role/exceptions-viewer` | `exceptions:read` | | Exceptions | `role/exceptions-approver` | `exceptions:read`, `exceptions:approve` | | Exceptions | `role/exceptions-editor` | `exceptions:read`, `exceptions:write` | | Attestor | `role/attestor-viewer` | `attest:read`, `aoc:verify` | | Attestor | `role/attestor-operator` | `attest:read`, `attest:create`, `aoc:verify` | | Attestor | `role/attestor-admin` | `attest:read`, `attest:create`, `attest:admin`, `aoc:verify` | | Signer | `role/signer-viewer` | `signer:read`, `aoc:verify` | | Signer | `role/signer-operator` | `signer:read`, `signer:sign`, `aoc:verify` | | Signer | `role/signer-admin` | `signer:read`, `signer:sign`, `signer:rotate`, `signer:admin`, `aoc:verify` | | SBOM | `role/sbom-viewer` | `sbom:read`, `aoc:verify` | | SBOM | `role/sbom-creator` | `sbom:read`, `sbom:write`, `aoc:verify` | | SBOM | `role/sbom-attestor` | `sbom:read`, `sbom:write`, `sbom:attest`, `attest:create`, `aoc:verify` | | Release | `role/release-viewer` | `release:read`, `policy:read`, `findings:read` | | Release | `role/release-manager` | `release:read`, `release:write`, `policy:read`, `findings:read` | | Release | `role/release-publisher` | `release:read`, `release:write`, `release:publish`, `policy:read`, `findings:read` | | Release | `role/release-admin` | `release:read`, `release:write`, `release:publish`, `release:bypass`, `policy:read`, `findings:read` | | Zastava | `role/zastava-viewer` | `zastava:read` | | Zastava | `role/zastava-operator` | `zastava:read`, `zastava:trigger` | | Zastava | `role/zastava-admin` | `zastava:read`, `zastava:trigger`, `zastava:admin` | **Missing scopes (must be added to Authority)**: Scanner scopes are not yet defined in Authority. They are proposed as `scanner:read`, `scanner:scan`, `scanner:export`, and `scanner:write` and must be added to Authority constants, discovery metadata, and gateway enforcement. Scheduler scopes are not yet defined in Authority. They are proposed as `scheduler:read`, `scheduler:operate`, and `scheduler:admin` and must be added to Authority constants, discovery metadata, and gateway enforcement. Authority admin scopes (partial): `authority:tenants.read` exists. Must add: `authority:tenants.write`, `authority:users.read`, `authority:users.write`, `authority:roles.read`, `authority:roles.write`, `authority:clients.read`, `authority:clients.write`, `authority:tokens.read`, `authority:tokens.revoke`, `authority:branding.read`, `authority:branding.write`. UI admin scope: `ui.admin` must be added to Authority constants. Attestor scopes: `attest:read` exists. Must add: `attest:create`, `attest:admin`. Signer scopes (all new): `signer:read`, `signer:sign`, `signer:rotate`, `signer:admin`. SBOM scopes (all new): `sbom:read`, `sbom:write`, `sbom:attest`. Release scopes (all new): `release:read`, `release:write`, `release:publish`, `release:bypass`. Zastava scopes (all new): `zastava:read`, `zastava:trigger`, `zastava:admin`. Graph admin scope: `graph:admin` must be added to Authority constants. Exception write scope: `exceptions:write` must be added to Authority constants (exceptions:read and exceptions:approve exist). ## 5. Console Admin API Surface ### 5.1 Tenants - `GET /console/admin/tenants` - `POST /console/admin/tenants` - `PATCH /console/admin/tenants/{tenantId}` - `POST /console/admin/tenants/{tenantId}/suspend` - `POST /console/admin/tenants/{tenantId}/resume` Scopes: `authority:tenants.read|write` ### 5.2 Users - `GET /console/admin/users?tenantId=...` - `POST /console/admin/users` (local users only) - `PATCH /console/admin/users/{userId}` - `POST /console/admin/users/{userId}/disable` - `POST /console/admin/users/{userId}/enable` Scopes: `authority:users.read|write` ### 5.3 Roles and scopes - `GET /console/admin/roles` - `POST /console/admin/roles` - `PATCH /console/admin/roles/{roleId}` - `POST /console/admin/roles/{roleId}/preview-impact` Scopes: `authority:roles.read|write` ### 5.4 Clients - `GET /console/admin/clients` - `POST /console/admin/clients` - `PATCH /console/admin/clients/{clientId}` - `POST /console/admin/clients/{clientId}/rotate` Scopes: `authority:clients.read|write` ### 5.5 Tokens and audit - `GET /console/admin/tokens?tenantId=...` - `POST /console/admin/tokens/revoke` - `GET /console/admin/audit?tenantId=...` Scopes: `authority:tokens.read|revoke`, `authority:audit.read` ## 6. Audit and Observability - Every admin mutation emits `authority.admin.*` events with tenant, actor, and trace id. - Audit export provides deterministic ordering and ISO-8601 timestamps. - Token revocations emit revocation bundle update markers for downstream caches. ## 7. Offline-first Administration - Admin changes can be exported as signed bundles for air-gapped import. - The Console produces a change manifest; Authority applies it via `/admin/bundles/apply` (mTLS). - UI labels changes as pending when Authority is offline. ## 8. UI Integration Contract - Admin workspace routes live under `/console/admin/*`. - Admin UI uses `/console/admin` APIs with DPoP; no mTLS endpoints are called by the browser. - `ui.admin` plus specific `authority:*` scopes are required to render and mutate data. ## 9. References - `docs/modules/authority/architecture.md` - `docs/modules/ui/architecture.md` - `docs/ui/admin.md` - `docs/contracts/web-gateway-tenant-rbac.md`