Keycloak
Configure Keycloak for deployment
Overview
Keycloak is an open-source identity and access management platform. Unlike Auth0 and Microsoft Entra ID, Keycloak is self-hosted: you run a Keycloak instance (in the same cluster or externally) and the Guardian Platform authenticates against it.
The platform requires a dedicated Keycloak realm containing three OAuth clients:
- Frontend client (
guardian-platform-frontend) - public SPA client used by Guardian for sign-in - Backend client (
guardian-platform-backend) - confidential client with a service account, used by the Auth Service for user management - Worker client (
governance-worker) - confidential machine-to-machine client used by the governance worker
The keycloak-bootstrap Helm chart automates all of this: it runs a one-time Kubernetes Job that creates the realm, the three clients, the platform’s custom authorization scopes, and an initial platform-admin user via the Keycloak Admin REST API. Run it before deploying the platform chart - the services need valid client credentials at startup.
Prerequisites
- A running Keycloak instance, or a cluster where one can be deployed (this guide includes an in-cluster option)
- Keycloak admin credentials for the master realm
kubectlandhelmaccess to the deployment namespace- A clone of the deployment repository for the
keycloak-bootstrapchart
Helm Configuration
After completing the setup steps below, the platform configuration files will contain the following. <keycloak-url> is Keycloak’s public base URL, including any path it is served under: with the deployment repository’s Keycloak values that is https://<keycloak-host>/keycloak. The issuer must match the one Keycloak reports exactly.
values.yaml:
auth-service:
config:
idp:
provider: "keycloak"
issuer: "<keycloak-url>/realms/guardian" # Verified in step 6
keycloak:
realm: "guardian"
adminUrl: "<keycloak-url>"
clientId: "guardian-platform-frontend"
enableUserManagement: true
tokenExchange:
enabled: true
keyId: "auth-service-001" # Identifier for the token exchange key
governance-service:
config:
keycloakUrl: "<keycloak-url>"
keycloakRealm: "guardian"
guardian:
config:
keycloakUrl: "<keycloak-url>"
keycloakRealm: "guardian"
keycloakClientId: "guardian-platform-frontend"
# Post-install hook: seeds the governance database with the organization
# and the platform-admin user created by the bootstrap
keycloak:
createOrganization: true
realmName: "guardian"
createPlatformAdmin: true
platformAdminEmail: "admin@<platform-domain>" # Must match users.admin.email in bootstrap-values.yaml
url: "<keycloak-url>" # Omit when Keycloak is at http://keycloak:8080/keycloak in this namespaceThe hook signs in to Keycloak as the master realm admin. If the bootstrap uses a non-default admin user or password secret, set keycloak.adminUsername and keycloak.adminPasswordSecret (name and key) in values.yaml to match.
secrets.yaml:
global:
secrets:
create: true
auth:
provider: "keycloak"
keycloak:
secretName: "platform-keycloak"
values:
serviceAccountClientId: "guardian-platform-backend"
serviceAccountClientSecret: "<backend-client-secret>" # Retrieved in step 5
tokenExchangePrivateKey: | # Generate with: openssl genrsa 2048
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
governanceWorker:
secretName: "platform-governance-worker"
values:
encryptionKey: "GENERATE_WORKER_KEY" # Generate with: openssl rand -base64 32
clientId: "governance-worker"
clientSecret: "<worker-client-secret>" # Retrieved in step 5Note: the frontend client ID is configured in values.yaml, not secrets.yaml - it is a public client with no secret.
Quick Start (Bootstrap Chart - Recommended)
Deploy or locate Keycloak, run the keycloak-bootstrap chart, and copy the client secrets it generates into the platform’s chart values, in six steps.
1. Deploy or Identify a Keycloak Instance
If you already run Keycloak, note its URL and admin credentials and skip to step 2. Otherwise, deploy Keycloak into the cluster with the Bitnami chart, using this keycloak-values.yaml:
auth:
adminUser: admin
existingSecret: "keycloak-admin"
passwordSecretKey: "password"
httpRelativePath: "/keycloak/"
production: true
proxyHeaders: xforwarded # TLS terminates at the ingress
postgresql:
enabled: true
auth:
database: keycloak
ingress:
enabled: true
ingressClassName: "nginx"
hostname: guardian.example.com # The platform domain, or a separate host such as keycloak.example.com
path: /keycloak
annotations:
cert-manager.io/issuer: "letsencrypt-prod"
tls: true
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi# Keycloak server admin password (master realm)
kubectl create secret generic keycloak-admin \
--from-literal=password="$(openssl rand -base64 32)" \
--namespace guardian
helm upgrade --install keycloak oci://registry-1.docker.io/bitnamicharts/keycloak \
--namespace guardian \
--values keycloak-values.yaml \
--wait \
--timeout 10mVerify Keycloak is healthy before continuing:
kubectl run curl-test --rm -it --image=curlimages/curl --restart=Never \
--namespace guardian -- \
curl -s -o /dev/null -w "%{http_code}" http://keycloak:9000/keycloak/health/ready2. Create Pre-Bootstrap Secrets
The bootstrap job authenticates to Keycloak with the keycloak-admin secret (created in step 1, or create it now with your external Keycloak’s admin password) and sets the initial Guardian admin user’s password from platform-admin:
# Guardian admin user password (guardian realm user, not the Keycloak server admin)
kubectl create secret generic platform-admin \
--from-literal=password="$(openssl rand -base64 32)" \
--namespace guardian3. Prepare the Bootstrap Values
From the deployment repository checkout, start from the example values file:
cp charts/keycloak-bootstrap/examples/values.yaml bootstrap-values.yamlEdit bootstrap-values.yaml, replacing the domain placeholders with the platform domain:
clients:
frontend:
redirectUris:
- "https://<platform-domain>/*"
webOrigins:
- "https://<platform-domain>"
backend:
redirectUris:
- "https://<platform-domain>/authService/*"
webOrigins:
- "https://<platform-domain>"
users:
admin:
email: "admin@<platform-domain>"If Keycloak is not reachable at the in-cluster default (http://keycloak:8080/keycloak), set its URL:
keycloak:
url: "<keycloak-url>" # External or cross-namespace URL4. Run the Bootstrap
helm upgrade --install keycloak-bootstrap ./charts/keycloak-bootstrap \
--namespace guardian \
--values bootstrap-values.yaml \
--wait \
--timeout 10mThe deployment repository also provides a helper script: ./scripts/keycloak/bootstrap-keycloak.sh -f bootstrap-values.yaml -n guardian. It checks that the Keycloak pod is ready and that the secrets named in the values file exist, runs the chart and waits for the job, prints the job logs (also when the job fails), and then deletes the job. It prints the kubectl get secret commands that read the admin passwords, not the passwords. It finds the Keycloak pod by the app=keycloak label; pass --keycloak-selector (-l) with another label selector if your Keycloak pod is labeled differently.
Monitor the job and confirm it completes:
kubectl get jobs -l app.kubernetes.io/instance=keycloak-bootstrap \
--namespace guardian -w
kubectl logs job/keycloak-bootstrap-keycloak-bootstrap --namespace guardian -fThe job is idempotent: re-running it updates existing clients from the values, and skips the realm, scopes, and users that already exist.
5. Retrieve the Auto-Generated Client Secrets
Keycloak generates the backend and worker client secrets during bootstrap. Copy both from the Keycloak admin console.
Get the master realm admin password:
kubectl get secret keycloak-admin --namespace guardian \
-o jsonpath='{.data.password}' | base64 -dIf Keycloak has no external URL, port-forward its service:
kubectl port-forward svc/keycloak 8080:8080 --namespace guardianSign in to the admin console at <keycloak-url>/admin (or http://localhost:8080/keycloak/admin) as admin, select the guardian realm, then open Clients → guardian-platform-backend → Credentials and copy the Client Secret. Do the same for governance-worker.
6. Update Chart Values
Fill in the Helm Configuration sections:
- The two client secrets from step 5
- A token exchange private key:
openssl genrsa 2048 - The issuer URL, which must equal the
issuerfield returned by:
curl "<keycloak-url>/realms/guardian/.well-known/openid-configuration"Then proceed to the Helm Deployment guide. The platform chart’s post-install hook creates the organization, looks up the admin in the realm by platformAdminEmail, and links that user automatically. The hook fails if no user in the realm has that email.
The hook runs on every install and upgrade and never creates duplicate records. On an existing install it points the organization and the platform admin’s user record at Keycloak and makes the admin an active organization_owner again: other organization roles are kept, and project roles and project assignments in the organization are removed. It does not change an existing organization’s display name.
Alternative: Manual Setup via Admin Console
The bootstrap chart is strongly recommended - it configures the realm, clients, protocol mappers, custom scopes, and service account roles consistently. If you must configure Keycloak manually, replicate what the bootstrap creates:
- Create a
guardianrealm with brute force protection enabled - Create the three clients listed in the Overview: frontend as a public client with the platform domain in redirect URIs and web origins; backend and worker as confidential clients with service accounts enabled
- Grant the backend client’s service account the
query-usersandview-usersrealm-management roles - Create two client scopes and add both as realm default scopes:
sub, with a Subject (oidc-sub-mapper) mapper on the access token, andplatform-audience, with an Audience mapper that addsguardian-platform-frontendto the access token. Assign the frontend client’s default scopes:openid,profile,email,roles,sub, andplatform-audience - Create a
platform-adminrealm user
See the keycloak-bootstrap chart README for the complete list of scopes, mappers, and defaults to replicate.
Verification
Confirm the realm’s OIDC discovery endpoint responds and its issuer field matches auth-service.config.idp.issuer in values.yaml:
curl "<keycloak-url>/realms/guardian/.well-known/openid-configuration"After deploying the platform, sign in to Guardian as platform-admin using the password from the platform-admin secret:
kubectl get secret platform-admin --namespace guardian \
-o jsonpath='{.data.password}' | base64 -dSecurity Best Practices
- Require TLS on all Keycloak endpoints (
sslRequired: externalis the bootstrap default; useallif internal traffic should also be encrypted) - Keep brute force protection enabled on the guardian realm (bootstrap default)
- Store the Keycloak admin password, client secrets, and token exchange private key only in Kubernetes secrets or an approved secret manager
- Leave self-registration disabled unless your onboarding process requires it
- Rotate the backend and worker client secrets periodically - regenerate in Keycloak, update the
platform-keycloakandplatform-governance-workersecrets in the same change window, then restart the services, which read the secrets only at startup:kubectl rollout restart deployment/guardian-platform-auth-service deployment/guardian-platform-governance-service --namespace guardian - Keep the Keycloak version patched; it is part of your authentication attack surface
Official Documentation
Related
- Authentication — how Keycloak compares with the other supported providers
- Identity Providers — what Auth Service supports in Keycloak mode
- Token Exchange and Refresh Tokens — what token exchange needs, including its signing key, and how refresh tokens rotate
- Chart Values — the full values and secrets files these settings go into
- Helm Deployment — deploying the platform after the bootstrap job