Single sign-on
This page describes how people sign in to a self-hosted ConfigHub with their work accounts, and how to set it up.
A fresh install has one administrator, who signs in with a key. To let other people sign in as themselves, ConfigHub uses Keycloak as a broker in front of your identity provider:
your identity provider → Keycloak (one realm for ConfigHub) → ConfigHub server and UI
OIDC or SAML OIDC
Your provider authenticates people. Keycloak turns that into the tokens ConfigHub reads, and decides two things about each person: which ConfigHub organization they belong to, and which org role they hold there. Both are set up by the commands on this page. For what the server reads and what the realm must contain, however it was set up, see Connecting your identity provider.
Single sign-on is added to a running instance. The administrator key keeps working afterwards, and is the way back in if Keycloak or your provider is the thing that is broken:
cub auth login --private-key confighub-admin --server https://confighub.example.com
cub auth browser-session
Before you start
- ConfigHub v0.9.5 or later. Older servers refuse sign-in to anyone without a mapped org role. To move a running instance, see Upgrading.
- The
cub serverplugin v0.2.11 or later:cub plugin install confighub/cub-server, orcub plugin upgrade server. - Keycloak 26.7, if you run your own. It is the release ConfigHub runs and tests with, and the admin API the commands below use changed in 26.7, so they refuse an older Keycloak and warn about a newer one. ConfigHub uses one realm on it and nothing else.
There are two ways to get Keycloak, depending on how ConfigHub was installed:
- Installed with
cub server install:cub server keycloak installdeploys Keycloak next to the instance and configures everything. It is meant for evaluation: that Keycloak runs in development mode with an embedded database. See Keycloak next to the instance. - Deployed from your own manifests, with a Keycloak you run: three commands set up the realm and write the changes for you to apply. Nothing under
~/.confighub/serversis needed. See A Keycloak you run.
Connecting your identity provider and Org roles are the same in both cases.
A Keycloak you run
This is three steps, and they can be done by two people:
cub server keycloak bootstrap, by an administrator of your Keycloak: creates the realm and the ConfigHub server's client.cub server keycloak configure, by whoever runs ConfigHub: sets up the rest of the realm and writes the changes to the server's and the UI's configuration.cub server keycloak idp, by whoever runs ConfigHub: connects your identity provider.
Only the first step needs a Keycloak administrator. It creates a key for the ConfigHub server's client, and that client can administer the ConfigHub realm and nothing else on the Keycloak. The other two steps sign in as that client, with that key. The key is also the server's own credential: it goes into the server's Secret, and the server uses it to sign its requests to Keycloak. No client secret or Keycloak password reaches the server.
Choosing the Keycloak URL
Every step takes --keycloak-url: Keycloak's base URL as browsers reach it, without /realms/.... For example https://keycloak.example.com, or https://sso.example.com/auth if Keycloak is served under a path.
It has to be exact. The server checks that each token's issuer is <keycloak-url>/realms/<realm>, and Keycloak writes the issuer from its own hostname setting (KC_HOSTNAME). Check what your Keycloak uses:
curl -s https://keycloak.example.com/realms/master/.well-known/openid-configuration | jq -r .issuer
# https://keycloak.example.com/realms/master
Use that value without /realms/master.
If the ConfigHub server cannot reach Keycloak at that address from inside the cluster, pass the address it can use as --internal-url to configure. Browsers still use --keycloak-url. If the command you run cannot reach it either, --admin-url sets the address for the command alone.
1. Bootstrap the realm
Run as an administrator of the Keycloak's master realm:
export KEYCLOAK_ADMIN_PASSWORD=...
cub server keycloak bootstrap --keycloak-url https://keycloak.example.com
The user is admin unless you pass --admin-user. To use a master-realm client with the admin role instead of a user, pass --admin-client-id and put its secret in KEYCLOAK_ADMIN_CLIENT_SECRET.
This creates the realm (confighub, or --realm) with one client, confighub, and gives that client's service account the administration of the realm. It writes the client's private key to ~/.confighub/keycloak/<keycloak host>/<realm>/keycloak-client-key.jwk (or --out-dir). Keycloak holds only the public half.
Hand the key file to whoever runs ConfigHub, and treat it as a secret: it is the server's credential.
Running it again on an existing realm is safe. It sets the client to the key it finds in the output directory, or --client-key-file, or a new one, and grants the realm's administration again. If it makes a new key, a server already configured with the old one cannot reach Keycloak until its Secret has the new one.
2. Configure the realm and the instance
cub server keycloak configure \
--keycloak-url https://keycloak.example.com \
--ui-url https://confighub.example.com \
--org "Example" --org-domain example.com
--ui-url is where browsers reach the ConfigHub UI. The UI's client only returns there after sign-in. --org and --org-domain name the organization people join; see Organizations and email domains.
The key is read from where bootstrap wrote it. If you received it as a file, pass --client-key-file.
This signs in to Keycloak as the server's client and sets up, or brings up to date:
- organizations, turned on for the realm
- the client
cubsigns in through, and the UI's client - the organization
- the realm roles that ConfigHub reads org roles from
Then it writes patches for your manifests. It does not change anything in the cluster.
| Object | What changes |
|---|---|
ConfigMap confighub-env (server) |
KEYCLOAK_REALM, KEYCLOAK_AUTH_URL, KEYCLOAK_INTERNAL_URL (with --internal-url), KEYCLOAK_CLIENT_ID, KEYCLOAK_DEVICE_CLIENT_ID, CONFIGHUB_AUTH_ISSUER, CONFIGHUB_TOKEN_EXCHANGE_AUDIENCE |
ConfigMap confighub-ui-env (UI) |
CONFIGHUB_UI_OAUTH_CLIENT_ID |
Secret confighub-secret-env (server) |
KEYCLOAK_CLIENT_PRIVATE_KEY_JWK: the server's key |
Merge the patches into the manifests you apply, or apply them with the kubectl patch commands it prints. Either way, restart both Deployments afterwards. A ConfigMap change alone does not restart the pods.
kubectl -n confighub rollout restart deployment/confighub deployment/confighub-ui
If you patch the live objects without changing your manifests, the next apply of your manifests removes the change.
Running configure again is safe: it compares the realm with what it should be and changes only what differs.
When the server's Secret comes from a secret store
If something else produces confighub-secret-env, a patch to the Secret lasts only until the next sync. Tell configure how the Secret is produced, and it writes the key as a file for you to put in your store, plus the change to the object that produces the Secret.
With External Secrets:
cub server keycloak configure ... \
--secrets external-secrets \
--remote-key confighub/server --remote-property KEYCLOAK_CLIENT_PRIVATE_KEY_JWK
You get the entry to add to the ExternalSecret's data (the ExternalSecret is confighub-secret-env unless you pass --external-secret). --remote-key is the entry in your store, and --remote-property the property within it, if the entry holds several values.
With the Secrets Store CSI driver and AWS Secrets Manager:
cub server keycloak configure ... \
--secrets secrets-store-csi --secret-provider-class confighub \
--remote-key confighub/server --remote-property KEYCLOAK_CLIENT_PRIVATE_KEY_JWK
You get the entries to add to the SecretProviderClass, and the volume and mount to add to the confighub Deployment. The driver writes the Secret only while a pod mounts the class, so the volume is required even though the server reads the key from its environment.
In both cases, store the key file's contents in your secret store before restarting the server.
3. Connect your identity provider
Pass --keycloak-url to cub server keycloak idp, and the rest as described in Connecting your identity provider. For example, for a SAML provider:
cub server keycloak idp --keycloak-url https://keycloak.example.com \
--saml-metadata-file idp-metadata.xml --alias corp \
--org-domain example.com \
--email-attribute mail --first-name-attribute givenName --last-name-attribute sn \
--role-attribute memberOf --role-map "CN=confighub-admins,OU=Groups,DC=example,DC=com=admin"
It signs in as the server's client, with the same key as configure, and changes nothing in the cluster.
Keycloak next to the instance
For an instance installed with cub server install:
cub server keycloak install
This deploys Keycloak into the instance's namespace, imports a realm with the clients ConfigHub needs, and reconfigures and restarts the server and the UI to use it. People then sign in on the UI through Keycloak.
This Keycloak runs in development mode with an embedded database, which lets it start on a laptop with no certificate or DNS. An instance serving real users needs a Keycloak of its own; see A Keycloak you run.
To open its admin console:
cub server keycloak open # copies the admin password to the clipboard and opens the console
cub server keycloak open --print-url # prints the URL only
Sign in as kcadmin. That account is Keycloak's own administrator and opens the console only; it is not a ConfigHub account.
Connecting your identity provider
cub server keycloak idp -i
-i asks for what is needed, starting from what the realm already has. The commands below pass the same answers as flags.
OpenID Connect
Register an application for ConfigHub at your provider, then pass its discovery URL, client id and secret:
cub server keycloak idp \
--discovery-url https://example.okta.com/.well-known/openid-configuration \
--client-id 0oa1b2c3 --client-secret ... \
--org "Example" --org-domain example.com
Okta, Entra ID, Google Workspace and Auth0 all publish a discovery document. The command prints a redirect URI at the end: register it with the application at your provider. Sign-in fails on the way back without it.
If your provider expects the client secret in an Authorization header, add --client-auth-method client_secret_basic.
SAML
Pass the provider's metadata, as a file or as a URL Keycloak can fetch, and the attributes that carry each person's email and name:
cub server keycloak idp \
--saml-metadata-file idp-metadata.xml --alias corp \
--org "Example" --org-domain example.com \
--email-attribute mail --first-name-attribute givenName --last-name-attribute sn
Use --saml-metadata-url instead of --saml-metadata-file for a URL. The email attribute is required: the organization's email domain is matched against it.
The command prints the service-provider metadata URL for Keycloak. Register Keycloak at your provider from it (in PingFederate, an SP connection; in Entra ID or Okta, a SAML application), and release the attributes you named above, and the group attribute if you map org roles.
--alias is a short name for the provider. It appears in the redirect URI, so choose it before registering Keycloak at your provider.
Organizations and email domains
ConfigHub reads a person's organization from their token. Someone who belongs to no organization sees a page saying their access is pending approval.
The organization's email domain is what makes membership automatic. Keycloak links the organization to your provider by that domain: someone who enters an address at that domain on the sign-in page is sent to your provider, and on their way back becomes a member of the organization. That is why idp sets the organization and the provider in one command; one without the other does not produce a working sign-in.
--org sets the organization's name. On a re-run, leave it out to keep the name as it is.
Org roles
Each person holds one org role: admin, manager, editor, user, viewer, creator or none. ConfigHub reads it from the person's Keycloak realm roles, which have the same names. Someone with none of them gets creator.
Give each person one role. If someone holds several, ConfigHub uses the first one it recognizes, and you do not control the order.
From your identity provider
The usual way is to derive roles from groups at your provider. Name the SAML attribute or OIDC claim that lists a person's groups, and map values to roles:
cub server keycloak idp --alias corp \
--role-attribute memberOf \
--role-map "CN=confighub-admins,OU=Groups,DC=example,DC=com=admin" \
--role-map "CN=confighub-viewers,OU=Groups,DC=example,DC=com=viewer"
Each --role-map is value=role, split at the last =, so group DNs that contain = work as written. Add --keycloak-url for a Keycloak you run.
Roles follow the provider at every sign-in: removing someone from the group at your provider removes the role the next time they sign in. To change the mapping, run the command again with the full new set of --role-map. Mappings from an earlier run that are not listed again are removed. Running it with only --alias and the role flags changes the mapping and leaves the rest of the provider as it is.
In Keycloak
You can also grant a realm role to a person (Users → Role mapping) or to a Keycloak group in the Keycloak admin console. A role granted there and a role mapped from your provider add up, so a person may end up with two; avoid granting both to the same person.
Signing in
Open the ConfigHub UI and enter your work email address. Keycloak sends you to your identity provider, and after you sign in there you land in ConfigHub as a member of the organization, with your org role.
When testing with several accounts, use a separate browser profile per account, or quit the browser completely between them. Private windows share one session until the browser quits.
cub signs in through the same Keycloak:
cub auth login --server https://confighub-api.example.com
Troubleshooting
The UI says it has no OAuth client configured. The UI's ConfigMap does not have CONFIGHUB_UI_OAUTH_CLIENT_ID, or the UI was not restarted after it changed.
Sign-in fails on the way back from Keycloak, and the server's logs mention the token issuer. --keycloak-url does not match the issuer Keycloak puts in its tokens. Check it as described in Choosing the Keycloak URL and run configure again with the matching value.
The server cannot reach Keycloak. The server's logs show the address it dials. If browsers and the cluster reach Keycloak at different addresses, pass the cluster's to configure as --internal-url.
People land on the pending-approval page. They are not members of the organization. Check that their email address is at the organization's domain (--org-domain), and for SAML, that your provider releases the attribute named by --email-attribute.
Everyone gets creator. No role mapping matched. Check that your provider releases the attribute named by --role-attribute, and that the --role-map values match what it sends exactly. In the Keycloak console, the user's Role mapping tab shows the roles they received.
Sign-in is refused for people without a mapped role. The server is older than v0.8.39. Upgrade to v0.9.5 or later.