End-to-End Setup Guide¶
This guide walks you through deploying a production-ready Keycloak setup from scratch, including database configuration, high availability, TLS, and monitoring.
For a simpler quick start, see the Quick Start Guide.
Overview¶
This guide covers:
- Infrastructure Setup - Kubernetes cluster, ingress, cert-manager, CloudNativePG
- Operator Installation - Deploy the Keycloak operator with monitoring
- Database Configuration - PostgreSQL with CloudNativePG, backup, HA
- Keycloak Deployment - Multi-replica Keycloak with TLS and ingress
- Multi-Tenant Setup - Platform team configures RBAC and namespace grants
- Realm Creation - Application teams create and manage realms
- Client Configuration - OAuth2/OIDC client setup with credential management
- Verification & Testing - End-to-end OAuth2 flow validation
- Production Checklist - Security, monitoring, backup verification
Estimated Time: 45-60 minutes
Prerequisites¶
Required¶
| Component | Version | Purpose | Installation |
|---|---|---|---|
| Kubernetes | 1.26+ | Container orchestration | kubernetes.io |
| kubectl | 1.26+ | Kubernetes CLI | Install Guide |
| Helm | 3.8+ | Package manager | helm.sh |
| CloudNativePG | 1.20+ | PostgreSQL operator | CNPG Docs |
Recommended for Production¶
| Component | Purpose | Installation |
|---|---|---|
| Ingress Controller | External access (nginx, traefik) | Ingress NGINX |
| cert-manager | Automatic TLS certificates | cert-manager Docs |
| Prometheus | Metrics collection | Prometheus Operator |
| Grafana | Dashboards | Grafana Helm Chart |
Cluster Requirements¶
- Nodes: 3+ nodes for high availability
- CPU: 4+ cores per node recommended
- Memory: 8+ GB per node recommended
- Storage: 100+ GB available (for database)
- RBAC: Cluster admin permissions required for installation
Part 1: Infrastructure Setup¶
1.1 Install Prerequisites¶
CloudNativePG Operator¶
# Add CloudNativePG Helm repository
helm repo add cnpg https://cloudnative-pg.io/charts
helm repo update
# Install CloudNativePG operator
helm install cnpg cnpg/cloudnative-pg \
--namespace cnpg-system \
--create-namespace \
--set monitoring.podMonitorEnabled=true
# Verify installation
kubectl get pods -n cnpg-system
Ingress Controller (nginx)¶
# Install ingress-nginx
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.metrics.enabled=true \
--set controller.service.type=LoadBalancer
# Get external IP (may take a few minutes)
kubectl get svc -n ingress-nginx ingress-nginx-controller
cert-manager (for TLS)¶
# Install cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set installCRDs=true
# Verify installation
kubectl get pods -n cert-manager
1.2 Configure DNS¶
Point your domain to the ingress controller's external IP:
# Get ingress external IP
INGRESS_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Configure DNS:"
echo " keycloak.example.com → $INGRESS_IP"
Create DNS A records:
- keycloak.example.com → Ingress IP
- *.keycloak.example.com → Ingress IP (for realm subdomains, optional)
1.3 Create TLS Issuer¶
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com # ← Update this
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
class: nginx
EOF
Verify the issuer is ready:
Part 2: Operator Installation¶
2.1 Create Operator Namespace¶
2.2 Install Operator with Monitoring¶
# Install from Helm repository (or local chart)
helm install keycloak-operator ./charts/keycloak-operator \
--namespace keycloak-operator-system \
--set metrics.enabled=true \
--set replicas=2 \
--set resources.requests.cpu=100m \
--set resources.requests.memory=256Mi \
--set resources.limits.cpu=500m \
--set resources.limits.memory=512Mi
2.3 Verify Operator¶
# Check pods are running
kubectl get pods -n keycloak-operator-system
# Expected: 2 replicas running
# Check operator logs
kubectl logs -n keycloak-operator-system -l app=keycloak-operator --tail=50
# Verify CRDs are installed
kubectl get crd | grep vriesdemichael.github.io
# Expected output:
# keycloakclients.vriesdemichael.github.io
# keycloakrealms.vriesdemichael.github.io
# keycloaks.vriesdemichael.github.io
2.4 Configure Service Monitor (Prometheus)¶
kubectl apply -f - <<EOF
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: keycloak-operator
namespace: keycloak-operator-system
spec:
selector:
matchLabels:
app: keycloak-operator
endpoints:
- port: metrics
interval: 30s
EOF
Part 3: Database Configuration¶
3.1 Create Database Namespace¶
3.2 Configure Database Storage¶
Create a StorageClass for database storage (example for cloud providers):
# For AWS EBS
kubectl apply -f - <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000"
throughput: "125"
allowVolumeExpansion: true
EOF
3.3 Deploy PostgreSQL Cluster¶
kubectl apply -f - <<EOF
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: keycloak-db
namespace: keycloak-db
spec:
instances: 3 # High availability
primaryUpdateStrategy: unsupervised
postgresql:
parameters:
max_connections: "200"
shared_buffers: "256MB"
effective_cache_size: "1GB"
work_mem: "16MB"
bootstrap:
initdb:
database: keycloak
owner: keycloak
secret:
name: keycloak-db-credentials
storage:
storageClass: fast-ssd
size: 50Gi
monitoring:
enabled: true
podMonitorEnabled: true
backup:
barmanObjectStore:
destinationPath: s3://my-backup-bucket/keycloak-db # ← Update this
s3Credentials:
accessKeyId:
name: backup-s3-credentials
key: ACCESS_KEY_ID
secretAccessKey:
name: backup-s3-credentials
key: ACCESS_SECRET_KEY
wal:
compression: gzip
data:
compression: gzip
retentionPolicy: "30d"
EOF
3.4 Create Database Credentials Secret¶
# Generate secure password
DB_PASSWORD=$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')
kubectl create secret generic keycloak-db-credentials \
--from-literal=username=keycloak \
--from-literal=password="$DB_PASSWORD" \
--namespace=keycloak-db
3.5 Create Backup S3 Credentials (if using S3 backup)¶
kubectl create secret generic backup-s3-credentials \
--from-literal=ACCESS_KEY_ID="your-access-key" \
--from-literal=ACCESS_SECRET_KEY="your-secret-key" \
--namespace=keycloak-db
3.6 Verify Database Cluster¶
# Check cluster status
kubectl get cluster -n keycloak-db
# Expected: STATUS=Cluster in healthy state
# Check pods
kubectl get pods -n keycloak-db
# Expected: 3 pods (1 primary, 2 replicas)
# Check which is primary
kubectl get cluster keycloak-db -n keycloak-db -o jsonpath='{.status.currentPrimary}'
# Test database connection
kubectl exec -it -n keycloak-db keycloak-db-1 -- psql -U keycloak -c "SELECT version();"
Part 4: Keycloak Deployment¶
4.1 Create Keycloak Namespace¶
4.2 Deploy Keycloak Instance¶
kubectl apply -f - <<EOF
apiVersion: vriesdemichael.github.io/v1
kind: Keycloak
metadata:
name: keycloak
namespace: keycloak-system
spec:
replicas: 3 # High availability
image:
repository: quay.io/keycloak/keycloak
tag: "26.0.0"
database:
type: cnpg
cluster: keycloak-db
namespace: keycloak-db
credentialsSecret: keycloak-db-credentials
service:
type: ClusterIP
http:
port: 8080
management:
port: 9000
tls:
enabled: true
termination: edge # TLS terminated at ingress
ingress:
enabled: true
className: nginx
hostname: keycloak.example.com # ← Update this
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
tls:
- secretName: keycloak-tls
hosts:
- keycloak.example.com
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 2Gi
jvm:
heapSize: "1536m"
probes:
liveness:
initialDelaySeconds: 120
periodSeconds: 30
readiness:
initialDelaySeconds: 60
periodSeconds: 10
EOF
4.3 Wait for Keycloak to Become Ready¶
# This takes 3-5 minutes for initial startup
kubectl wait --for=condition=Ready keycloak/keycloak \
-n keycloak-system \
--timeout=10m
# Check status
kubectl get keycloak -n keycloak-system
# Expected: PHASE=Ready
# Check pods
kubectl get pods -n keycloak-system -l app=keycloak
# Expected: 3/3 pods running
4.4 Retrieve Admin Credentials¶
# Get admin username
kubectl get secret keycloak-admin-credentials \
-n keycloak-system \
-o jsonpath='{.data.username}' | base64 -d && echo
# Get admin password
kubectl get secret keycloak-admin-credentials \
-n keycloak-system \
-o jsonpath='{.data.password}' | base64 -d && echo
# Store for later use
export KEYCLOAK_ADMIN_USER=$(kubectl get secret keycloak-admin-credentials -n keycloak-system -o jsonpath='{.data.username}' | base64 -d)
export KEYCLOAK_ADMIN_PASS=$(kubectl get secret keycloak-admin-credentials -n keycloak-system -o jsonpath='{.data.password}' | base64 -d)
Note: Admin credentials are managed by the operator and stored in secrets. You should never need direct access to the Keycloak admin console - all configuration is done through CRDs.
Part 5: Multi-Tenant Setup (Platform Team)¶
This section is for platform teams setting up multi-tenant Keycloak access.
5.1 Understanding Multi-Tenant Authorization¶
The operator uses namespace grant lists for multi-tenant authorization:
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#00b8d9','primaryTextColor':'#fff','primaryBorderColor':'#0097a7','lineColor':'#00acc1','secondaryColor':'#006064','tertiaryColor':'#fff'}}}%%
graph LR
subgraph platform["👥 Platform Team"]
realm["Create Realm (central)<br/>with clientAuthorizationGrants"]
end
subgraph app["👥 Application Team"]
create["Create Client Resources<br/><small>(authorized via grant list)</small>"]
manage["Manage Own Clients<br/><small>(full self-service)</small>"]
end
realm -.->|authorize| create
create --> manage
style platform fill:#263238,stroke:#00acc1,stroke-width:2px,color:#fff
style app fill:#263238,stroke:#00acc1,stroke-width:2px,color:#fff
style realm fill:#00838f,stroke:#006064,color:#fff
style create fill:#00838f,stroke:#006064,color:#fff
style manage fill:#00838f,stroke:#006064,color:#fff
Key Concepts:
- Realm Creation: Controlled by Kubernetes RBAC
- Client Creation: Controlled by realm's clientAuthorizationGrants
- No Tokens: Authorization is declarative (namespace names in manifest)
- GitOps-Friendly: All authorization changes via PR workflow
5.2 Create Namespace for Application Team¶
# Create namespace for team-alpha
kubectl create namespace team-alpha
kubectl label namespace team-alpha team=alpha
5.3 Grant Team Permissions via RBAC¶
# Create Role for realm management
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: keycloak-realm-manager
namespace: team-alpha
rules:
- apiGroups: ["vriesdemichael.github.io"]
resources: ["keycloakrealms", "keycloakclients"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
EOF
# Bind to team's ServiceAccount (for GitOps)
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-keycloak-manager
namespace: team-alpha
subjects:
- kind: ServiceAccount
name: argocd-application-controller
namespace: argocd
- kind: Group
name: team-alpha
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: keycloak-realm-manager
apiGroup: rbac.authorization.k8s.io
EOF
5.4 Create Realm with Client Authorization Grants¶
Platform team creates realm and grants team-alpha access:
kubectl apply -f - <<EOF
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakRealm
metadata:
name: team-alpha-realm
namespace: team-alpha
spec:
realmName: team-alpha
displayName: "Team Alpha Identity"
instanceRef:
name: keycloak
namespace: keycloak-system
enabled: true
# Grant team-alpha namespace permission to create clients
clientAuthorizationGrants:
- team-alpha
security:
registrationAllowed: false
resetPasswordAllowed: true
rememberMe: true
loginConfig:
rememberMe: true
themes:
loginTheme: keycloak
accountTheme: keycloak
EOF
Wait for realm to be ready:
5.5 Share Realm Information with Team¶
Option A: GitOps (Recommended)
# Export realm manifest to team's GitOps repository
kubectl get keycloakrealm team-alpha-realm -n team-alpha -o yaml > team-alpha-realm.yaml
# Commit to team's repository
git add team-alpha-realm.yaml
git commit -m "feat(team-alpha): add Keycloak realm"
git push
# Team can now create clients via PR
Option B: Direct Communication
Share realm details with team:
- Realm name: team-alpha
- Namespace: team-alpha
- Authorization: team-alpha namespace can create clients
5.6 Team Creates First Client (Self-Service)¶
Team can now create clients without platform team intervention:
kubectl apply -f - <<EOF
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: team-alpha-app
namespace: team-alpha
spec:
clientId: team-alpha-app
realmRef:
name: team-alpha-realm
namespace: team-alpha
name: "Team Alpha Application"
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: true
redirectUris:
- "https://team-alpha-app.example.com/callback"
webOrigins:
- "https://team-alpha-app.example.com"
EOF
Key Points:
- ✅ Team manages own clients (self-service)
- ✅ Platform team controls authorization via clientAuthorizationGrants
- ✅ All changes via GitOps/PR workflow
- ✅ No secrets to distribute or rotate
5.7 Add Cross-Namespace Authorization (Optional)¶
If team-alpha needs to create clients in another realm:
# Platform team updates realm to grant access
kubectl patch keycloakrealm shared-api-realm -n platform --type=merge -p '
spec:
clientAuthorizationGrants:
- platform
- team-alpha # ← Add this via PR
'
Workflow: 1. Team-alpha creates PR requesting access 2. Platform team reviews and approves PR 3. Realm manifest updated with new namespace 4. Team-alpha can create clients in that realm
Part 6: Application Team Self-Service¶
6.1 Create Additional Clients¶
Application teams can create unlimited clients (within resource quotas):
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: keycloak-realm-manager
namespace: team-alpha
rules:
# Manage Keycloak resources
- apiGroups: ["vriesdemichael.github.io"]
resources: ["keycloakrealms", "keycloakclients"]
verbs: ["create", "update", "patch", "delete", "get", "list", "watch"]
# Read authorization tokens
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["*"] # All secrets
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-keycloak-access
namespace: team-alpha
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: keycloak-realm-manager
subjects:
- kind: ServiceAccount
name: team-alpha-deployer
namespace: team-alpha
EOF
Part 6: Realm Creation (Application Team)¶
This section is for application teams creating their realms.
6.1 Create a Realm¶
kubectl apply -f - <<EOF
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakRealm
metadata:
name: team-alpha-prod
namespace: team-alpha
spec:
realmName: team-alpha-prod
operatorRef:
namespace: keycloak-system
clientAuthorizationGrants:
- team-alpha
security:
registrationAllowed: false
resetPasswordAllowed: true
rememberMe: true
verifyEmail: true
loginWithEmailAllowed: true
sessions:
ssoSessionIdleTimeout: 1800
ssoSessionMaxLifespan: 36000
tokens:
accessTokenLifespan: 300
refreshTokenMaxReuse: 0
themes:
loginTheme: keycloak
accountTheme: keycloak
smtp:
host: smtp.sendgrid.net
port: 587
from: noreply@example.com
fromDisplayName: Team Alpha
auth: true
starttls: true
credentialsSecret: team-alpha-smtp-credentials
EOF
6.2 Wait for Realm¶
# Wait for realm to become ready (10-30 seconds)
kubectl wait --for=condition=Ready keycloakrealm/team-alpha-prod \
-n team-alpha \
--timeout=2m
What happened: 1. ✅ Operator validated RBAC permissions 2. ✅ Realm created in Keycloak 3. ✅ OIDC endpoints populated in status 4. ✅ Realm ready for client creation
6.3 Create Additional Realms¶
Create more realms as needed:
kubectl apply -f - <<EOF
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakRealm
metadata:
name: team-alpha-staging
namespace: team-alpha
spec:
realmName: team-alpha-staging
operatorRef:
namespace: keycloak-system
clientAuthorizationGrants:
- team-alpha
security:
registrationAllowed: false
resetPasswordAllowed: true
EOF
Part 7: Client Configuration¶
7.1 Create OAuth2 Client (Web Application)¶
kubectl apply -f - <<EOF
apiVersion: vriesdemichael.github.io/v1
kind: KeycloakClient
metadata:
name: team-alpha-webapp
namespace: team-alpha
spec:
clientId: team-alpha-webapp
realmRef:
name: team-alpha-prod
namespace: team-alpha
key: token
settings:
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
serviceAccountsEnabled: false
redirectUris:
- "https://app.example.com/callback"
- "https://app.example.com/silent-check-sso.html"
webOrigins:
- "https://app.example.com"
attributes:
pkce.code.challenge.method: "S256"
post.logout.redirect.uris: "https://app.example.com/"
protocolMappers:
- name: audience-mapper
protocol: openid-connect
protocolMapper: oidc-audience-mapper
config:
included.client.audience: "team-alpha-api"
access.token.claim: "true"
EOF
7.2 Wait for Client Creation¶
kubectl wait --for=condition=Ready keycloakclient/team-alpha-webapp \
-n team-alpha \
--timeout=2m
# Check status
kubectl get keycloakclient -n team-alpha
7.3 Retrieve Client Credentials¶
# Get credentials secret
kubectl get secret team-alpha-webapp-credentials -n team-alpha -o yaml
# Extract for application use
kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.client_id}' | base64 -d && echo
kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.client_secret}' | base64 -d && echo
kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.issuer_url}' | base64 -d && echo
7.4 Create Environment File for Application¶
# Generate .env file for application
kubectl get secret team-alpha-webapp-credentials -n team-alpha -o json | \
jq -r '.data | to_entries[] | "\(.key | ascii_upcase)=\(.value | @base64d)"' > team-alpha-webapp.env
cat team-alpha-webapp.env
Part 8: Verification & Testing¶
8.1 Verify All Resources¶
# Check operator
kubectl get pods -n keycloak-operator-system
kubectl logs -n keycloak-operator-system -l app=keycloak-operator --tail=50
# Check Keycloak
kubectl get keycloak -n keycloak-system
kubectl get pods -n keycloak-system
# Check database
kubectl get cluster -n keycloak-db
kubectl get pods -n keycloak-db
# Check realm
kubectl get keycloakrealm -n team-alpha
# Check client
kubectl get keycloakclient -n team-alpha
All resources should show PHASE=Ready or STATUS=Cluster in healthy state.
8.2 Test OIDC Discovery¶
# Get discovery URL from secret
ISSUER_URL=$(kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.issuer_url}' | base64 -d)
# Test OIDC discovery endpoint
curl -s "$ISSUER_URL/.well-known/openid-configuration" | jq .
Expected output: JSON with authorization_endpoint, token_endpoint, etc.
8.4 Test OAuth2 Authorization Code Flow¶
# Get OAuth2 endpoints
CLIENT_ID=$(kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.client_id}' | base64 -d)
AUTH_ENDPOINT="https://keycloak.example.com/realms/team-alpha-prod/protocol/openid-connect/auth"
REDIRECT_URI="https://app.example.com/callback"
# Generate authorization URL
AUTH_URL="${AUTH_ENDPOINT}?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=openid%20profile%20email"
echo "Open in browser:"
echo "$AUTH_URL"
Expected behavior:
1. Redirects to Keycloak login page
2. After login, redirects to https://app.example.com/callback?code=...
3. Application exchanges code for tokens
8.5 Test Token Endpoint¶
# Get client credentials
CLIENT_ID=$(kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.client_id}' | base64 -d)
CLIENT_SECRET=$(kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.client_secret}' | base64 -d)
TOKEN_URL=$(kubectl get secret team-alpha-webapp-credentials -n team-alpha \
-o jsonpath='{.data.token_url}' | base64 -d)
# Test client credentials grant (if serviceAccounts enabled)
curl -X POST "$TOKEN_URL" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" | jq .
Part 9: Production Checklist¶
9.1 Security Checklist¶
- TLS enabled - All traffic encrypted (ingress handles TLS termination)
- Admin credentials rotated - Changed from initial auto-generated password
- Secret references used - All sensitive values use Kubernetes secrets with proper labels
- RBAC configured - Application teams have least privilege access
- Network policies - Limit pod-to-pod communication (optional)
- Audit logging enabled - Kubernetes audit logs capture secret access
9.2 High Availability Checklist¶
- Operator replicas - 2+ operator pods running
- Keycloak replicas - 3+ Keycloak pods running
- Database replicas - 3+ PostgreSQL pods (1 primary, 2+ replicas)
- Pod anti-affinity - Pods spread across nodes
- Load balancer - Ingress controller distributes traffic
- Health checks - Liveness and readiness probes configured
9.3 Backup & Disaster Recovery Checklist¶
- Database backups enabled - CloudNativePG barman backups to S3
- Backup retention policy - 30 days configured
- Backup tested - Restore tested at least once
- Realm exports - Regular exports of realm configurations
- Documentation - Disaster recovery procedures documented
9.4 Monitoring Checklist¶
- Metrics enabled - Operator, Keycloak, database expose Prometheus metrics
- ServiceMonitors created - Prometheus scrapes all components
- Dashboards created - Grafana dashboards for operator, Keycloak, database
- Alerts configured - Pod failures, database issues, drift detection
- Log aggregation - Logs forwarded to centralized system (ELK, Loki)
9.5 Authorization Management Checklist¶
- Namespace grants documented - Each realm's clientAuthorizationGrants tracked in Git
- Access reviews scheduled - Quarterly review of namespace authorization grants
- Approval workflow - PRs required for adding new namespace grants
- Drift detection enabled - Monitors for unauthorized manual changes
- Audit trail maintained - Git history tracks all authorization changes
9.6 Testing Checklist¶
- OAuth2 flow tested - Authorization code flow works end-to-end
- Token refresh tested - Refresh tokens work correctly
- User login tested - Users can authenticate to realms
- Client registration tested - New clients can be created
- Realm isolation tested - Realms are properly isolated
- Multi-namespace tested - Multiple teams can create realms
Troubleshooting¶
Database Connection Issues¶
# Check database cluster status
kubectl get cluster keycloak-db -n keycloak-db
# Check database pods
kubectl get pods -n keycloak-db
# Test connection from Keycloak pod
kubectl exec -it -n keycloak-system <keycloak-pod> -- \
psql -h keycloak-db-rw.keycloak-db.svc -U keycloak -d keycloak -c "SELECT 1;"
TLS Certificate Issues¶
# Check certificate status
kubectl get certificate -n keycloak-system
# Check cert-manager logs
kubectl logs -n cert-manager -l app=cert-manager
# Describe certificate for details
kubectl describe certificate keycloak-tls -n keycloak-system
Token Rotation Issues¶
# Check token metadata
kubectl get configmap keycloak-operator-token-metadata \
-n keycloak-operator-system -o yaml | grep team-alpha
# Check operator logs for rotation
kubectl logs -n keycloak-operator-system -l app=keycloak-operator | grep -i rotation
Ingress Not Working¶
# Check ingress
kubectl get ingress -n keycloak-system
# Check ingress controller logs
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller
# Verify service endpoints
kubectl get endpoints keycloak-keycloak -n keycloak-system
Next Steps¶
After completing this guide, you have:
- ✅ Production-ready Keycloak operator installation
- ✅ High-availability Keycloak instance with TLS
- ✅ PostgreSQL database with backups
- ✅ Multi-tenant token system configured
- ✅ Working OAuth2/OIDC setup
- ✅ Monitoring and observability
Recommended next actions:
- Configure Additional Teams - Repeat Part 5-7 for other teams
- Set Up Monitoring - Create Grafana dashboards (see Observability)
- Configure Identity Providers - Add SAML/OIDC providers (see Identity Providers)
- Implement Backup Testing - Schedule regular backup restore tests
- Document Runbooks - Create team-specific runbooks for common operations
- Set Up Alerts - Configure PagerDuty/OpsGenie for critical alerts
Further Reading: