PodWarden Hub
User ManualPodWarden

SSO RBAC Enablement Guide

Ship-blocking runbook for enabling OIDC SSO RBAC enforcement (OIDC_SSO_RBAC_ENABLED=true) safely. Covers the soft-lockout hazard, preconditions, the enablement sequence, and rollback.

SSO RBAC Enablement Guide

Tracked by: #1821 (Definition of Done) Spec: ADR-021 Phase 2 — docs/superpowers/specs/2026-07-02-adr021-phase2-sso-design.md

This guide is SHIP-BLOCKING. Do not set OIDC_SSO_RBAC_ENABLED=true in any environment before completing every step below. Skipping the preconditions can lock out all SSO users until corrected.


Primary operational hazard — soft-lockout

Scenario: An operator sets OIDC_SSO_RBAC_ENABLED=true before configuring any group mappings. Effect: every SSO login receives a zero-binding session. With the default no_match_posture = zero_binding_session the user is authenticated but denied access to every resource. With no_match_posture = deny_session the UI login ceremony returns 403 no_access immediately.

In both cases the operator (who may also be an SSO-only user) can be locked out. The only escape is either restoring the flag to false and redeploying, or performing an out-of-band admin action with a temp_admin JWT.

The SsoEnablementWarning banner (shown in the UI when the flag is true and zero mappings are configured) is an advisory mitigation for this hazard — not a hard gate. An already-locked-out admin who 403s on the mappings endpoint will not see the banner. The real safeguards are the preconditions below.


Preconditions (complete ALL before enabling)

P1 — Group mappings configured and preview-validated

  1. Sign in as an admin and navigate to Settings → SSO Group Mappings.
  2. Add at least one mapping that covers your primary admin group.
  3. Use the Preview Claim tool to paste an example token payload and confirm the expected mappings match. Check for:
    • Case-sensitivity mismatches (e.g. Admins vs admins).
    • Dotted-path issues (e.g. realm_access.roles resolves correctly).
    • Dead-scope references (cluster-scoped mappings whose cluster no longer exists).

P2 — Keycloak access-token TTL ≤ 5 minutes (E-OQ-F)

The staleness window for group changes is bounded by the OIDC access-token lifetime. A token with a 24 h lifetime means a user removed from a group retains their bindings until the token expires — up to 24 h.

Before enabling enforcement:

  1. Open the Keycloak admin console.

  2. Navigate to Realm Settings → Tokens → Access Token Lifespan.

  3. Set it to 300 seconds (5 minutes) or less.

    Alternatively set it per-client:

    • Open the target client → Advanced tab → Access Token Lifespan.
    • Set the override to 300 s or less.
  4. Confirm the change takes effect by issuing a new token and checking exp - iat.

Note: The PodWarden frontend cannot read Keycloak's realm-level TTL config, so there is no automatic validation of this precondition. The SsoEnablementWarning banner will prompt you, but the TTL confirmation is entirely your responsibility.

P3 — Legacy-admin first-login path understood

Existing manually-created admin accounts have oidc_subject = NULL. Their first SSO login hits the identity_conflict path (same verified email, no sub match). An admin must perform the one-time explicit safe-link action (Settings → Users → Link SSO Identity) before that account can use SSO login.

Plan who will do this link and test it in staging before production enablement.

The temp_admin JWT can perform the link if the only admin would otherwise be locked out.


Enablement sequence

Step 1 — Staging validation

  1. Confirm all preconditions (P1, P2, P3) in staging.

  2. Set OIDC_SSO_RBAC_ENABLED=true on both containers:

    • API container: OIDC_SSO_RBAC_ENABLED=true
    • Frontend container: OIDC_SSO_RBAC_ENABLED=true

    Use the plain OIDC_SSO_RBAC_ENABLED form on the frontend container — it is the single supported way. The frontend runtime resolver (readRuntimeEnv) also accepts the NEXT_PUBLIC_OIDC_SSO_RBAC_ENABLED alias, but setting both spellings to different values will behave unexpectedly. Always set the plain form; do not set both.

  3. Redeploy the stack.

  4. Verify the SsoEnablementWarning banner does not appear (mappings configured).

  5. Test SSO login with a user whose IdP groups match a mapping. Confirm:

    • The callback reaches / (or the intended callbackUrl) without error.
    • The backend /api/v1/auth/sso-session returns session_type: "bound".
    • The user has the expected role bindings (visible in Settings → Users).
  6. Test SSO login with a user whose IdP groups do not match any mapping. Confirm:

    • The /auth/sso-session?outcome=zero_binding page appears.
    • The user is logged in but gets 403 on protected endpoints.
  7. Test with a suspended user. Confirm 403 account_suspended error page.

Step 2 — Production enablement

Only after Step 1 is clean:

  1. Set OIDC_SSO_RBAC_ENABLED=true on both containers in production (plain form — see note in Step 1).
  2. Redeploy.
  3. Verify the SsoEnablementWarning banner is absent.
  4. Smoke-test SSO login.
  5. Monitor the /api/v1/auth/sso-session response metrics for unexpected 403/422 rates.

Rollback

Primary (data-preserving, instant): Set OIDC_SSO_RBAC_ENABLED=false on both containers and redeploy. The legacy hasRequiredGroup gate is restored. No database changes are required.

Data rollback (if needed after significant SSO usage):

-- Remove all SSO-managed role bindings.
DELETE FROM user_roles WHERE is_sso_managed = true;

-- Remove SSO-provisioned users (those created by the SSO path, not manually).
-- WARNING: verify before running in production.
UPDATE system_users SET provisioned_via = NULL, oidc_issuer = NULL,
  oidc_subject = NULL WHERE provisioned_via = 'sso';

Audit rows are retained by design.


Monitoring signals

SignalMeaning
sso_session 403 no_access in logsUser's IdP groups have no mapping match.
sso_session 403 account_suspendedSuspended user attempted SSO login.
sso_session 403 identity_conflictPre-existing account with same verified email; admin link required.
sso_session 422 claim_unparseableIdP token's groups claim is malformed or at wrong path. Check groups_claim config.
SsoEnablementWarning banner visibleEnforcement enabled but zero mappings — all SSO logins will produce zero-binding sessions.

Related pages

SSO RBAC Enablement Guide | PodWarden Hub