Skip to content
Set up single sign-on

Set up single sign-on

A freshly deployed cluster has ZITADEL running and nothing configured in it. These are the steps that turn that into a working Google login for Grafana, Harbor, the Flux UI and Headlamp — the same steps on both clouds, with different values.

For what the resulting stack actually does — and why ZITADEL sits in the middle of it — see Authentication.

The values for your cluster

Everything below is the same on both clouds except these:

aws-0gcp-0
--clusteraws-0gcp-0
--cloudawsgcp
account flag--region eu-west-3--project ogenki-435905
IDP_URLhttps://auth.cloud.ogenki.iohttps://auth.gcp.cloud.ogenki.io
PRIVATE_DOMAINpriv.aws.ogenki.iopriv.gcp.ogenki.io
step 2not needed — External Secrets uses EKS Pod Identity, granted by OpenTofurequired

Set them once and the rest of the page copies straight into a shell:

On aws-0:

CL="--cluster aws-0 --cloud aws --region eu-west-3"
IDP_URL=https://auth.cloud.ogenki.io
PRIVATE_DOMAIN=priv.aws.ogenki.io

On gcp-0:

CL="--cluster gcp-0 --cloud gcp --project ogenki-435905"
IDP_URL=https://auth.gcp.cloud.ogenki.io
PRIVATE_DOMAIN=priv.gcp.ogenki.io

The steps

The order is load-bearing: each one creates what the next reads. Running only the first leaves you with OIDC clients and no way to log in through Google.

Every step is idempotent — re-running prints [skip …] and changes nothing.

# 1. Register the OIDC clients, the project, its roles, and
#    projectRoleAssertion. That last one is not optional: with it off ZITADEL
#    puts NO roles in any token AND leaves ctx.v1.user.grants empty inside the
#    groups action, so every consumer authenticates and then has no groups.
./scripts/zitadel-oidc-clients.sh sync $CL --apply
# 2. GCP ONLY. Let External Secrets read what step 1 just created. Those secrets
#    did not exist when the OpenTofu stack applied, so nothing granted access to
#    them. On AWS this step does not exist — External Secrets authenticates with
#    EKS Pod Identity, which OpenTofu already granted.
./scripts/secret-store.sh grant --cloud gcp --project ogenki-435905 --apply
# 3. The Google identity provider, the LOGIN POLICY entry that actually enables
#    it, and the action that flattens project roles into a `groups` claim.
#    Creating the provider without the policy entry gives "User not found".
IDP_URL=$IDP_URL ./scripts/zitadel-idp.sh sync $CL --apply

# 4. Harbor's auth mode. Harbor stores this in its DATABASE, not in the chart,
#    so no manifest can express it and a fresh cluster has no SSO button.
PRIVATE_DOMAIN=$PRIVATE_DOMAIN ./scripts/harbor-oidc.sh sync $CL --apply

Then log in once through Google, at any consumer. That first login is what CREATES your ZITADEL user — the IdP auto-registers it — so there is nobody to authorise before it. Afterwards:

# 5. Give yourself the admin role. Group-based RBAC (cluster-admin via the
#    `admin` group) does nothing until a user actually holds it.
./scripts/zitadel-oidc-clients.sh sync $CL --grant-admin you@example.com --apply

The one step no script can do

The OAuth client’s authorized redirect URI, on the Google side, must list:

https://auth.<public domain>/ui/login/login/externalidp/callback

Google accepts many redirect URIs on one client, so a single client serves every cluster — add both clusters’ URIs once and you are done. zitadel-idp.sh prints the exact URI on every run and cannot add it for you.

Then check it worked

Open Grafana, Harbor, the Flux UI or Headlamp. You should be offered a Google button rather than a username field, and after logging in your ZITADEL user exists.

The per-cloud verify pages cover the rest of the post-deploy checks: aws-0 · gcp-0.

A restored ZITADEL can predate its own configuration. aws-0 bootstraps its database from a frozen backup, so a cluster rebuilt today comes back with whatever OIDC clients and roles existed when that seed was taken — not the ones this page creates. Re-run steps 1, 3 and 4 after a restore; they are idempotent and will fill in whatever the seed is missing. See Restore a database.

Related

  • Authentication — what the stack does and where the two clouds genuinely differ
  • ADR-0024 — why each cloud runs its own ZITADEL rather than sharing one