Harthelm Docs
Configure

Sign-in

OIDC providers: what Harthelm needs from one, provider notes and the break-glass admin.

Last updated 2026-10-19

People sign in to Harthelm through an OpenID Connect provider. Harthelm keeps no passwords, except for one optional local emergency account.

What the provider must offer

RequirementWhy
The authorization code flow with PKCE and a confidential clienthow Harthelm signs people in
Redirect URI https://<console>/auth/oidc/callbackthe callback
The scopes openid profile email groupsname, email, picture and groups. Harthelm always requests all four
A groups claim in the ID tokenMembers of oidc.adminGroup (default harthelm-admins) are cluster admins. The claim is oidc.groupsClaim (default groups), a list or a single string, and the flag is recomputed at every sign-in
email_verified: true for addresses the provider has verifiedInvitations attach to a user only by a verified address

Configure it

Terminal
kubectl -n harthelm-system create secret generic harthelm-oidc \
  --from-literal=clientID=<client id> --from-literal=clientSecret=<client secret>
my-values.yaml
oidc:
  issuer: https://id.example.com/
  displayName: Example ID          # the button says "Sign in with Example ID"
  adminGroup: harthelm-admins
  existingSecret: harthelm-oidc
ValueMeaning
oidc.issuerThe issuer URL. Empty turns OIDC off.
oidc.existingSecretThe client's id and secret. oidc.clientIDKey and oidc.clientSecretKey name its keys, so an existing Secret needs no rewrite.
oidc.caConfigMapA ConfigMap with an extra CA (key oidc.caKey, default ca.crt) for an issuer behind a private CA.
oidc.trustEmailVerifiedfalse treats every address of the issuer as unverified. Invitations then cannot be accepted; use it only for an issuer that cannot tell.

With networkPolicy.enabled, core reaches the provider through networkPolicy.egress.idp (port 443); narrow its to to the provider's addresses.

The issuer is discovered at the first sign-in, not when core starts, so core comes up while the provider is down.

Provider notes

ProviderNotes
DexMarks the addresses of static users and of connectors that verify addresses as verified.
AuthentikIts built-in email scope mapping sends email_verified: false for everyone. Use a scope mapping that sets it for users whose address you trust, for example users an admin created.
Keycloak and other full providersConfigure a confidential client with the redirect URI, a groups scope that puts the group names into the ID token, and verified emails.
Microsoft Entra IDSends no email_verified, emits group object IDs by default, and reportedly refuses the scope groups. Put a broker in front. See the AKS track.
Amazon CognitoThe groups claim is cognito:groups; set oidc.groupsClaim to it. Whether it accepts the scope groups is not verified.
GoogleNo groups claim, so nobody becomes cluster admin through it; use a broker.

A broker is an OIDC provider of your own (Dex, Keycloak, Authentik) that signs people in through the upstream provider and issues the groups and verified emails Harthelm needs.

Dex with a GitHub connector

Dex in front of GitHub gives a small installation sign-in through GitHub organizations and teams and no other user store. With one connector Dex skips its own sign-in page.

Dex config
issuer: https://auth.example.org
storage: {type: kubernetes, config: {inCluster: true}}
web: {http: 0.0.0.0:5556}
oauth2: {skipApprovalScreen: true}
enablePasswordDB: false
connectors:
  - type: github
    id: github
    name: GitHub
    config:
      clientID: $GITHUB_CLIENT_ID
      clientSecret: $GITHUB_CLIENT_SECRET
      redirectURI: https://auth.example.org/callback    # also the callback URL of the GitHub OAuth app
      orgs:
        - name: example-org
      teamNameField: slug
staticClients:
  - id: harthelm
    name: Harthelm
    secretEnv: HARTHELM_CLIENT_SECRET
    redirectURIs: [https://console.example.org/auth/oidc/callback]
my-values.yaml
oidc:
  issuer: https://auth.example.org
  displayName: GitHub
  adminGroup: example-org:harthelm-admins
  existingSecret: harthelm-oidc          # clientID: harthelm, clientSecret: the value of HARTHELM_CLIENT_SECRET
  • Groups arrive as <org>:<team>. teamNameField: slug makes the team part the team's slug (harthelm-admins), not its display name. Only members of orgs can sign in at all, and the members of the organization's harthelm-admins team are the cluster admins. Core recomputes admin rights from adminGroup at every sign-in, so removing someone from the team takes the right away at their next sign-in.
  • Dex asks GitHub for the read:org scope to read teams; an organization that restricts third-party OAuth apps has to approve the app.
  • Dex marks the address of a verifying connector as verified (email_verified: true), which invitations need. A GitHub user without a verified address has none to match.
  • With Dex in the cluster behind the same Ingress, Without a load balancer shows how core reaches it.
  • When Dex or GitHub is down nobody can sign in. Enable the break-glass account as the way in for that case.

Sign-in errors

A failed sign-in returns to the sign-in page with a code that the page explains:

CodeMeaning
idp_unavailableNo issuer is configured, it cannot be reached, or core's database failed.
login_expiredThe sign-in took longer than 10 minutes, was used already, or another tab started a newer one. Start again.
access_deniedThe provider refused: the user cancelled or has no access to the application.
account_disabledThe user is disabled in Harthelm.
login_failedAnything else: a token the provider would not issue, a bad ID token, a wrong issuer or nonce.

Sessions

A console session ends after 12 hours without a request, or 7 days after sign-in. Disabling a user ends their sessions at their next request.

Break-glass admin

A local admin for when the OIDC provider is down. Make an argon2id hash of a password and store it with a user name:

Terminal
echo -n "$PASSWORD" | argon2 "$(openssl rand -base64 16)" -id -t 3 -m 16 -p 4 -e
kubectl -n harthelm-system create secret generic harthelm-break-glass \
  --from-literal=username=admin --from-literal=password-hash='<the hash>'
my-values.yaml
auth:
  breakGlass: {enabled: true, existingSecret: harthelm-break-glass}
mail:
  breakGlassTo: [email protected]

The sign-in page then links Use the break-glass account. A break-glass sign-in is a cluster admin, the console shows it with a red banner, it is audited with the client address and mailed to mail.breakGlassTo. After 10 failures within 15 minutes every attempt is refused until the oldest failure leaves the window. The Secret is read at every attempt, so a rotated password applies without a restart.

On this page