Files
harmony/docs/guides/operator-dashboard-sso.md

2.7 KiB

Operator Dashboard SSO (Zitadel) — setup

Browser SSO for the fleet operator web UI (OIDC Authorization Code + PKCE, public client). Distinct from the agent/callout machine auth (fleet-zitadel-faq); the security rationale is in web-auth-security. Code: harmony_zitadel_auth/.

Deployment

FleetApp declares a dedicated dashboard Web PKCE application with the dashboard callback and logout URLs. The operator's Device Code application is separate. ZitadelSetupScore reconciles both and publishes the dashboard client ID. FleetOperatorScore builds ZitadelAuthConfig from that output and the dashboard Ingress, then generates and retains the session cookie key in the operator Secret. No client ID or cookie key is entered by hand.

The dashboard requires the exact Zitadel project role fleet-admin. Create the role on the fleet project and grant it to each operator. Login automatically requests urn:zitadel:iam:org:project:roles, so the project-level Assert Roles on Authentication setting is not required. Users without the role receive a 403 response that retains the session cookie and includes a sign-out link.

Local dev (serve-web)

fleet/harmony-fleet-operator/dev.sh sets the same config as two HARMONY_CONFIG_<TypeName> env JSON blobs (ConfigClient's env source). Point base_url at http://localhost:18080, register that callback in the app, and turn on the app's Development Mode (Zitadel rejects non-HTTPS redirects otherwise).

When login fails — check these first

  • iss mismatchzitadel_base must equal the token issuer byte-for-byte, no trailing slash.
  • aud mismatchtrusted_audiences must contain the token's aud; Zitadel puts the app's Client ID there by default.
  • Client secret — the app must be PKCE-only; the code never sends a secret.
  • Redirect URI — must be exactly {base_url}/auth/callback.
  • Cookie keycookie_key_b64 must decode to ≥64 bytes, else the dashboard refuses to start (cookie_key_b64 must decode to at least 64 bytes; reconcile keeps running).
  • 403 after login — confirm the user has the exact fleet-admin project role and that the aggregate roles claim is present in the ID token.

Config reference

The operator reads ZitadelAuthConfig and OperatorCookieKey through ConfigClient. The deploy derives zitadel_base, base_url, client ID, trusted audience, logout URI, and scope = openid profile email. The login flow adds the aggregate roles scope once if it is absent. All endpoints derive from zitadel_base: /.well-known/openid-configuration, /oauth/v2/authorize, /oauth/v2/token, /oidc/v1/end_session.