ZitadelScore:
- Auto-provisions an `iam-admin-pat` Kubernetes secret via the chart's
FirstInstance.Org.Machine.Pat block. ZitadelSetupScore depended on
this secret existing; without the chart values, the prior code path
was non-functional.
- New `external_port: Option<u32>` field. Controls Zitadel's emitted
issuer URL when the host port mapping isn't 80/443 (k3d typically
maps 8080:80). Without it, JWT-bearer audience validation 500s with
`Errors.Internal` because the assertion's `aud` doesn't match the
chart-default issuer at port 80.
ZitadelSetupScore is extended for the JWT-bearer flow needed by the
NATS auth callout:
- API apps (resource servers — required for project-id audience scope)
- Project roles (`POST .../projects/{id}/roles`, idempotent)
- Machine users with KEY_TYPE_JSON keys (provisioned + cached
device-side; Zitadel does not expose the key material on subsequent
reads, so the local cache is the source of truth)
- User grants (project + role keys)
Cache (ZitadelClientConfig) gains projects, machine_user_ids,
machine_keys, and user_grants — keyed for idempotency across re-runs.
Backwards compatible with existing harmony_sso example: the new fields
have `#[serde(default)]` and prior callers just need empty vecs.
Refresh upgrade-by-default in helm chart (separate commit) lets
ExternalPort changes propagate to existing releases on re-run.
Harmony SSO Example
Deploys Zitadel (identity provider) and OpenBao (secrets management) on a local k3d cluster, then demonstrates using them as harmony_config backends for shared config and secret management.
Prerequisites
- Docker running
- Ports 8080 and 8200 free
/etc/hostsentries (or use a local DNS resolver):127.0.0.1 sso.harmony.local 127.0.0.1 bao.harmony.local
Usage
Full deployment
# Deploy everything (OpenBao + Zitadel)
cargo run -p example-harmony-sso
# OpenBao only (faster, skip Zitadel)
cargo run -p example-harmony-sso -- --skip-zitadel
Config storage demo (token auth)
After deployment, run the config demo to verify harmony_config works with OpenBao:
cargo run -p example-harmony-sso -- --demo
This writes and reads a SsoExampleConfig through the ConfigManager chain (EnvSource -> StoreSource<OpenbaoSecretStore>), demonstrating environment variable overrides and persistent storage in OpenBao KV v2.
SSO device flow demo
Requires a Zitadel application configured for device code grant:
HARMONY_SSO_CLIENT_ID=<zitadel-app-client-id> \
cargo run -p example-harmony-sso -- --sso-demo
Cleanup
cargo run -p example-harmony-sso -- --cleanup
What gets deployed
| Component | Namespace | Access |
|---|---|---|
| OpenBao (standalone, file storage) | openbao |
http://bao.harmony.local:8200 |
| Zitadel (with CNPG PostgreSQL) | zitadel |
http://sso.harmony.local:8080 |
OpenBao configuration
- Auth methods: userpass, JWT
- Secrets engine: KV v2 at
secret/ - Policy:
harmony-devgrants CRUD onsecret/data/harmony/* - Userpass credentials:
harmony/harmony-dev-password - JWT auth: configured with Zitadel as OIDC provider, role
harmony-developer - Unseal keys: saved to
~/.local/share/harmony/openbao/unseal-keys.json
Architecture
Developer CLI
|
|-- harmony_config::ConfigManager
| |-- EnvSource (HARMONY_CONFIG_* env vars)
| |-- StoreSource<OpenbaoSecretStore>
| |-- Token auth (OPENBAO_TOKEN)
| |-- Cached token validation
| |-- Zitadel OIDC device flow (RFC 8628)
| |-- Userpass fallback
|
v
k3d cluster (harmony-example)
|-- OpenBao (KV v2 secrets engine)
| |-- JWT auth -> validates Zitadel id_tokens
| |-- userpass auth -> dev credentials
|
|-- Zitadel (OpenID Connect IdP)
|-- Device authorization grant
|-- Federated login (Google, GitHub, Entra ID)