Files
harmony/examples/harmony_sso
Jean-Gabriel Gill-Couture b8bc2217fd feat(zitadel): ExternalPort + machine-user/role/key/grant provisioning
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.
2026-05-03 15:01:22 -04:00
..

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/hosts entries (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-dev grants CRUD on secret/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)