Keycloak
Overview
Section titled “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 Governance Platform authenticates against it.
The platform requires a dedicated Keycloak realm containing three OAuth clients:
- Frontend client (
governance-platform-frontend) - public SPA client used by Governance Studio for sign-in - Backend client (
governance-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
Section titled “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 checkout of the deployment repository for the
keycloak-bootstrapchart
Helm Configuration
Section titled “Helm Configuration”After completing the setup steps below, the platform configuration files will contain:
values.yaml:
auth-service: config: idp: provider: "keycloak" issuer: "https://<keycloak-host>/realms/governance" # Verified in step 6 keycloak: realm: "governance" adminUrl: "https://<keycloak-host>" # Keycloak base URL clientId: "governance-platform-frontend" enableUserManagement: true tokenExchange: enabled: true keyId: "auth-service-001" # Identifier for the token exchange key
governance-service: config: keycloakUrl: "https://<keycloak-host>" keycloakRealm: "governance"
governance-studio: config: keycloakUrl: "https://<keycloak-host>" keycloakRealm: "governance" keycloakClientId: "governance-platform-frontend"
# Post-install hook: seeds the governance database with the organization# and the platform-admin user created by the bootstrapkeycloak: createOrganization: true realmName: "governance" createPlatformAdmin: true platformAdminEmail: "" # Defaults to admin@<global.domain>secrets.yaml:
global: secrets: create: true auth: provider: "keycloak" keycloak: secretName: "platform-keycloak" values: serviceAccountClientId: "governance-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)
Section titled “Quick Start (Bootstrap Chart - Recommended)”1. Deploy or Identify a Keycloak Instance
Section titled “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:
# Keycloak server admin password (master realm)kubectl create secret generic keycloak-admin \ --from-literal=password="$(openssl rand -base64 32)" \ --namespace governance
helm upgrade --install keycloak bitnami/keycloak \ --namespace governance \ --values keycloak-values.yaml \ --wait \ --timeout 10mSee the deployment repository’s Keycloak guides for complete keycloak-values.yaml examples per cloud provider, including ingress and TLS settings.
Verify Keycloak is healthy before continuing:
kubectl run curl-test --rm -it --image=curlimages/curl --restart=Never \ --namespace governance -- \ curl -s -o /dev/null -w "%{http_code}" http://keycloak:9000/keycloak/health/ready2. Create Pre-Bootstrap Secrets
Section titled “2. 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 Governance Studio admin user’s password from platform-admin:
# Governance Studio admin user password (governance realm user, not the Keycloak server admin)kubectl create secret generic platform-admin \ --from-literal=password="$(openssl rand -base64 32)" \ --namespace governance3. Prepare the Bootstrap Values
Section titled “3. 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: "https://<keycloak-host>" # External or cross-namespace URL4. Run the Bootstrap
Section titled “4. Run the Bootstrap”helm upgrade --install keycloak-bootstrap ./charts/keycloak-bootstrap \ --namespace governance \ --values bootstrap-values.yaml \ --wait \ --timeout 10mThe deployment repository also provides a helper script that validates prerequisites, runs the chart, and monitors the job: ./scripts/keycloak/bootstrap-keycloak.sh -f bootstrap-values.yaml -n governance.
Monitor the job and confirm it completes:
kubectl get jobs -l app.kubernetes.io/instance=keycloak-bootstrap \ --namespace governance -wkubectl logs job/keycloak-bootstrap --namespace governance -fThe job is idempotent - re-running it skips resources that already exist.
5. Retrieve the Auto-Generated Client Secrets
Section titled “5. Retrieve the Auto-Generated Client Secrets”Keycloak generates the backend and worker client secrets during bootstrap. Retrieve both via the Admin API:
# Port-forward the Keycloak service (skip if Keycloak has an external URL)kubectl port-forward svc/keycloak 8080:8080 --namespace governance &
# Get an admin tokenADMIN_PASS=$(kubectl get secret keycloak-admin --namespace governance \ -o jsonpath='{.data.password}' | base64 -d)TOKEN=$(curl -s -X POST "http://localhost:8080/keycloak/realms/master/protocol/openid-connect/token" \ -d "username=admin" \ -d "password=$ADMIN_PASS" \ -d "grant_type=password" \ -d "client_id=admin-cli" | jq -r '.access_token')
# Backend client secretcurl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/keycloak/admin/realms/governance/clients?clientId=governance-platform-backend" \ | jq -r '.[0].secret'
# Worker client secretcurl -s -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/keycloak/admin/realms/governance/clients?clientId=governance-worker" \ | jq -r '.[0].secret'
kill %1Alternatively, use the admin console: Clients → governance-platform-backend → Credentials tab (and the same for governance-worker).
6. Update Chart Values
Section titled “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 you can verify with:
curl -s https://<keycloak-host>/realms/governance/.well-known/openid-configuration | jq '.issuer'Then proceed to the Helm Deployment guide. The platform chart’s post-install hook creates the organization and links the platform-admin user automatically.
Alternative: Manual Setup via Admin Console
Section titled “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
governancerealm 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 the platform’s custom authorization scopes and assign the frontend client’s default scopes (
openid,profile,email,roles,sub) - Create a
platform-adminrealm user
See the keycloak-bootstrap chart README for the complete list of scopes, mappers, and defaults to replicate.
Verification
Section titled “Verification”Confirm the realm’s OIDC discovery endpoint responds and the issuer matches values.yaml:
curl -s https://<keycloak-host>/realms/governance/.well-known/openid-configuration | jq '.issuer'After deploying the platform, sign in to Governance Studio as platform-admin using the password from the platform-admin secret:
kubectl get secret platform-admin --namespace governance \ -o jsonpath='{.data.password}' | base64 -dSecurity Best Practices
Section titled “Security 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 governance 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, then update the
platform-keycloakandplatform-governance-workersecrets in the same change window - Keep the Keycloak version patched; it is part of your authentication attack surface