All checks were successful
Run Check Script / check (pull_request) Successful in 2m31s
Role-gate follow-up from v0.3 plan Ch1: - `build_login_attempt` appends the `urn:zitadel:iam:org:project:roles` scope, so the gate no longer depends on Zitadel's out-of-band "Assert Roles on Authentication" checkbox (which silently broke it once). Idempotent if the scope is already present. - docs/guides/operator-dashboard-sso.md step 1b + config reference: drop the wrong checkbox instruction, document the in-band scope. Role extraction stays local to each crate (dashboard object-map; callout configurable claim path) — two small, genuinely-different parsers, not a shared crate. Lifting `require_role` to a composable layer is skipped as YAGNI — only `fleet-admin` exists; revisit at the second role.
61 lines
3.2 KiB
Markdown
61 lines
3.2 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/`.
|
|
|
|
## Quickstart (staging)
|
|
|
|
1. **Zitadel app** — create a **Web** application, auth method **PKCE** (no client
|
|
secret), redirect URI `https://fleet-stg.<base>/auth/callback`, post-logout URI
|
|
`https://fleet-stg.<base>/`. Copy its **Client ID**.
|
|
1b. **Roles** — the dashboard requires the **`fleet-admin`** project role. Create that
|
|
role on the project and grant it to each operator user. The login flow requests
|
|
roles **in-band** via the OIDC scope `urn:zitadel:iam:org:project:roles`, so the
|
|
project's **Assert Roles on Authentication** checkbox is **not** needed. Without
|
|
the role grant, users authenticate but get **403 Access denied**.
|
|
2. **Seed config** in OpenBao (namespace `fleet-staging`) — the deploy derives every
|
|
host from `base_domain`, so you set only:
|
|
- `FleetDeployConfig.operator_oidc_client_id` = the Client ID
|
|
- `FleetDeployConfig.operator_trusted_audiences` = `["<Client ID>"]`
|
|
- `FleetDeploySecrets.operator_cookie_key_b64` = `openssl rand -base64 64`
|
|
3. **Deploy**: `./fleet/scripts/dev-deploy-operator.sh`
|
|
4. Open `https://fleet-stg.<base>/` → Zitadel login → back to the dashboard.
|
|
|
|
`fleet_staging_install` already generates the cookie key, so a fresh install needs
|
|
only the Client ID + audiences.
|
|
|
|
## 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).
|
|
|
|
## Config reference
|
|
|
|
The operator reads `ZitadelAuthConfig` + `OperatorCookieKey` via ConfigClient. The
|
|
deploy derives `zitadel_base` / `base_url` / `logout_redirect_uri` from `base_domain`
|
|
(`https://sso-stg.<base>`, `https://fleet-stg.<base>`, `…/`) and fixes
|
|
`scope = openid profile email` (the login flow appends
|
|
`urn:zitadel:iam:org:project:roles` so roles are always asserted); you supply
|
|
`client_id`, `trusted_audiences`, `cookie_key_b64`. All endpoints derive from `zitadel_base`:
|
|
`/.well-known/openid-configuration`, `/oauth/v2/authorize`, `/oauth/v2/token`,
|
|
`/oidc/v1/end_session`.
|
|
|
|
> The dashboard requires both authentication **and** the `fleet-admin` role
|
|
> (see step 1b). A logged-in user without it gets a 403 with a sign-out link.
|