ADR-0043 — Auth Unification: RBAC Reconciliation
Status: Accepted
Date: 2026-05-03
Deciders: Kristerpher (product owner), software-architect
Scope: CF Access group taxonomy + app-layer RBAC integration for all operator surfaces
Design doc: docs/architecture/auth-unification-rbac-reconciliation.md
Does NOT supersede: ADR-0042 (sibling ADR; extends it)
Extends: [ADR-0119](https://internal-docs.raxx.app/architecture/adr/0119-rbac-groups-not-direct-roles.html), [ADR-0042](https://internal-docs.raxx.app/architecture/adr/0042-auth-unification-hybrid-model.html)
Refs: Epic #146 (console operator admin console); Velvet v2 epic #907; NV10 (#954); DQ-4 resolved 2026-05-03 ~08:00 UTC
Context
ADR-0042 established Google Workspace as the IDP for all operator surfaces via CF Access Zero Trust. It left open DQ-4: the CF Access group taxonomy. The prior proposal in auth-unification.md §5.3 defined four flat CF Access groups (ops, superadmin, developer, auditor). Kristerpher rejected that taxonomy as incompatible with the fine-grained <app>-<resource>-<level> model specified in rbac-design.md and ADR-0020.
The reconciliation question: CF Access JWTs carry group claims at the network edge, but Raxx's canonical authz namespace uses fine-grained role names internal to each app. The two layers must be connected without duplicating policy state or tying Workspace group management to every app's release cycle.
Decision
D1: CF Access groups encode operator function, not app-level permissions
The CF Access group taxonomy uses four team-function groups — raxx-platform-admins, raxx-support, raxx-devops, raxx-break-glass — not fine-grained role names. These groups answer the question "is this person a Raxx operator of a given function?" at the network edge. They do not answer "can this person perform console:secrets:rotate?" That question is answered entirely by the app-layer.
D2: App-layer RBAC is authoritative; JWT groups claim is advisory
The CF JWT groups claim is read by the app layer for two advisory purposes only:
1. raxx-break-glass in claims triggers a mandatory audit alert before the passkey step.
2. Coarse surface filtering (UI optimization before role resolution completes).
The authoritative permission decision is always the user_groups → group_roles → role_permissions → role_inheritance DAG walk from rbac-design.md §7. A permission check that relies solely on the JWT groups claim and skips the DAG walk is a security defect.
D3: <app>-<resource>-<level> naming is the canonical role namespace
All Raxx roles follow the pattern from rbac-design.md §3.1. CF Access group names and Workspace group names never appear in permission checks. The mapping is:
Workspace group → CF Access Group → [advisory JWT claim for edge routing]
Workspace group → Raxx internal group (same name, different system) → roles → permissions
The Workspace group name and the Raxx internal group name may share the same string (e.g., both are raxx-platform-admins) but are independently managed systems.
D4: No auto-provisioning from CF JWT group claims
An operator whose CF JWT carries raxx-platform-admins but who has no row in user_groups for the Console is permitted past the CF edge but receives 403 at the first authenticated route. CF Access group membership is a necessary but not sufficient condition for app-layer access. This upholds ADR-0042 D5 (no auto-provisioning via Google OAuth).
D5: Velvet NV10 yaml gate uses Raxx internal group/role names exclusively
The NV10 yaml-driven revocation auth gate references group (Raxx internal group name), role (Raxx role name), or single-user (admin_id UUID). It never references CF Access group names or Workspace group emails.
D6: single-user gate is an exception to I2, permitted only for break-glass scenarios
ADR-0020's invariant (no direct permissions on users) is upheld for all routine access. The single-user option in NV10 is permitted exclusively for emergency break-glass gate configurations. Every use must generate a mandatory audit event. Routine use of single-user gates is prohibited.
Consequences
Positive
- The four CF Access groups (
raxx-platform-admins,raxx-support,raxx-devops,raxx-break-glass) are far fewer than the ~20 groups that full<app>-<resource>-<level>replication in CF Access would require. Workspace admin stays clean. - The
<app>-<resource>-<level>role taxonomy is fully internal to Raxx. Adding a new fine-grained role requires no Workspace group creation and no CF policy change. - SAML readiness (ADR-0020) is preserved: when an enterprise IdP is connected, it emits Raxx internal group names. Role taxonomy never leaves Raxx's schema.
- The CF edge and the app-layer fail-closed independently. A CF misconfiguration stops operators at the edge. An app-layer misconfiguration produces a 403 with an audit row.
- Velvet NV10 yaml gate vocabulary requires no change.
Negative / Risks
- Two-step operator onboarding. Adding a new operator requires a Workspace group add AND a Console RBAC group assignment. This is intentional (ADR-0042 D5) but adds friction compared to a fully CF-driven model.
- JWT
groupsclaim drift. If the Workspace group sync to CF Access has delay (up to 1 hour for non-SCIM), a newly added operator may pass the Workspace IDP login but have stale JWTgroups. The app-layer role check is the safety net — but for the advisory surface-filtering use of the JWT claim, the UI may show the wrong subset for up to 1 hour. Acceptable at current team size. admin_rolestable coexists with RBAC v2 tables during migration. The transition period (Phases 0–3) has two role-resolution paths behind a feature flag. Both paths must be maintained inrbac.pyuntil Phase 4. Tech debt is explicit and time-bounded (30-day soak then drop).
Neutral
- ADR-0042 is not superseded. The IDP decision (Google Workspace), the per-surface policy model, and the Phase 1–3 transition plan all remain in force. This ADR adds the group taxonomy and integration layer.
- ADR-0001 (passkeys-only for customer-facing Antlers) is unaffected.
- ADR-0003 (GDPR) applies to
user_groupsrows that join toadmin_id(covered by existing DSR tooling).
Alternatives Considered
Alternative A: CF Access groups mirror the full <app>-<resource>-<level> namespace
Rejected. Requires ~20 Workspace groups; ties group management to every role addition or rename; exposes Raxx's internal permission taxonomy to an external system (violates I7 from the reconciliation doc); breaks SAML readiness (IdP would need to know role names, not just team-function group names). See design doc §5 Option 2 for full analysis.
Alternative B: CF Access as network gate only; no group information in JWT
Rejected as suboptimal, not as incorrect. Option 1 (design doc §5) is operationally correct but wastes the information that CF Access can carry. The raxx-break-glass alerting use-case (D2 above) is valuable and requires the JWT groups claim. Without it, break-glass access generates no early warning until after passkey authentication completes.
Alternative C: SCIM bridge for near-real-time group sync
Not rejected — this is a future upgrade path. At current team size, 1-hour eventual consistency on group membership changes is acceptable. SCIM can be added when the team grows and real-time revocation latency matters. The group taxonomy and policy design are compatible with SCIM; no architectural change is needed to adopt it later.
References
- docs/architecture/auth-unification-rbac-reconciliation.md — full design doc
- [ADR-0119](https://internal-docs.raxx.app/architecture/adr/0119-rbac-groups-not-direct-roles.html) — RBAC groups as the permission bridge; centralized identity
- [ADR-0042](https://internal-docs.raxx.app/architecture/adr/0042-auth-unification-hybrid-model.html) — auth unification hybrid model
- docs/architecture/rbac-design.md — full role taxonomy and data model
- docs/architecture/auth-unification.md — CF Access policy templates (prior doc; §5.3 group names superseded by this ADR)
- Kristerpher DQ-4 resolution: 2026-05-03 ~08:00 UTC — "we should review the taxonomy and RBAC"
- NV10 (#954) — yaml-driven revocation auth gate
- Epic #146 — raxx-console operator admin console