Files
git.stella-ops.org/docs/architecture/console-admin-rbac.md
master fcb5ffe25d feat(scanner): Complete PoE implementation with Windows compatibility fix
- Fix namespace conflicts (Subgraph → PoESubgraph)
- Add hash sanitization for Windows filesystem (colon → underscore)
- Update all test mocks to use It.IsAny<>()
- Add direct orchestrator unit tests
- All 8 PoE tests now passing (100% success rate)
- Complete SPRINT_3500_0001_0001 documentation

Fixes compilation errors and Windows filesystem compatibility issues.
Tests: 8/8 passing
Files: 8 modified, 1 new test, 1 completion report

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 14:52:08 +02:00

237 lines
14 KiB
Markdown

# 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`