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
issmismatch —zitadel_basemust equal the token issuer byte-for-byte, no trailing slash.audmismatch —trusted_audiencesmust contain the token'saud; 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 key —
cookie_key_b64must 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-adminproject 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.