Files
harmony/docs/guides/fleet-zitadel-faq.md
Jean-Gabriel Gill-Couture 5e3b0522e0
Some checks failed
Run Check Script / check (pull_request) Failing after 13s
docs(fleet): add operator and device guide
2026-07-27 10:34:31 -04:00

102 lines
4.6 KiB
Markdown

# Fleet and Zitadel FAQ
This page describes the authentication path used by the public Fleet production
composition.
## What does Zitadel provision for Fleet?
`ZitadelSetupScore` creates a Fleet project, an API application for NATS, a web
PKCE application for the production dashboard, the `fleet-admin` and `device`
roles, and the operator machine identity. Device enrollment creates a separate
machine user and JSON key for each device.
An application is an OIDC client configuration. Users belong to a Zitadel
organization; project role grants associate a user with roles in the Fleet
project. A project role is not a Zitadel administrator role.
## How does a device authenticate?
The agent keeps the JSON machine key at
`/etc/fleet-agent/zitadel-key.json`. On each NATS connection it:
1. signs a JWT assertion whose `iss` and `sub` are the machine user ID;
2. sends the assertion to `<issuer>/oauth/v2/token` with the RFC 7523
JWT-bearer grant;
3. requests `openid`, project roles, and the Fleet project audience;
4. takes `id_token` from the response and presents it as the NATS bearer token.
Production requires `id_token`, not `access_token`. Zitadel access tokens are
opaque by default. The ID token is a JWT that the callout can verify through
Zitadel's JWKS and that contains the audience, `client_id`, and role claims used
by Fleet.
The assertion lasts 60 seconds. The ID token is cached in memory and replaced
before its JWT `exp`; no refresh token is stored. The machine key remains valid
until it expires or is removed in Zitadel.
## How is a device ID derived?
Enrollment conventionally creates the username `device-<device-id>`. The
callout reads the ID token's `client_id` claim and strips `device-`. It validates
the result before inserting it into device-scoped NATS subjects.
## What happens while a device is offline?
The machine key remains on disk. Once the network returns, a NATS connection
attempt obtains a fresh ID token if the cached one is near expiry. The agent
cannot mint while Zitadel is unreachable, so reconnect waits until both Zitadel
and NATS are available.
## Are machine keys equivalent to PATs?
Both permit impersonation if copied. A machine key signs short-lived assertions
sent to Zitadel, while a PAT is itself a bearer credential. Zitadel allows more
than one key per machine user, which permits installing a replacement before
removing the old key. Fleet does not currently use TPM-backed key storage.
## Does enrollment make OpenBao secrets available?
No. The same ID token can authenticate to an OpenBao JWT role, but enrollment
must also configure the agent's OpenBao endpoint. Group membership, OpenBao
policies, policy attachments, and secret values must exist separately.
The operator has optional group-source and OpenBao grant-sync support. The
public production composition does not wire the required group source or
OpenBao administrator token, so those behaviors are disabled there.
## How does dashboard login differ?
The dashboard uses an OIDC authorization-code flow with PKCE. It validates the
returned ID token, stores it in an encrypted private cookie, and requires the
`fleet-admin` role. Its Zitadel application is configured to include roles in
the ID token. See [Operator dashboard SSO](./operator-dashboard-sso.md).
## Why is the IAM administrator PAT in a Kubernetes Secret?
`ZitadelSetupScore` uses the PAT for management API operations. Kubernetes
Secrets are not encrypted at rest unless the cluster enables encryption at
rest. Anyone who can read this Secret can act with its privileges. Moving this
bootstrap credential to a stronger storage path remains production-hardening
work.
## Token reference
| Material | Storage | Purpose |
|---|---|---|
| Machine JSON key | Device filesystem | Signs JWT-bearer assertions |
| JWT assertion | Memory and token request | Proves possession of the machine key; valid for 60 seconds |
| OIDC ID token | Agent memory and NATS connection | Verifiable bearer used by the NATS callout and OpenBao JWT login |
| Zitadel access token | Token response only | Opaque by default; not used for production Fleet bearer authentication |
| NATS user JWT | NATS authorization exchange | Carries the permissions issued by the auth callout |
## Code map
| Topic | File |
|---|---|
| Fleet identity composition | `fleet/harmony-fleet-deploy/src/app.rs` |
| Machine-key token exchange | `harmony_zitadel_jwt/src/lib.rs` |
| Shared agent/operator credentials | `fleet/harmony-fleet-auth/src/credentials.rs` |
| Callout validation | `nats/callout/src/zitadel.rs` |
| Device enrollment | `fleet/harmony-fleet-deploy/src/device_setup.rs` |
| Manual diagnosis | [Manual token mint](./fleet-manual-token-mint.md) |