53 lines
2.7 KiB
Markdown
53 lines
2.7 KiB
Markdown
# 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](./fleet-zitadel-faq.md)); the security rationale is in
|
|
[web-auth-security](./web-auth-security.md). 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` mismatch** — `zitadel_base` must equal the token issuer byte-for-byte, no
|
|
trailing slash.
|
|
- **`aud` mismatch** — `trusted_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 key** — `cookie_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`.
|