About
The Keycloak Operator is a Kubernetes operator developed by Hostzero that manages Keycloak instances through the Keycloak Admin API. The overall goal is to provide a cloud-native management interface for Keycloak instances.
Features
- Declarative Configuration: Manage Keycloak resources as Kubernetes Custom Resources
- Automatic Synchronization: Changes to CRs are automatically applied to Keycloak
- Secret Management: Client secrets are automatically synced to Kubernetes Secrets
- Status Tracking: Resource status reflects the current state in Keycloak
- Finalizers: Proper cleanup when resources are deleted
Goals
- Manage Keycloak instances solely through Kubernetes resources
- Provide a GitOps-friendly way to manage Keycloak configuration
- Enable infrastructure-as-code for identity management
- Support multiple Keycloak instances from a single operator
Non-Goals
- Manage the deployment of Keycloak instances (use Keycloak Operator or Helm for that)
- Support other IdM solutions than Keycloak
Supported Resources
| Resource | Description |
|---|---|
KeycloakInstance | Connection to a Keycloak server (namespaced) |
ClusterKeycloakInstance | Connection to a Keycloak server (cluster-scoped) |
KeycloakRealm | Realm configuration (namespaced) |
ClusterKeycloakRealm | Realm configuration (cluster-scoped) |
KeycloakClient | OAuth2/OIDC client configuration |
KeycloakClientScope | Client scope configuration |
KeycloakProtocolMapper | Token claim mappers for clients/scopes |
KeycloakUser | User management |
KeycloakUserCredential | User password management |
KeycloakGroup | Group management |
KeycloakRole | Realm and client role definitions |
KeycloakRoleMapping | Role-to-user/group assignments |
KeycloakIdentityProvider | External identity provider configuration |
KeycloakComponent | LDAP federation, key providers, etc. |
KeycloakOrganization | Organization management (Keycloak 26+) |
Enterprise Support
This operator is developed and maintained by Hostzero GmbH, a provider of sovereign IT infrastructure and security solutions based in Germany.
For organizations with critical infrastructure needs (KRITIS), we offer:
| Service | Description |
|---|---|
| Enterprise Support | SLA-backed support with guaranteed response times |
| Security Consulting | Hardening, compliance audits, and KRITIS certification support |
| On-Premises Deployment | Air-gapped and sovereign cloud deployments |
| Incident Response | 24/7 emergency support for production environments |
| Training | Workshops and certification programs |
→ Contact Hostzero for enterprise solutions
License
This project is licensed under the MIT License. See the LICENSE file for details.
Installation
There are several ways to install the Keycloak Operator:
Helm Chart (Recommended)
The preferred way to install the Keycloak Operator is using the provided Helm chart from our OCI registry.
helm install keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
--namespace keycloak-operator \
--create-namespace
To install a specific version:
helm install keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
--version 0.1.0 \
--namespace keycloak-operator \
--create-namespace
For detailed Helm configuration options, see the Helm Chart documentation.
Kustomize
You can also deploy using kustomize:
# Install CRDs
kubectl apply -k config/crd
# Deploy the operator
kubectl apply -k config/default
From Source
For development or customization:
# Clone the repository
git clone https://github.com/Hostzero-GmbH/keycloak-operator.git
cd keycloak-operator
# Install CRDs
make install
# Run the operator locally
make run
Next Steps
After installation, proceed to the Quick Start guide to create your first Keycloak resources.
Quick Start
This guide will walk you through setting up the Keycloak Operator and creating your first managed resources.
Prerequisites
- A running Kubernetes cluster
kubectlinstalled and configuredhelminstalled (optional, for Helm installation)- A Keycloak instance (or use the provided Kind setup)
Step 1: Install the Operator
Option A: Using Helm (Recommended)
helm install keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
--namespace keycloak-operator \
--create-namespace
Option B: Using Helm from Source
helm install keycloak-operator ./charts/keycloak-operator \
--namespace keycloak-operator \
--create-namespace
Option C: Using Kind (for development)
# This creates a Kind cluster with Keycloak and deploys the operator
make kind-all
Step 2: Create Admin Credentials Secret
Create a secret containing your Keycloak admin credentials:
kubectl create secret generic keycloak-admin-credentials \
--namespace keycloak-operator \
--from-literal=username=admin \
--from-literal=password=your-admin-password
Step 3: Create a KeycloakInstance
Create a KeycloakInstance resource to connect the operator to your Keycloak server:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakInstance
metadata:
name: my-keycloak
namespace: keycloak-operator
spec:
baseUrl: https://keycloak.example.com
credentials:
secretRef:
name: keycloak-admin-credentials
Apply it:
kubectl apply -f keycloak-instance.yaml
Verify the connection:
kubectl get keycloakinstances -n keycloak-operator
You should see:
NAME READY URL VERSION AGE
my-keycloak true https://keycloak.example.com 26.0.0 30s
Step 4: Create a Realm
Create a realm in your Keycloak instance:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
name: my-realm
namespace: keycloak-operator
spec:
instanceRef:
name: my-keycloak
definition:
realm: my-realm
displayName: My Application Realm
enabled: true
registrationAllowed: false
loginWithEmailAllowed: true
Apply it:
kubectl apply -f keycloak-realm.yaml
Step 5: Create a Client
Create an OAuth2/OIDC client:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClient
metadata:
name: my-app
namespace: keycloak-operator
spec:
realmRef:
name: my-realm
definition:
clientId: my-app
name: My Application
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
redirectUris:
- "https://my-app.example.com/callback"
webOrigins:
- "https://my-app.example.com"
clientSecret:
secretName: my-app-credentials
key: clientSecret
Apply it:
kubectl apply -f keycloak-client.yaml
The operator will create a Kubernetes secret with the client credentials:
kubectl get secret my-app-credentials -n keycloak-operator -o yaml
Step 6: Verify Resources
Check the status of all your Keycloak resources:
kubectl get keycloakinstances,keycloakrealms,keycloakclients -n keycloak-operator
Next Steps
- Learn about Helm Chart configuration
- Explore all Custom Resource Definitions
- Set up a local development environment
Helm Chart Installation
The Keycloak Operator Helm chart provides a flexible way to deploy the operator with customizable settings.
Installation
From OCI Registry (Recommended)
helm install keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
--namespace keycloak-operator \
--create-namespace
To install a specific version:
helm install keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
--version 0.1.0 \
--namespace keycloak-operator \
--create-namespace
From Local Chart
helm install keycloak-operator ./charts/keycloak-operator \
--namespace keycloak-operator \
--create-namespace
With Custom Values
helm install keycloak-operator ./charts/keycloak-operator \
--namespace keycloak-operator \
--create-namespace \
--values my-values.yaml
Configuration
Common Parameters
| Parameter | Description | Default |
|---|---|---|
replicaCount | Number of operator replicas | 1 |
image.repository | Container image repository | ghcr.io/hostzero-gmbh/keycloak-operator |
image.tag | Container image tag | Chart appVersion |
image.pullPolicy | Image pull policy | IfNotPresent |
Resources
| Parameter | Description | Default |
|---|---|---|
resources.limits.cpu | CPU limit | 500m |
resources.limits.memory | Memory limit | 256Mi |
resources.requests.cpu | CPU request | 100m |
resources.requests.memory | Memory request | 128Mi |
Features
| Parameter | Description | Default |
|---|---|---|
leaderElection.enabled | Enable leader election | true |
metrics.enabled | Enable metrics endpoint | true |
metrics.serviceMonitor.enabled | Create Prometheus ServiceMonitor | false |
CRDs
| Parameter | Description | Default |
|---|---|---|
crds.install | Install CRDs with Helm | true |
crds.keep | Keep CRDs on uninstall | true |
Example Values Files
Development
# values-dev.yaml
replicaCount: 1
image:
pullPolicy: Never
tag: "dev"
resources:
limits:
cpu: 200m
memory: 128Mi
leaderElection:
enabled: false
logging:
level: debug
crds:
keep: false
Production
# values-prod.yaml
replicaCount: 2
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
metrics:
serviceMonitor:
enabled: true
podDisruptionBudget:
enabled: true
minAvailable: 1
networkPolicy:
enabled: true
Upgrading
helm upgrade keycloak-operator ./charts/keycloak-operator \
--namespace keycloak-operator \
--values my-values.yaml
Uninstalling
helm uninstall keycloak-operator --namespace keycloak-operator
Note: CRDs are kept by default. To remove them:
kubectl delete crd keycloakinstances.keycloak.hostzero.com
kubectl delete crd keycloakrealms.keycloak.hostzero.com
kubectl delete crd keycloakclients.keycloak.hostzero.com
kubectl delete crd keycloakusers.keycloak.hostzero.com
kubectl delete crd keycloakclientscopes.keycloak.hostzero.com
kubectl delete crd keycloakgroups.keycloak.hostzero.com
kubectl delete crd keycloakidentityproviders.keycloak.hostzero.com
Kind Cluster Setup
This guide explains how to set up a local development environment using Kind (Kubernetes in Docker).
Prerequisites
- Docker
- Kind (
brew install kindorgo install sigs.k8s.io/kind@latest) - kubectl
- Helm
Quick Setup
The easiest way to get started is using the all-in-one command:
make kind-all
This will:
- Create a Kind cluster
- Build the operator image
- Load the image into Kind
- Install CRDs
- Deploy the operator via Helm
- Deploy Keycloak for testing
- Create a test KeycloakInstance
Step-by-Step Setup
Create the Cluster
make kind-create
This creates a Kind cluster with the following features:
- Multi-node setup (1 control plane + 2 workers)
- Port mappings for Keycloak access (8080, 8443)
- Ingress-ready configuration
Deploy Keycloak
make kind-deploy-keycloak
Keycloak will be available at:
- In-cluster:
http://keycloak.keycloak.svc.cluster.local - External:
http://localhost:8080(via NodePort 30080) - Credentials: admin / admin
Note: The NodePort service maps port 30080 to the host’s port 8080. If port 8080 is already in use, you can use
make kind-port-forwardas an alternative.
Deploy the Operator
make kind-deploy
This builds the operator image, loads it into Kind, and deploys via Helm.
Useful Commands
| Command | Description |
|---|---|
make kind-create | Create the Kind cluster |
make kind-delete | Delete the Kind cluster |
make kind-reset | Delete and recreate the cluster |
make kind-status | Show cluster status |
make kind-deploy | Build and deploy operator |
make kind-deploy-keycloak | Deploy Keycloak |
make kind-logs | Tail operator logs |
make kind-port-forward | Port-forward Keycloak to localhost:8080 |
Running Tests
Run the full E2E test suite against the Kind cluster:
make kind-test
This sets up port-forwarding automatically and runs all E2E tests.
Cluster Configuration
The Kind cluster is configured in hack/kind-config.yaml:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: keycloak-operator-dev
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
# Keycloak HTTP (NodePort 30080 -> localhost:8080)
- containerPort: 30080
hostPort: 8080
protocol: TCP
# Keycloak HTTPS
- containerPort: 30443
hostPort: 8443
protocol: TCP
# Ingress HTTP
- containerPort: 80
hostPort: 80
protocol: TCP
# Ingress HTTPS
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
- role: worker
Troubleshooting
Check Operator Logs
kubectl logs -n keycloak-operator -l app.kubernetes.io/name=keycloak-operator -f
Check Keycloak Logs
kubectl logs -n keycloak -l app=keycloak -f
Verify CRDs
kubectl get crds | grep keycloak
Check Resource Status
kubectl get keycloakinstances,keycloakrealms,keycloakclients -A
Configuration
The Keycloak Operator can be configured through various mechanisms:
- Helm Values: For deployment-time configuration
- Environment Variables: For runtime configuration
- Command-Line Flags: For operator behavior
Operator Configuration
The operator accepts the following configuration options:
| Option | Description | Default |
|---|---|---|
--metrics-bind-address | Address for metrics endpoint | :8080 |
--health-probe-bind-address | Address for health probes | :8081 |
--leader-elect | Enable leader election | false |
Keycloak Connection
Each KeycloakInstance resource defines how to connect to a Keycloak server:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakInstance
metadata:
name: my-keycloak
spec:
# Base URL of the Keycloak server
baseUrl: https://keycloak.example.com
# Realm to authenticate against (default: master)
realm: master
# Credentials for admin access
credentials:
secretRef:
name: keycloak-credentials
namespace: keycloak-operator # Optional, defaults to resource namespace
usernameKey: username # Optional, defaults to "username"
passwordKey: password # Optional, defaults to "password"
Resource References
Resources reference their parent using *Ref fields:
# Realm references an Instance
spec:
instanceRef:
name: my-keycloak
namespace: default # Optional
# Client references a Realm
spec:
realmRef:
name: my-realm
namespace: default # Optional
See Also
Environment Variables
The operator can be configured using environment variables, which are automatically set when deploying via Helm.
Operator Settings
| Variable | Description | Default |
|---|---|---|
POD_NAMESPACE | Namespace where the operator is running | Injected by Kubernetes |
POD_NAME | Name of the operator pod | Injected by Kubernetes |
Logging
| Variable | Description | Default |
|---|---|---|
LOG_LEVEL | Log level (debug, info, error) | info |
LOG_FORMAT | Log format (json, console) | json |
Metrics
| Variable | Description | Default |
|---|---|---|
METRICS_BIND_ADDRESS | Address for metrics endpoint | :8080 |
Health Probes
| Variable | Description | Default |
|---|---|---|
HEALTH_PROBE_BIND_ADDRESS | Address for health probes | :8081 |
Development
For local development, you can set these in your shell:
export LOG_LEVEL=debug
export LOG_FORMAT=console
make run
Or use a .env file with your IDE.
Helm Values Reference
Complete reference for all Helm chart values.
Global
# Number of replicas
replicaCount: 1
# Image configuration
image:
repository: ghcr.io/hostzero/keycloak-operator
pullPolicy: IfNotPresent
tag: "" # Defaults to Chart.appVersion
# Image pull secrets
imagePullSecrets: []
# Name overrides
nameOverride: ""
fullnameOverride: ""
Service Account
serviceAccount:
create: true
annotations: {}
name: ""
Pod Configuration
# Pod annotations
podAnnotations: {}
# Pod labels
podLabels: {}
# Pod security context
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# Container security context
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
Resources
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
Scheduling
nodeSelector: {}
tolerations: []
affinity: {}
priorityClassName: ""
Leader Election
leaderElection:
enabled: true
Metrics
metrics:
enabled: true
port: 8080
serviceMonitor:
enabled: false
additionalLabels: {}
interval: 30s
scrapeTimeout: 10s
Health Probes
health:
port: 8081
Logging
logging:
level: info # debug, info, error
format: json # json, console
development: false
Performance Tuning
performance:
# Sync period for re-checking successfully reconciled resources
# Higher values reduce Keycloak API load but increase drift detection time
syncPeriod: "5m" # e.g., "5m", "30m", "1h"
# Maximum concurrent requests to Keycloak (0 = no limit)
# Lower values reduce Keycloak load but slow reconciliation
maxConcurrentRequests: 10
For large deployments (100+ resources), consider:
performance:
syncPeriod: "30m"
maxConcurrentRequests: 5
RBAC
rbac:
create: true
CRDs
crds:
install: true
keep: true # Keep CRDs on uninstall
Extra Configuration
# Additional environment variables
extraEnv: []
# - name: MY_VAR
# value: my-value
# Additional volumes
extraVolumes: []
# Additional volume mounts
extraVolumeMounts: []
High Availability
# Termination grace period
terminationGracePeriodSeconds: 10
# Network policy
networkPolicy:
enabled: false
ingress: []
egress: []
# Pod disruption budget
podDisruptionBudget:
enabled: false
minAvailable: 1
maxUnavailable: ""
Custom Resource Definitions
The Keycloak Operator provides several Custom Resource Definitions (CRDs) to manage Keycloak resources declaratively.
Resource Hierarchy
KeycloakInstance / ClusterKeycloakInstance
└── KeycloakRealm / ClusterKeycloakRealm
├── KeycloakClient
│ ├── KeycloakUser (service account, via clientRef)
│ ├── KeycloakRole (client role)
│ └── KeycloakProtocolMapper
├── KeycloakUser (regular users, via realmRef)
│ └── KeycloakUserCredential
├── KeycloakGroup
├── KeycloakClientScope
│ └── KeycloakProtocolMapper
├── KeycloakRole (realm role)
├── KeycloakRoleMapping (maps roles to Users/Groups)
├── KeycloakComponent (LDAP, key providers, etc.)
├── KeycloakIdentityProvider
└── KeycloakOrganization (requires Keycloak 26+)
Overview
Instance Resources
| CRD | Description | Scope |
|---|---|---|
| KeycloakInstance | Connection to a Keycloak server | Namespaced |
| ClusterKeycloakInstance | Cluster-scoped Keycloak connection | Cluster |
Realm Resources
| CRD | Description | Parent |
|---|---|---|
| KeycloakRealm | Realm configuration | KeycloakInstance |
| ClusterKeycloakRealm | Cluster-scoped realm | ClusterKeycloakInstance |
OAuth & Client Resources
| CRD | Description | Parent |
|---|---|---|
| KeycloakClient | OAuth2/OIDC client | KeycloakRealm |
| KeycloakClientScope | Client scope configuration | KeycloakRealm |
| KeycloakProtocolMapper | Token claim mappers | KeycloakClient or KeycloakClientScope |
Identity Resources
| CRD | Description | Parent |
|---|---|---|
| KeycloakUser | User management | KeycloakRealm or KeycloakClient¹ |
| KeycloakUserCredential | User password management | KeycloakUser |
| KeycloakGroup | Group management | KeycloakRealm |
Role & Access Control
| CRD | Description | Parent |
|---|---|---|
| KeycloakRole | Realm and client roles | KeycloakRealm or KeycloakClient |
| KeycloakRoleMapping | Role-to-subject mappings | KeycloakUser or KeycloakGroup |
Federation & Infrastructure
| CRD | Description | Parent |
|---|---|---|
| KeycloakComponent | LDAP federation, key providers | KeycloakRealm |
| KeycloakIdentityProvider | External identity providers | KeycloakRealm |
| KeycloakOrganization | Organization management² | KeycloakRealm |
¹ KeycloakUser supports clientRef for managing service account users associated with a client
² KeycloakOrganization requires Keycloak 26.0.0 or later
Common Patterns
Definition Field
Most resources include a definition field that accepts the full Keycloak API representation:
spec:
definition:
# Full Keycloak API object
realm: my-realm
enabled: true
displayName: My Realm
This provides flexibility to configure any Keycloak property, even those not explicitly modeled in the CRD.
Status Tracking
All resources expose status information:
status:
ready: true
message: "Resource synchronized successfully"
conditions:
- type: Ready
status: "True"
lastTransitionTime: "2024-01-01T00:00:00Z"
reason: Synchronized
message: "Resource is in sync with Keycloak"
Finalizers
Resources use finalizers to ensure proper cleanup when deleted:
metadata:
finalizers:
- keycloak.hostzero.com/finalizer
API Version
All CRDs use the keycloak.hostzero.com/v1beta1 API version:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
KeycloakInstance
A KeycloakInstance represents a connection to a Keycloak server. It serves as the root resource for managing Keycloak configuration.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakInstance
metadata:
name: my-keycloak
spec:
# Required: Base URL of the Keycloak server
baseUrl: https://keycloak.example.com
# Optional: Realm to authenticate against (default: master)
realm: master
# Required: Credentials for admin access
credentials:
secretRef:
# Required: Name of the secret containing credentials
name: keycloak-admin-credentials
# Optional: Namespace of the secret (defaults to resource namespace)
namespace: keycloak-operator
# Optional: Key for username (default: username)
usernameKey: username
# Optional: Key for password (default: password)
passwordKey: password
Status
status:
# Whether the connection is established
ready: true
# Keycloak server version
version: "26.0.0"
# Status message
message: "Connected successfully"
# Last successful connection time
lastConnected: "2024-01-01T12:00:00Z"
# Conditions
conditions:
- type: Ready
status: "True"
reason: Connected
message: "Successfully connected to Keycloak"
lastTransitionTime: "2024-01-01T12:00:00Z"
Example
Basic Instance
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakInstance
metadata:
name: production-keycloak
namespace: keycloak-operator
spec:
baseUrl: https://auth.example.com
credentials:
secretRef:
name: keycloak-admin
With Custom Realm
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakInstance
metadata:
name: dev-keycloak
spec:
baseUrl: http://keycloak.keycloak.svc.cluster.local:8080
realm: admin-realm
credentials:
secretRef:
name: keycloak-credentials
usernameKey: admin-user
passwordKey: admin-pass
Credentials Secret
The credentials secret must contain the admin username and password:
apiVersion: v1
kind: Secret
metadata:
name: keycloak-admin-credentials
type: Opaque
stringData:
username: admin
password: your-secure-password
Short Names
| Alias | Full Name |
|---|---|
kci | keycloakinstances |
kubectl get kci
Notes
- The operator validates the connection on creation and periodically thereafter
- Connection failures are reflected in the
status.readyfield - The Keycloak version is detected automatically and stored in
status.version
ClusterKeycloakInstance
The ClusterKeycloakInstance resource makes a Keycloak server known to the operator at the cluster level, allowing resources in any namespace to reference it.
Overview
This is the cluster-scoped equivalent of KeycloakInstance. Use it when:
- You have a central Keycloak server shared across multiple namespaces
- You want to avoid duplicating instance definitions in each namespace
- You need cross-namespace realm and client management
Example
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakInstance
metadata:
name: central-keycloak
spec:
baseUrl: https://keycloak.example.com
credentials:
secretRef:
name: keycloak-admin-credentials
namespace: keycloak-system
With Client Authentication
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakInstance
metadata:
name: central-keycloak
spec:
baseUrl: https://keycloak.example.com
realm: master
credentials:
secretRef:
name: keycloak-admin-credentials
namespace: keycloak-system
usernameKey: admin-user
passwordKey: admin-password
client:
id: admin-cli
Spec
| Field | Type | Description | Required |
|---|---|---|---|
baseUrl | string | URL of the Keycloak server | Yes |
credentials.secretRef.name | string | Name of the credentials secret | Yes |
credentials.secretRef.namespace | string | Namespace of the credentials secret | Yes |
credentials.secretRef.usernameKey | string | Key for username in secret | No (default: “username”) |
credentials.secretRef.passwordKey | string | Key for password in secret | No (default: “password”) |
realm | string | Admin realm name | No (default: “master”) |
client.id | string | Client ID for authentication | No |
client.secret | string | Client secret (if confidential) | No |
token.secretName | string | Secret to cache access tokens | No |
Status
| Field | Type | Description |
|---|---|---|
ready | boolean | Whether connection to Keycloak is established |
version | string | Detected Keycloak server version |
status | string | Current status (Ready, ConnectionFailed, Error) |
message | string | Additional status information |
conditions | []Condition | Kubernetes conditions |
Behavior
Connection Verification
The operator periodically verifies the connection to Keycloak by:
- Authenticating with the provided credentials
- Fetching server info to detect the version
- Updating the
readystatus and connection metrics
Secret Reference
Since ClusterKeycloakInstance is cluster-scoped, the namespace field in secretRef is required (unlike the namespaced KeycloakInstance where it defaults to the resource’s namespace).
Client Manager
The operator maintains a pool of authenticated Keycloak clients. When a ClusterKeycloakInstance is created, a client is registered in the pool with a special cluster-scoped key, making it available for all resources that reference it.
Use Cases
Central Keycloak for Multi-Tenant Platform
# Define the central instance once
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakInstance
metadata:
name: platform-keycloak
spec:
baseUrl: https://auth.platform.example.com
credentials:
secretRef:
name: keycloak-admin
namespace: auth-system
---
# Create cluster-scoped realms for each tenant
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: tenant-a-realm
spec:
clusterInstanceRef:
name: platform-keycloak
definition:
realm: tenant-a
enabled: true
Shared Instance Across Environments
# Credentials in a secure namespace
apiVersion: v1
kind: Secret
metadata:
name: keycloak-credentials
namespace: keycloak-secrets
type: Opaque
stringData:
username: admin
password: ${KEYCLOAK_ADMIN_PASSWORD}
---
# Cluster instance referencing the secret
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakInstance
metadata:
name: shared-keycloak
spec:
baseUrl: https://keycloak.internal.example.com
credentials:
secretRef:
name: keycloak-credentials
namespace: keycloak-secrets
Comparison with KeycloakInstance
| Aspect | KeycloakInstance | ClusterKeycloakInstance |
|---|---|---|
| Scope | Namespaced | Cluster |
| Secret namespace | Optional (defaults to same) | Required |
| Accessible from | Same namespace only | Any namespace |
| Short name | kci | ckci |
| Use case | Single namespace | Multi-namespace/platform |
Notes
- Only one
ClusterKeycloakInstancewith a given name can exist - Deleting the instance will invalidate all resources that reference it
- The credentials secret must exist before creating the instance
- The operator requires RBAC permissions to read secrets from the specified namespace
KeycloakRealm
A KeycloakRealm represents a realm within a Keycloak instance.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
name: my-realm
spec:
# Required: Reference to the KeycloakInstance
instanceRef:
name: my-keycloak
namespace: default # Optional
# Optional: Realm name in Keycloak (defaults to metadata.name)
realmName: my-realm
# Required: Realm definition (Keycloak RealmRepresentation)
definition:
realm: my-realm
displayName: My Realm
enabled: true
# ... any other Keycloak realm properties
Status
status:
ready: true
realmId: "my-realm"
message: "Realm synchronized successfully"
conditions:
- type: Ready
status: "True"
reason: Synchronized
Example
Basic Realm
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
name: my-app-realm
spec:
instanceRef:
name: production-keycloak
definition:
realm: my-app
displayName: My Application
enabled: true
Full Configuration
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRealm
metadata:
name: production-realm
spec:
instanceRef:
name: production-keycloak
definition:
realm: production
displayName: Production Realm
enabled: true
# Login settings
registrationAllowed: false
registrationEmailAsUsername: true
loginWithEmailAllowed: true
duplicateEmailsAllowed: false
resetPasswordAllowed: true
rememberMe: true
# Session settings
ssoSessionIdleTimeout: 1800
ssoSessionMaxLifespan: 36000
accessTokenLifespan: 300
# Security settings
bruteForceProtected: true
permanentLockout: false
maxFailureWaitSeconds: 900
minimumQuickLoginWaitSeconds: 60
waitIncrementSeconds: 60
quickLoginCheckMilliSeconds: 1000
maxDeltaTimeSeconds: 43200
failureFactor: 5
# Themes
loginTheme: keycloak
accountTheme: keycloak
adminTheme: keycloak
emailTheme: keycloak
# SMTP settings
smtpServer:
host: smtp.example.com
port: "587"
fromDisplayName: My App
from: noreply@example.com
starttls: "true"
auth: "true"
user: smtp-user
password: smtp-password
Definition Properties
The definition field accepts any property from the Keycloak RealmRepresentation.
Common properties:
| Property | Type | Description |
|---|---|---|
realm | string | Realm name (required) |
displayName | string | Display name for the realm |
enabled | boolean | Whether the realm is enabled |
registrationAllowed | boolean | Allow user registration |
loginWithEmailAllowed | boolean | Allow login with email |
ssoSessionIdleTimeout | integer | SSO session idle timeout (seconds) |
accessTokenLifespan | integer | Access token lifespan (seconds) |
Short Names
| Alias | Full Name |
|---|---|
kcr | keycloakrealms |
kubectl get kcr
ClusterKeycloakRealm
The ClusterKeycloakRealm resource defines a Keycloak realm at the cluster level, making it accessible to resources in any namespace.
Overview
This is the cluster-scoped equivalent of KeycloakRealm. Use it when:
- You need a realm that can be referenced from multiple namespaces
- You’re using
ClusterKeycloakInstancefor your Keycloak server - You want centralized realm management with distributed client/user definitions
Examples
Basic Cluster Realm
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: shared-realm
spec:
clusterInstanceRef:
name: central-keycloak
definition:
realm: shared
enabled: true
displayName: Shared Platform Realm
With Namespaced Instance
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: company-realm
spec:
instanceRef:
name: keycloak-instance
namespace: keycloak-system
definition:
realm: company
enabled: true
loginWithEmailAllowed: true
registrationAllowed: false
Full Configuration
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: production-realm
spec:
clusterInstanceRef:
name: production-keycloak
realmName: prod # Override the Keycloak realm name
definition:
realm: prod
enabled: true
displayName: Production
sslRequired: external
registrationAllowed: false
loginWithEmailAllowed: true
duplicateEmailsAllowed: false
resetPasswordAllowed: true
verifyEmail: true
bruteForceProtected: true
accessTokenLifespan: 300
ssoSessionIdleTimeout: 1800
ssoSessionMaxLifespan: 36000
loginTheme: keycloak
accountTheme: keycloak
adminTheme: keycloak
emailTheme: keycloak
Spec
| Field | Type | Description | Required |
|---|---|---|---|
clusterInstanceRef.name | string | Reference to ClusterKeycloakInstance | One of these |
instanceRef.name | string | Reference to namespaced KeycloakInstance | One of these |
instanceRef.namespace | string | Namespace of the KeycloakInstance | Required if instanceRef |
realmName | string | Override realm name in Keycloak | No (defaults to metadata.name) |
definition | object | Keycloak RealmRepresentation | Yes |
Definition Fields
The definition field accepts any valid Keycloak RealmRepresentation. Common fields include:
| Field | Type | Description |
|---|---|---|
realm | string | Realm identifier (required) |
enabled | boolean | Whether realm is enabled |
displayName | string | Display name |
sslRequired | string | SSL requirement: all, external, none |
registrationAllowed | boolean | Allow user self-registration |
loginWithEmailAllowed | boolean | Allow login with email |
verifyEmail | boolean | Require email verification |
resetPasswordAllowed | boolean | Enable password reset |
bruteForceProtected | boolean | Enable brute force protection |
accessTokenLifespan | integer | Access token lifetime (seconds) |
ssoSessionIdleTimeout | integer | SSO session idle timeout (seconds) |
loginTheme | string | Login page theme |
Status
| Field | Type | Description |
|---|---|---|
ready | boolean | Whether the realm is synced |
status | string | Current status (Ready, InstanceNotReady, CreateFailed, etc.) |
message | string | Additional status information |
resourcePath | string | Keycloak API path for this realm |
realmName | string | Actual realm name in Keycloak |
instance | object | Resolved instance reference |
conditions | []Condition | Kubernetes conditions |
Behavior
Instance Resolution
The controller supports two instance reference types:
- clusterInstanceRef: References a
ClusterKeycloakInstanceby name - instanceRef: References a namespaced
KeycloakInstanceby name and namespace
One of these must be specified.
Realm Synchronization
On each reconciliation:
- Connect to Keycloak using the referenced instance
- Check if the realm exists
- Create or update the realm with the specified definition
- Update status with the resource path
Cleanup
When a ClusterKeycloakRealm is deleted:
- The finalizer removes the realm from Keycloak
- All resources in Keycloak (clients, users, etc.) within that realm are deleted
- The Kubernetes resource is then removed
Use Cases
Multi-Tenant Platform
# Central Keycloak instance
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakInstance
metadata:
name: platform-keycloak
spec:
baseUrl: https://auth.example.com
credentials:
secretRef:
name: admin-creds
namespace: auth-system
---
# Realm for each tenant (cluster-scoped)
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: tenant-acme
spec:
clusterInstanceRef:
name: platform-keycloak
definition:
realm: acme
enabled: true
displayName: ACME Corporation
---
# Clients can be in any namespace
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakClient
metadata:
name: acme-web-app
namespace: acme-apps
spec:
clusterRealmRef:
name: tenant-acme
definition:
clientId: acme-web-app
protocol: openid-connect
publicClient: true
redirectUris:
- https://app.acme.example.com/*
Environment-Specific Realms
# Development realm
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: app-dev
spec:
clusterInstanceRef:
name: keycloak-dev
definition:
realm: app-dev
enabled: true
registrationAllowed: true # Allow in dev
sslRequired: none # Relaxed for dev
---
# Production realm
apiVersion: keycloak.hostzero.io/v1beta1
kind: ClusterKeycloakRealm
metadata:
name: app-prod
spec:
clusterInstanceRef:
name: keycloak-prod
definition:
realm: app-prod
enabled: true
registrationAllowed: false
sslRequired: external
bruteForceProtected: true
verifyEmail: true
Referencing from Namespaced Resources
Resources in any namespace can reference a ClusterKeycloakRealm:
# KeycloakClient in namespace-a
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakClient
metadata:
name: my-client
namespace: namespace-a
spec:
clusterRealmRef:
name: shared-realm # References ClusterKeycloakRealm
definition:
clientId: my-client
# ...
---
# KeycloakUser in namespace-b
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakUser
metadata:
name: my-user
namespace: namespace-b
spec:
clusterRealmRef:
name: shared-realm # Same ClusterKeycloakRealm
definition:
username: myuser
# ...
Comparison with KeycloakRealm
| Aspect | KeycloakRealm | ClusterKeycloakRealm |
|---|---|---|
| Scope | Namespaced | Cluster |
| Instance ref | Same namespace or cross-namespace | Cluster or any namespaced |
| Accessible from | Same namespace | Any namespace |
| Short name | kcrm | ckcrm |
| Use case | Single namespace | Multi-namespace/platform |
Notes
- Only one
ClusterKeycloakRealmwith a given name can exist - Deleting the realm will delete all Keycloak resources within it
- The referenced instance must be ready before the realm can be created
- Changes to the
definitionare applied on each reconciliation
KeycloakClient
A KeycloakClient represents an OAuth2/OIDC client within a Keycloak realm.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClient
metadata:
name: my-app
spec:
# Required: Reference to the KeycloakRealm
realmRef:
name: my-realm
namespace: default # Optional
# Optional: Client ID in Keycloak (defaults to metadata.name)
clientId: my-app
# Required: Client definition (Keycloak ClientRepresentation)
definition:
clientId: my-app
name: My Application
enabled: true
publicClient: false
# ... any other Keycloak client properties
# Optional: Sync client secret to a Kubernetes Secret
clientSecret:
secretName: my-app-credentials
key: clientSecret # Default: clientSecret
Status
status:
ready: true
clientId: "my-app"
clientUUID: "12345678-1234-1234-1234-123456789abc"
message: "Client synchronized successfully"
Example
Public Client (SPA)
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClient
metadata:
name: my-spa
spec:
realmRef:
name: my-realm
definition:
clientId: my-spa
name: My Single Page Application
enabled: true
publicClient: true
standardFlowEnabled: true
directAccessGrantsEnabled: false
rootUrl: https://my-app.example.com
redirectUris:
- https://my-app.example.com/*
webOrigins:
- https://my-app.example.com
Confidential Client (Backend)
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClient
metadata:
name: my-api
spec:
realmRef:
name: my-realm
definition:
clientId: my-api
name: My Backend API
enabled: true
publicClient: false
serviceAccountsEnabled: true
standardFlowEnabled: false
directAccessGrantsEnabled: false
clientSecret:
secretName: my-api-credentials
Service Account with Roles
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClient
metadata:
name: my-service
spec:
realmRef:
name: my-realm
definition:
clientId: my-service
name: My Service Account
enabled: true
publicClient: false
serviceAccountsEnabled: true
standardFlowEnabled: false
directAccessGrantsEnabled: false
authorizationServicesEnabled: true
clientSecret:
secretName: my-service-credentials
Client Secret Synchronization
When clientSecret is specified, the operator creates a Kubernetes Secret with the client credentials:
apiVersion: v1
kind: Secret
metadata:
name: my-app-credentials
type: Opaque
data:
client-id: bXktYXBw # base64 encoded
client-secret: c2VjcmV0... # base64 encoded
Definition Properties
Common properties from Keycloak ClientRepresentation:
| Property | Type | Description |
|---|---|---|
clientId | string | Client identifier (required) |
name | string | Display name |
enabled | boolean | Whether client is enabled |
publicClient | boolean | Public or confidential client |
standardFlowEnabled | boolean | Enable Authorization Code flow |
directAccessGrantsEnabled | boolean | Enable Resource Owner Password flow |
serviceAccountsEnabled | boolean | Enable service account |
redirectUris | string[] | Valid redirect URIs |
webOrigins | string[] | Allowed CORS origins |
rootUrl | string | Root URL for relative URIs |
Short Names
| Alias | Full Name |
|---|---|
kcc | keycloakclients |
kubectl get kcc
KeycloakClientScope
A KeycloakClientScope represents a client scope within a Keycloak realm.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClientScope
metadata:
name: my-scope
spec:
# One of realmRef or clusterRealmRef must be specified
# Option 1: Reference to a namespaced KeycloakRealm
realmRef:
name: my-realm
namespace: default # Optional, defaults to same namespace
# Option 2: Reference to a ClusterKeycloakRealm
clusterRealmRef:
name: my-cluster-realm
# Required: Client scope definition
definition:
name: my-scope
protocol: openid-connect
# ... any other properties
Status
status:
ready: true
scopeId: "12345678-1234-1234-1234-123456789abc"
message: "Client scope synchronized successfully"
Example
Basic Scope
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClientScope
metadata:
name: profile-extended
spec:
realmRef:
name: my-realm
definition:
name: profile-extended
description: Extended profile information
protocol: openid-connect
Scope with Protocol Mappers
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakClientScope
metadata:
name: department-scope
spec:
realmRef:
name: my-realm
definition:
name: department
description: Department information
protocol: openid-connect
protocolMappers:
- name: department
protocol: openid-connect
protocolMapper: oidc-usermodel-attribute-mapper
consentRequired: false
config:
claim.name: department
user.attribute: department
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
Definition Properties
| Property | Type | Description |
|---|---|---|
name | string | Scope name (required) |
description | string | Description |
protocol | string | Protocol (openid-connect, saml) |
protocolMappers | array | Protocol mapper configurations |
attributes | map | Additional attributes |
Short Names
| Alias | Full Name |
|---|---|
kccs | keycloakclientscopes |
kubectl get kccs
KeycloakProtocolMapper
A KeycloakProtocolMapper defines how user attributes, roles, and other data are mapped into tokens. Protocol mappers can be attached to either clients or client scopes.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakProtocolMapper
metadata:
name: my-mapper
spec:
# One of clientRef or clientScopeRef must be specified
clientRef:
name: my-client
# Or for client scopes:
# clientScopeRef:
# name: my-scope
# Required: Mapper definition
definition:
name: department
protocol: openid-connect
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: department
claim.name: department
Status
status:
ready: true
mapperID: "12345678-1234-1234-1234-123456789abc"
message: "Protocol mapper synchronized successfully"
Examples
Client Protocol Mapper
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakProtocolMapper
metadata:
name: department-mapper
namespace: keycloak
spec:
clientRef:
name: my-client
definition:
name: department
protocol: openid-connect
protocolMapper: oidc-usermodel-attribute-mapper
config:
user.attribute: department
claim.name: department
jsonType.label: String
id.token.claim: "true"
access.token.claim: "true"
userinfo.token.claim: "true"
Client Scope Protocol Mapper
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakProtocolMapper
metadata:
name: groups-mapper
namespace: keycloak
spec:
clientScopeRef:
name: my-scope
definition:
name: groups
protocol: openid-connect
protocolMapper: oidc-group-membership-mapper
config:
full.path: "false"
id.token.claim: "true"
access.token.claim: "true"
claim.name: groups
userinfo.token.claim: "true"
Parent Reference
A KeycloakProtocolMapper belongs to either a client or client scope:
| Reference | Use Case |
|---|---|
clientRef | Mapper applies to a specific client only |
clientScopeRef | Mapper applies to all clients using the scope |
Note: Exactly one of these must be specified.
Definition Properties
The definition field accepts any valid Keycloak ProtocolMapperRepresentation:
| Field | Type | Description |
|---|---|---|
name | string | Mapper name (required) |
protocol | string | Protocol (usually “openid-connect” or “saml”) |
protocolMapper | string | Mapper type (see common types below) |
config | object | Mapper-specific configuration |
Common Protocol Mapper Types
OpenID Connect
| Mapper Type | Description |
|---|---|
oidc-usermodel-attribute-mapper | Maps user attribute to token claim |
oidc-usermodel-property-mapper | Maps user property to token claim |
oidc-group-membership-mapper | Includes group membership in token |
oidc-role-name-mapper | Maps role names |
oidc-hardcoded-claim-mapper | Adds hardcoded claim |
oidc-audience-mapper | Adds audience to token |
oidc-full-name-mapper | Maps full name |
SAML
| Mapper Type | Description |
|---|---|
saml-user-attribute-mapper | Maps user attribute |
saml-group-membership-mapper | Maps group membership |
saml-role-list-mapper | Maps roles |
Short Names
| Alias | Full Name |
|---|---|
kcpm | keycloakprotocolmappers |
kubectl get kcpm
Notes
- Mapper names must be unique within the client or client scope
- The
configvalues are all strings (including boolean values like “true”/“false”) - Changes to mappers affect all tokens issued after the change
KeycloakUser
A KeycloakUser represents a user within a Keycloak realm, or a service account user associated with a client.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: john-doe
spec:
# One of realmRef, clusterRealmRef, or clientRef must be specified
# Option 1: Reference to a KeycloakRealm (for regular realm users)
realmRef:
name: my-realm
namespace: default # Optional
# Option 2: Reference to a ClusterKeycloakRealm (for cluster-scoped realms)
# clusterRealmRef:
# name: my-cluster-realm
# Option 3: Reference to a KeycloakClient (for service account users)
# clientRef:
# name: my-client
# namespace: default # Optional
# User definition (Keycloak UserRepresentation)
# Note: For service account users (clientRef), definition is optional
definition:
username: johndoe
email: john.doe@example.com
firstName: John
lastName: Doe
enabled: true
# ... any other Keycloak user properties
# Optional: Password configuration
userSecret:
secretName: john-doe-password
passwordKey: password # Default: password
Status
status:
ready: true
userId: "12345678-1234-1234-1234-123456789abc"
message: "User synchronized successfully"
Example
Basic User
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: admin-user
spec:
realmRef:
name: my-realm
definition:
username: admin
email: admin@example.com
firstName: Admin
lastName: User
enabled: true
emailVerified: true
User with Credentials
First, create a secret with the password:
apiVersion: v1
kind: Secret
metadata:
name: john-password
type: Opaque
stringData:
password: "secure-password-123"
Then create the user:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: john-doe
spec:
realmRef:
name: my-realm
definition:
username: johndoe
email: john.doe@example.com
firstName: John
lastName: Doe
enabled: true
emailVerified: true
userSecret:
secretName: john-password
User with Attributes
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: employee
spec:
realmRef:
name: my-realm
definition:
username: jsmith
email: jsmith@company.com
firstName: Jane
lastName: Smith
enabled: true
attributes:
department:
- Engineering
employee_id:
- "12345"
manager:
- "jdoe"
User with Groups
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: developer
spec:
realmRef:
name: my-realm
definition:
username: developer1
email: dev@example.com
enabled: true
groups:
- /developers
- /team-alpha
Service Account User
Manage the service account user associated with a client. This is useful for assigning roles or attributes to a client’s service account.
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: my-service-account
spec:
# Use clientRef instead of realmRef for service account users
clientRef:
name: my-service-client
# Definition is optional - the service account is automatically created by Keycloak
# when serviceAccountsEnabled: true on the client
definition:
# You can add/modify attributes on the service account
attributes:
department:
- Platform
Service Account with Role Mapping
Combine KeycloakUser (via clientRef) with KeycloakRoleMapping to assign roles to a service account:
# First, define the service account user
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakUser
metadata:
name: my-service-sa
spec:
clientRef:
name: my-service-client
---
# Then, map roles to the service account
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRoleMapping
metadata:
name: my-service-sa-roles
spec:
userRef:
name: my-service-sa
roles:
realm:
- admin
client:
realm-management:
- manage-users
- view-users
Definition Properties
Common properties from Keycloak UserRepresentation:
| Property | Type | Description |
|---|---|---|
username | string | Username (required) |
email | string | Email address |
firstName | string | First name |
lastName | string | Last name |
enabled | boolean | Whether user is enabled |
emailVerified | boolean | Email verified flag |
attributes | map | Custom user attributes |
groups | string[] | Group paths to join |
requiredActions | string[] | Required actions on login |
Short Names
| Alias | Full Name |
|---|---|
kcu | keycloakusers |
kubectl get kcu
Parent Reference
A KeycloakUser can belong to one of three parent types:
| Reference | Use Case | Parent Type |
|---|---|---|
realmRef | Regular realm users | KeycloakRealm |
clusterRealmRef | Users in cluster-scoped realms | ClusterKeycloakRealm |
clientRef | Service account users | KeycloakClient |
Note: Exactly one of realmRef, clusterRealmRef, or clientRef must be specified.
Service Account Users
When using clientRef, the operator manages the service account user that Keycloak automatically creates for clients with serviceAccountsEnabled: true. This allows you to:
- Add custom attributes to the service account
- Use
KeycloakRoleMappingto assign roles to the service account - Manage the service account declaratively alongside other resources
The definition field is optional for service account users since Keycloak creates the user automatically.
Notes
- Passwords are only set on user creation
- To update a password, delete and recreate the user, or use Keycloak’s admin console
- Group memberships specified in
groupsare resolved by path - For service account users, the username is automatically set by Keycloak (format:
service-account-<client-id>)
KeycloakUserCredential
The KeycloakUserCredential resource manages user credentials (passwords) in Keycloak via Kubernetes Secrets.
Overview
This CRD provides a way to:
- Store user passwords in Kubernetes Secrets
- Automatically create secrets with generated passwords
- Sync passwords to Keycloak users
- Manage password policies
Example
Using an existing Secret
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakUserCredential
metadata:
name: user-credential
spec:
userRef:
name: my-user
userSecret:
secretName: my-user-credentials
usernameKey: username
passwordKey: password
Auto-creating a Secret
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakUserCredential
metadata:
name: user-credential
spec:
userRef:
name: my-user
userSecret:
secretName: my-user-credentials
create: true
usernameKey: username
passwordKey: password
passwordPolicy:
length: 24
symbols: true
Spec
| Field | Type | Description | Required |
|---|---|---|---|
userRef | ResourceRef | Reference to the KeycloakUser resource | Yes |
userSecret.secretName | string | Name of the Kubernetes Secret | Yes |
userSecret.create | boolean | Create secret if it doesn’t exist | No (default: false) |
userSecret.usernameKey | string | Key in secret for username | No (default: “username”) |
userSecret.passwordKey | string | Key in secret for password | No (default: “password”) |
userSecret.passwordPolicy.length | int | Length of generated password | No (default: 16) |
userSecret.passwordPolicy.symbols | boolean | Include symbols in password | No (default: true) |
Status
| Field | Type | Description |
|---|---|---|
ready | boolean | Whether the credential is synced |
status | string | Current status (Synced, Error, SecretError) |
secretCreated | boolean | Whether the secret was created by the operator |
message | string | Additional status information |
lastPasswordSync | string | Timestamp of last password sync |
Behavior
Secret Creation
When create: true is set:
- The operator creates a new Secret if it doesn’t exist
- A password is generated according to the password policy
- The username is set to match the Keycloak user’s username
Password Sync
When the Secret exists (created or pre-existing):
- The operator reads the password from the Secret
- The password is set in Keycloak for the referenced user
- The
lastPasswordSynctimestamp is updated
Cleanup
When the KeycloakUserCredential is deleted:
- If
secretCreated: truein status, the Secret is also deleted (via owner references) - Pre-existing secrets are not deleted
Use Cases
Initial User Setup
Create users with auto-generated passwords:
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakUser
metadata:
name: new-user
spec:
realmRef:
name: my-realm
definition:
username: new-user
email: user@example.com
enabled: true
---
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakUserCredential
metadata:
name: new-user-creds
spec:
userRef:
name: new-user
userSecret:
secretName: new-user-password
create: true
Service Account Passwords
Manage service account credentials that can be mounted into pods:
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakUserCredential
metadata:
name: service-account-creds
spec:
userRef:
name: service-account-user
userSecret:
secretName: app-keycloak-credentials
create: true
passwordPolicy:
length: 32
symbols: false
KeycloakGroup
A KeycloakGroup represents a group within a Keycloak realm.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakGroup
metadata:
name: my-group
spec:
# One of realmRef or clusterRealmRef must be specified
# Option 1: Reference to a namespaced KeycloakRealm
realmRef:
name: my-realm
namespace: default # Optional, defaults to same namespace
# Option 2: Reference to a ClusterKeycloakRealm
clusterRealmRef:
name: my-cluster-realm
# Optional: Reference to parent group (for nested groups)
parentGroupRef:
name: parent-group
namespace: default # Optional, defaults to same namespace
# Required: Group definition
definition:
name: my-group
# ... any other properties
Status
status:
ready: true
groupId: "12345678-1234-1234-1234-123456789abc"
message: "Group synchronized successfully"
Example
Basic Group
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakGroup
metadata:
name: developers
spec:
realmRef:
name: my-realm
definition:
name: developers
Group with Attributes
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakGroup
metadata:
name: engineering
spec:
realmRef:
name: my-realm
definition:
name: engineering
attributes:
department:
- Engineering
cost_center:
- "1234"
Nested Group
First, create the parent group:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakGroup
metadata:
name: organization
spec:
realmRef:
name: my-realm
definition:
name: organization
Then create child groups:
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakGroup
metadata:
name: team-alpha
spec:
realmRef:
name: my-realm
parentGroupRef:
name: organization
definition:
name: team-alpha
Definition Properties
| Property | Type | Description |
|---|---|---|
name | string | Group name (required) |
path | string | Full group path (auto-generated) |
attributes | map | Custom group attributes |
realmRoles | string[] | Realm roles assigned to group |
clientRoles | map | Client roles assigned to group |
Short Names
| Alias | Full Name |
|---|---|
kcg | keycloakgroups |
kubectl get kcg
KeycloakRole
A KeycloakRole manages Keycloak roles. Roles can be either realm-level (shared across all clients) or client-level (specific to a single client).
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRole
metadata:
name: my-role
spec:
# One of realmRef, clusterRealmRef, or clientRef must be specified
# For realm roles:
realmRef:
name: my-realm
# For client roles:
# clientRef:
# name: my-client
# Required: Role definition (Keycloak RoleRepresentation)
definition:
name: admin-role
description: Administrator role
Status
status:
ready: true
roleName: "admin-role"
message: "Role synchronized successfully"
Examples
Realm Role
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRole
metadata:
name: my-realm-role
namespace: keycloak
spec:
realmRef:
name: my-realm
definition:
name: admin-role
description: Administrator role with full access
composite: false
Client Role
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakRole
metadata:
name: my-client-role
namespace: keycloak
spec:
clientRef:
name: my-client
definition:
name: editor
description: Can edit resources
Parent Reference
A KeycloakRole can belong to one of three parent types:
| Reference | Scope | Use Case |
|---|---|---|
realmRef | Realm role | Shared across all clients in the realm |
clusterRealmRef | Realm role | For cluster-scoped realms |
clientRef | Client role | Specific to a single client |
Note: Exactly one of these must be specified.
Definition Properties
The definition field accepts any valid Keycloak RoleRepresentation:
| Field | Type | Description |
|---|---|---|
name | string | Role name (required) |
description | string | Role description |
composite | boolean | Whether this is a composite role |
composites | object | Composite role definitions (realm/client roles) |
attributes | object | Custom attributes |
Status Fields
| Field | Type | Description |
|---|---|---|
ready | boolean | Whether the role is synchronized |
status | string | Current status (e.g., “Ready”, “Error”) |
message | string | Human-readable status message |
roleName | string | The role name in Keycloak |
observedGeneration | integer | Last observed generation |
Short Names
| Alias | Full Name |
|---|---|
kcrl | keycloakroles |
kubectl get kcrl
Notes
- Role names must be unique within their scope (realm or client)
- When using
clientRef, the role becomes a client role - Composite roles can reference other realm or client roles
KeycloakRoleMapping
The KeycloakRoleMapping resource assigns Keycloak roles to users or groups.
Overview
This CRD provides a declarative way to:
- Assign realm roles to users
- Assign client roles to users
- Assign realm roles to groups
- Assign client roles to groups
Examples
Realm Role to User
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: admin-role-mapping
spec:
subject:
userRef:
name: admin-user
roleRef:
name: admin-role
Client Role to User
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: client-admin-mapping
spec:
subject:
userRef:
name: service-user
roleRef:
name: manage-clients
clientRef:
name: my-client
Inline Role Reference
Instead of referencing a KeycloakRole resource, you can specify the role name directly:
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: builtin-role-mapping
spec:
subject:
userRef:
name: my-user
role:
name: offline_access
Role to Group
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: group-role-mapping
spec:
subject:
groupRef:
name: developers
roleRef:
name: developer-role
Spec
| Field | Type | Description | Required |
|---|---|---|---|
subject.userRef | ResourceRef | Reference to KeycloakUser | Either userRef or groupRef |
subject.groupRef | ResourceRef | Reference to KeycloakGroup | Either userRef or groupRef |
roleRef | ResourceRef | Reference to KeycloakRole resource | Either roleRef or role |
role.name | string | Keycloak role name (inline) | Either roleRef or role |
clientRef | ResourceRef | Reference to KeycloakClient for client roles | No (realm role if omitted) |
Status
| Field | Type | Description |
|---|---|---|
ready | boolean | Whether the mapping is synced |
status | string | Current status (Synced, Error, SubjectError, RoleError) |
message | string | Additional status information |
subjectId | string | Keycloak ID of the user/group |
roleId | string | Keycloak ID of the role |
mappingType | string | Type: UserRealmRole, UserClientRole, GroupRealmRole, GroupClientRole |
Behavior
Role Resolution
Using roleRef:
- The operator looks up the referenced
KeycloakRoleresource - It retrieves the Keycloak role ID from the role’s status
- This is the recommended approach for roles managed by the operator
Using role.name:
- The operator queries Keycloak for a role with the given name
- This is useful for built-in roles like
offline_access
Mapping Types
| Subject | ClientRef | Result |
|---|---|---|
| userRef | - | User realm role mapping |
| userRef | set | User client role mapping |
| groupRef | - | Group realm role mapping |
| groupRef | set | Group client role mapping |
Cleanup
When the KeycloakRoleMapping is deleted:
- The finalizer removes the role mapping from Keycloak
- The user/group no longer has the role assigned
Use Cases
RBAC Setup
Set up role-based access control with groups:
# Create a group
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakGroup
metadata:
name: admins
spec:
realmRef: my-realm
definition:
name: admins
---
# Create a role
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRole
metadata:
name: admin-role
spec:
realmRef:
name: my-realm
definition:
name: admin
description: Full admin access
---
# Map role to group
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: admins-admin-role
spec:
subject:
groupRef:
name: admins
roleRef:
name: admin-role
Service Account Roles
Assign specific client roles to service accounts:
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: service-manage-users
spec:
subject:
userRef:
name: service-account
role:
name: manage-users
clientRef:
name: realm-management
Multiple Role Assignments
Assign multiple roles to the same user:
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: user-role-1
spec:
subject:
userRef:
name: my-user
roleRef:
name: role-1
---
apiVersion: keycloak.hostzero.io/v1beta1
kind: KeycloakRoleMapping
metadata:
name: user-role-2
spec:
subject:
userRef:
name: my-user
roleRef:
name: role-2
Notes
- Only one of
userReforgroupRefcan be specified - Only one of
roleReforrolecan be specified - When using
clientRef, the role must be a client role, not a realm role - Built-in Keycloak roles (like
offline_access,uma_authorization) should use inlinerole.name
KeycloakComponent
A KeycloakComponent manages Keycloak components such as LDAP user federation, custom storage providers, key providers, and other pluggable realm components.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakComponent
metadata:
name: my-component
spec:
# One of realmRef or clusterRealmRef must be specified
realmRef:
name: my-realm
# Required: Component definition
definition:
name: corporate-ldap
providerId: ldap
providerType: org.keycloak.storage.UserStorageProvider
config:
enabled:
- "true"
connectionUrl:
- "ldap://ldap.example.com:389"
Status
status:
ready: true
componentID: "12345678-1234-1234-1234-123456789abc"
message: "Component synchronized successfully"
Examples
LDAP User Federation
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakComponent
metadata:
name: ldap-federation
namespace: keycloak
spec:
realmRef:
name: my-realm
definition:
name: corporate-ldap
providerId: ldap
providerType: org.keycloak.storage.UserStorageProvider
config:
enabled:
- "true"
vendor:
- "ad"
connectionUrl:
- "ldap://ldap.example.com:389"
bindDn:
- "cn=admin,dc=example,dc=com"
bindCredential:
- "secret"
usersDn:
- "ou=users,dc=example,dc=com"
userObjectClasses:
- "person, organizationalPerson, user"
editMode:
- "READ_ONLY"
RSA Key Provider
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakComponent
metadata:
name: rsa-key
namespace: keycloak
spec:
realmRef:
name: my-realm
definition:
name: rsa-generated
providerId: rsa-generated
providerType: org.keycloak.keys.KeyProvider
config:
priority:
- "100"
algorithm:
- "RS256"
Definition Properties
The definition field accepts any valid Keycloak ComponentRepresentation:
| Field | Type | Description |
|---|---|---|
name | string | Component name (required) |
providerId | string | Provider ID (e.g., “ldap”, “rsa-generated”) |
providerType | string | Provider type (e.g., “org.keycloak.storage.UserStorageProvider”) |
parentId | string | Parent component ID (defaults to realm ID) |
config | object | Provider-specific configuration (array of strings per key) |
Common Provider Types
| Provider Type | Use Case |
|---|---|
org.keycloak.storage.UserStorageProvider | LDAP, custom user storage |
org.keycloak.keys.KeyProvider | Cryptographic keys (RSA, AES, etc.) |
org.keycloak.storage.ldap.mappers.LDAPStorageMapper | LDAP attribute mappers |
Short Names
| Alias | Full Name |
|---|---|
kcco | keycloakcomponents |
kubectl get kcco
Notes
- Component configuration uses arrays of strings for all values
- LDAP credentials should be managed via Kubernetes Secrets (not directly in the CR)
- Some components may require specific ordering via
priorityconfig
KeycloakIdentityProvider
A KeycloakIdentityProvider represents an external identity provider configuration within a Keycloak realm.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakIdentityProvider
metadata:
name: my-idp
spec:
# One of realmRef or clusterRealmRef must be specified
# Option 1: Reference to a namespaced KeycloakRealm
realmRef:
name: my-realm
namespace: default # Optional, defaults to same namespace
# Option 2: Reference to a ClusterKeycloakRealm
clusterRealmRef:
name: my-cluster-realm
# Required: Identity provider definition
definition:
alias: my-idp
providerId: oidc
enabled: true
# ... any other properties
Status
status:
ready: true
alias: "my-idp"
message: "Identity provider synchronized successfully"
Example
OIDC Provider
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakIdentityProvider
metadata:
name: corporate-sso
spec:
realmRef:
name: my-realm
definition:
alias: corporate-sso
displayName: Corporate SSO
providerId: oidc
enabled: true
trustEmail: true
firstBrokerLoginFlowAlias: first broker login
config:
authorizationUrl: https://sso.corp.example.com/auth
tokenUrl: https://sso.corp.example.com/token
userInfoUrl: https://sso.corp.example.com/userinfo
clientId: keycloak-client
clientSecret: client-secret-here
defaultScope: openid profile email
syncMode: IMPORT
Google Provider
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakIdentityProvider
metadata:
name: google
spec:
realmRef:
name: my-realm
definition:
alias: google
displayName: Sign in with Google
providerId: google
enabled: true
trustEmail: true
config:
clientId: your-google-client-id
clientSecret: your-google-client-secret
defaultScope: openid profile email
GitHub Provider
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakIdentityProvider
metadata:
name: github
spec:
realmRef:
name: my-realm
definition:
alias: github
displayName: Sign in with GitHub
providerId: github
enabled: true
config:
clientId: your-github-client-id
clientSecret: your-github-client-secret
SAML Provider
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakIdentityProvider
metadata:
name: saml-idp
spec:
realmRef:
name: my-realm
definition:
alias: saml-idp
displayName: Corporate SAML
providerId: saml
enabled: true
config:
entityId: https://idp.example.com
singleSignOnServiceUrl: https://idp.example.com/sso
nameIDPolicyFormat: urn:oasis:names:tc:SAML:2.0:nameid-format:transient
signatureAlgorithm: RSA_SHA256
wantAssertionsSigned: "true"
wantAuthnRequestsSigned: "true"
Definition Properties
Common properties from Keycloak IdentityProviderRepresentation:
| Property | Type | Description |
|---|---|---|
alias | string | Unique alias (required) |
displayName | string | Display name |
providerId | string | Provider type (oidc, saml, google, etc.) |
enabled | boolean | Whether provider is enabled |
trustEmail | boolean | Trust email from provider |
storeToken | boolean | Store provider tokens |
config | map | Provider-specific configuration |
Short Names
| Alias | Full Name |
|---|---|
kcidp | keycloakidentityproviders |
kubectl get kcidp
Notes
- Store sensitive values like client secrets in Kubernetes Secrets and reference them
- Consider using
syncMode: IMPORTto import users on first login - Configure mappers to transform claims from the external provider
KeycloakOrganization
A KeycloakOrganization represents an organization within a Keycloak realm.
Note: Organizations require Keycloak 26.0.0 or later. Attempting to use this resource with earlier Keycloak versions will result in an error.
Specification
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakOrganization
metadata:
name: acme-corp
spec:
# One of realmRef or clusterRealmRef must be specified
realmRef:
name: my-realm
namespace: default # Optional
# Required: Organization definition (Keycloak OrganizationRepresentation)
definition:
name: ACME Corporation
alias: acme
description: ACME Corp organization
enabled: true
domains:
- name: acme.com
verified: true
attributes:
industry:
- Technology
Status
status:
ready: true
organizationID: "12345678-1234-1234-1234-123456789abc"
message: "Organization synchronized successfully"
Examples
Basic Organization
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakOrganization
metadata:
name: my-org
spec:
realmRef:
name: my-realm
definition:
name: My Organization
enabled: true
Organization with Domains
Organizations can be associated with email domains. Users with matching email domains can be automatically associated with the organization.
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakOrganization
metadata:
name: example-org
spec:
realmRef:
name: my-realm
definition:
name: Example Organization
alias: example
description: An example organization with verified domains
enabled: true
domains:
- name: example.com
verified: true
- name: example.org
verified: false
Organization with Custom Attributes
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakOrganization
metadata:
name: enterprise-org
spec:
realmRef:
name: my-realm
definition:
name: Enterprise Organization
alias: enterprise
enabled: true
attributes:
tier:
- enterprise
maxUsers:
- "1000"
supportLevel:
- premium
Organization with Cluster-Scoped Realm
apiVersion: keycloak.hostzero.com/v1beta1
kind: KeycloakOrganization
metadata:
name: global-org
spec:
clusterRealmRef:
name: shared-realm
definition:
name: Global Organization
enabled: true
Definition Properties
Common properties from Keycloak OrganizationRepresentation:
| Property | Type | Description |
|---|---|---|
name | string | Organization name (required) |
alias | string | URL-friendly identifier |
description | string | Description of the organization |
enabled | boolean | Whether organization is enabled |
domains | array | Associated email domains |
attributes | map | Custom organization attributes |
Domain Properties
| Property | Type | Description |
|---|---|---|
name | string | Domain name (e.g., “example.com”) |
verified | boolean | Whether the domain is verified |
Short Names
| Alias | Full Name |
|---|---|
kcorg | keycloakorganizations |
kubectl get kcorg
Requirements
- Keycloak 26.0.0+: Organizations are a feature introduced in Keycloak 26. The operator will report an error if you try to create an organization on an older Keycloak version.
- Organizations must be enabled: The organization feature must be enabled in the realm settings.
Notes
- Organizations are immutable by ID - once created, the
idfield cannot be changed - The
aliasis used in URLs and should be URL-safe - Verified domains can be used for automatic user association based on email
- Use attributes for custom metadata and configuration
Monitoring
The Keycloak Operator exposes Prometheus metrics to enable comprehensive monitoring and alerting for your Keycloak resources.
Metrics Endpoint
Metrics are exposed at :8080/metrics by default (configurable via --metrics-bind-address).
Available Metrics
Reconciliation Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
keycloak_operator_reconcile_total | Counter | controller, result | Total number of reconciliations per controller |
keycloak_operator_reconcile_duration_seconds | Histogram | controller | Time spent in reconciliation |
keycloak_operator_reconcile_errors_total | Counter | controller, error_type | Total errors by controller and type |
keycloak_operator_last_reconcile_timestamp_seconds | Gauge | controller | Timestamp of last successful reconciliation |
Resource Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
keycloak_operator_resources_managed | Gauge | resource_type, namespace | Number of managed resources |
keycloak_operator_resources_ready | Gauge | resource_type, namespace | Number of resources in ready state |
Keycloak Connection Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
keycloak_operator_keycloak_connection_status | Gauge | instance, namespace | Connection status (1=connected, 0=disconnected) |
keycloak_operator_keycloak_api_requests_total | Counter | instance, method, endpoint, status | Total Keycloak API requests |
keycloak_operator_keycloak_api_latency_seconds | Histogram | instance, method, endpoint | Keycloak API latency |
Controller Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
keycloak_operator_workqueue_depth | Gauge | controller | Work queue depth per controller |
Error Types
The error_type label can have the following values:
fetch_error- Failed to fetch the Kubernetes resourceconnection_error- Failed to connect to Keycloakinstance_not_ready- Referenced KeycloakInstance is not readyrealm_not_ready- Referenced KeycloakRealm is not readyinvalid_definition- Invalid resource definition (JSON parsing failed)keycloak_api_error- Keycloak API call failedsecret_sync_error- Failed to synchronize client secret
Monitoring Recommendations
Critical Alerts
Set up alerts for these critical conditions:
1. Keycloak Connection Failures
alert: KeycloakConnectionDown
expr: keycloak_operator_keycloak_connection_status == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Keycloak connection lost"
description: "Instance {{ $labels.instance }} in {{ $labels.namespace }} has been disconnected for 5 minutes"
2. High Reconciliation Error Rate
alert: KeycloakOperatorHighErrorRate
expr: |
rate(keycloak_operator_reconcile_errors_total[5m])
/ rate(keycloak_operator_reconcile_total[5m]) > 0.1
for: 10m
labels:
severity: warning
annotations:
summary: "High reconciliation error rate"
description: "Controller {{ $labels.controller }} has >10% error rate"
3. Resources Not Ready
alert: KeycloakResourcesNotReady
expr: |
keycloak_operator_resources_managed - keycloak_operator_resources_ready > 0
for: 15m
labels:
severity: warning
annotations:
summary: "Keycloak resources not ready"
description: "{{ $value }} {{ $labels.resource_type }} resources are not ready in {{ $labels.namespace }}"
4. Slow Reconciliation
alert: KeycloakSlowReconciliation
expr: |
histogram_quantile(0.99,
rate(keycloak_operator_reconcile_duration_seconds_bucket[5m])
) > 30
for: 10m
labels:
severity: warning
annotations:
summary: "Slow reconciliation detected"
description: "Controller {{ $labels.controller }} p99 reconciliation time exceeds 30s"
5. Controller Stale
alert: KeycloakControllerStale
expr: |
time() - keycloak_operator_last_reconcile_timestamp_seconds > 600
for: 5m
labels:
severity: critical
annotations:
summary: "Controller not reconciling"
description: "Controller {{ $labels.controller }} has not reconciled for 10+ minutes"
Dashboard Recommendations
Create a Grafana dashboard with these panels:
-
Overview
- Total managed resources by type
- Ready vs non-ready resources
- Keycloak instance connection status
-
Reconciliation Performance
- Reconciliation rate per controller
- Reconciliation duration (p50, p95, p99)
- Error rate over time
-
Errors & Issues
- Error breakdown by type
- Recent error spikes
- Connection failures over time
-
Keycloak API
- API request rate by endpoint
- API latency distribution
- Error responses by status code
Key Metrics to Watch
| Metric | Normal Range | Action if Abnormal |
|---|---|---|
| Connection status | 1 | Check Keycloak availability, credentials |
| Error rate | < 5% | Review logs, check Keycloak health |
| Reconcile duration p99 | < 10s | Check Keycloak performance |
| Queue depth | < 50 | Scale operator or reduce resources |
| Resources not ready | 0 | Check individual resource status |
Prometheus ServiceMonitor
If using Prometheus Operator, create a ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: keycloak-operator
labels:
app: keycloak-operator
spec:
selector:
matchLabels:
control-plane: controller-manager
endpoints:
- port: metrics
interval: 30s
path: /metrics
Helm Chart Configuration
Enable metrics in the Helm chart:
metrics:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
labels: {}
Architecture
The Keycloak Operator follows the Kubernetes operator pattern to manage Keycloak resources declaratively.
Overview
┌─────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ Custom Resources ││
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ││
│ │ │ Keycloak │ │ Keycloak │ │ Keycloak │ ││
│ │ │ Instance │ │ Realm │ │ Client │ ... ││
│ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ ││
│ └────────┼──────────────┼──────────────┼───────────────────────┘│
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐│
│ │ Keycloak Operator ││
│ │ ┌────────────────────────────────────────────────────────┐ ││
│ │ │ Controller Manager │ ││
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ ││
│ │ │ │ Instance │ │ Realm │ │ Client │ │ ││
│ │ │ │ Controller │ │ Controller │ │ Controller │ │ ││
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ ││
│ │ └────────────────────────────────────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌────────────────────────────────────────────────────────┐ ││
│ │ │ Keycloak Client │ ││
│ │ │ (custom resty-based HTTP client) │ ││
│ │ └────────────────────────────────────────────────────────┘ ││
│ └──────────────────────────────────────────────────────────────┘│
│ │ │
└───────────────────────────────┼──────────────────────────────────┘
│
▼
┌───────────────────────┐
│ Keycloak Server │
│ (Admin REST API) │
└───────────────────────┘
Components
Controller Manager
The controller manager is the main component that runs all controllers. It is built using the controller-runtime library.
Key features:
- Leader election for high availability
- Health and readiness probes
- Metrics endpoint for Prometheus
- Graceful shutdown handling
Controllers
Each CRD type has a dedicated controller that implements the reconciliation logic:
| Controller | CRD | Responsibilities |
|---|---|---|
| Instance Controller | KeycloakInstance, ClusterKeycloakInstance | Connection management, health checking |
| Realm Controller | KeycloakRealm, ClusterKeycloakRealm | Realm CRUD, configuration sync |
| Client Controller | KeycloakClient | Client CRUD, secret management |
| ClientScope Controller | KeycloakClientScope | Scope CRUD |
| ProtocolMapper Controller | KeycloakProtocolMapper | Token claim mapper configuration |
| User Controller | KeycloakUser | User CRUD |
| UserCredential Controller | KeycloakUserCredential | Password management |
| Group Controller | KeycloakGroup | Group CRUD, hierarchy management |
| Role Controller | KeycloakRole | Realm and client role management |
| RoleMapping Controller | KeycloakRoleMapping | Role-to-subject assignments |
| IdentityProvider Controller | KeycloakIdentityProvider | External IDP configuration |
| Component Controller | KeycloakComponent | LDAP, key providers, etc. |
| Organization Controller | KeycloakOrganization | Organization management (KC 26+) |
Keycloak Client
The operator uses a custom HTTP client built on resty. Key features:
- Version-agnostic: Works with raw JSON to support all Keycloak versions
- Connection pooling: Multiple KeycloakInstances share clients via
ClientManager - Token management: Automatic token acquisition and refresh
- Retry logic: Exponential backoff for transient errors (5xx, network issues)
- Pass-through definitions: CR definitions are sent directly to Keycloak without field stripping
Reconciliation Flow
┌─────────────────┐
│ CR Created/ │
│ Updated/Deleted│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Controller │
│ Triggered │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Get Current │
│ State from KC │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Compare Desired │
│ vs Actual State │
└────────┬────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Create │ │ Update │ │ Delete │
│ in KC │ │ in KC │ │ from KC │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└───────────────┴───────────────┘
│
▼
┌─────────────────┐
│ Update CR │
│ Status │
└─────────────────┘
Resource Dependencies
Resources form a hierarchy with parent-child relationships:
KeycloakInstance (connection to Keycloak)
│
└── KeycloakRealm (realm within instance)
│
├── KeycloakClient (client within realm)
│
├── KeycloakUser (user within realm)
│
├── KeycloakGroup (group within realm)
│ │
│ └── KeycloakGroup (nested child group)
│
├── KeycloakClientScope (scope within realm)
│
└── KeycloakIdentityProvider (IDP within realm)
Controllers resolve parent references and wait for parents to be ready before proceeding.
Finalizers
The operator uses finalizers to ensure proper cleanup:
- When a CR is created, a finalizer is added
- When a CR is deleted, the controller:
- Removes the resource from Keycloak
- Removes the finalizer
- Kubernetes then removes the CR
This ensures resources are properly cleaned up in Keycloak even if the cluster is disrupted.
High Availability
The operator supports running multiple replicas with leader election:
- Only the leader processes reconciliations
- Other replicas are hot standby
- Automatic failover on leader failure
- Configurable via
leaderElection.enabledHelm value
Performance Tuning
For large deployments with many resources, the operator provides tuning options:
Sync Period
The --sync-period flag controls how often successfully reconciled resources are re-checked for drift:
# Default: 5 minutes
--sync-period=5m
# For large deployments (100+ resources): 30 minutes
--sync-period=30m
# For very large deployments or slow networks: 1 hour
--sync-period=1h
Trade-offs:
- Shorter periods: Faster drift detection, higher Keycloak API load
- Longer periods: Lower API load, slower drift detection
In Helm:
performance:
syncPeriod: "30m"
Rate Limiting
The --max-concurrent-requests flag limits parallel requests to Keycloak:
# Default: 10 concurrent requests
--max-concurrent-requests=10
# For resource-constrained Keycloak instances
--max-concurrent-requests=5
# No limit (not recommended for large deployments)
--max-concurrent-requests=0
Trade-offs:
- Lower values: Less Keycloak load, slower reconciliation on startup
- Higher values: Faster reconciliation, more Keycloak load
In Helm:
performance:
maxConcurrentRequests: 5
Recommendations by Scale
| Resources | Sync Period | Max Concurrent Requests |
|---|---|---|
| < 50 | 5m (default) | 10 (default) |
| 50-200 | 15-30m | 10 |
| 200-500 | 30m | 5-10 |
| 500+ | 1h | 5 |
The exact values depend on your Keycloak instance capacity and acceptable drift detection latency.
Development
This section covers how to set up a development environment and contribute to the Keycloak Operator.
Prerequisites
- Go 1.22+
- Docker
- kubectl
- Kind or Minikube
- Make
Getting Started
-
Clone the repository:
git clone https://github.com/Hostzero-GmbH/keycloak-operator.git cd keycloak-operator -
Install dependencies:
go mod download -
Set up the development environment:
make kind-all
Project Structure
keycloak-operator/
├── api/v1beta1/ # CRD type definitions
├── cmd/main.go # Entry point
├── internal/
│ ├── controller/ # Reconciliation logic
│ └── keycloak/ # Keycloak client wrapper
├── config/
│ ├── crd/ # CRD manifests
│ ├── manager/ # Operator deployment
│ ├── rbac/ # RBAC configuration
│ └── samples/ # Example CRs
├── charts/ # Helm chart
├── hack/ # Development scripts
├── test/
│ └── e2e/ # End-to-end tests
└── docs/ # Documentation (mdBook)
Development Workflow
See the specific guides:
Local Setup
This guide explains how to set up a local development environment.
Prerequisites
- Go 1.22+
- Docker
- Kind (
brew install kindorgo install sigs.k8s.io/kind@latest) - kubectl
- Helm
Quick Start with Kind (Recommended)
The easiest way to develop is using Kind:
# Create cluster and deploy everything
make kind-all
This sets up:
- Kind cluster with 3 nodes
- Keycloak instance (admin/admin)
- Operator deployment
- Test resources
Iterating on Changes
# After code changes, rebuild and redeploy
make kind-deploy
# Check operator logs
make kind-logs
Accessing Keycloak
To access Keycloak from your local machine:
# Port-forward Keycloak to localhost:8080
make kind-port-forward
Then open http://localhost:8080 (admin/admin).
Run Against External Keycloak
You can run the operator against any Keycloak instance:
- Configure kubeconfig for your cluster
- Install CRDs:
make install - Create a KeycloakInstance pointing to your Keycloak
- Run locally:
make run
Development Commands
| Command | Description |
|---|---|
make build | Build the operator binary |
make run | Run the operator locally |
make install | Install CRDs to cluster |
make generate | Generate DeepCopy methods |
make manifests | Generate CRD manifests |
make fmt | Format code |
make vet | Run go vet |
make lint | Run golangci-lint |
IDE Setup
VS Code
Recommended extensions:
- Go
- YAML
- Kubernetes
Settings (.vscode/settings.json):
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.testFlags": ["-v"]
}
GoLand
- Enable Go modules integration
- Configure GOROOT to Go 1.22+
- Set up golangci-lint as external tool
Debugging
Local Debugging
- Set breakpoints in your IDE
- Run with debug configuration:
dlv debug ./cmd/main.go
Remote Debugging
For debugging in-cluster:
-
Build with debug symbols:
CGO_ENABLED=0 go build -gcflags="all=-N -l" -o manager ./cmd/main.go -
Use
kubectl port-forwardto access debugger port
Environment Variables
| Variable | Description | Default |
|---|---|---|
KUBECONFIG | Path to kubeconfig | ~/.kube/config |
KEYCLOAK_URL | Keycloak URL for tests | http://localhost:8080 |
LOG_LEVEL | Log level | info |
Testing
The Keycloak Operator has two levels of testing:
- Unit Tests: Fast, isolated tests using
envtest - End-to-End Tests: Full cluster tests against Kind with Keycloak
Unit Tests
Run unit tests with:
make test
Unit tests use the controller-runtime’s envtest package to provide a lightweight Kubernetes API server. These don’t require a real Keycloak instance.
Coverage
make test
go tool cover -html=cover.out
End-to-End Tests
E2E tests run against a full Kind cluster with the operator and Keycloak deployed.
Understanding E2E Test Network Topology
E2E tests involve two different network perspectives:
- Operator’s perspective (inside the cluster): The operator connects to Keycloak using the in-cluster service URL (e.g.,
http://keycloak.keycloak.svc.cluster.local) - Test’s perspective (your local machine): When running tests locally, you need port-forwarding to access Keycloak directly for certain tests (drift detection, cleanup verification)
┌─────────────────────────────────────────────────────────┐
│ Kind Cluster │
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Operator │──────│ Keycloak │ │
│ │ │ │ (port 80/8080) │ │
│ └─────────────┘ └────────┬─────────┘ │
│ │ │
└────────────────────────────────┼────────────────────────┘
│ port-forward
▼
┌────────────────────────┐
│ localhost:8080 │
│ (your machine) │
└────────────────────────┘
Running E2E Tests
Recommended approach (fully automated):
# Full setup: creates cluster, deploys operator and Keycloak, runs tests
make kind-all
make kind-test-e2e
The kind-test-e2e target runs ./hack/setup-kind.sh test-e2e, which:
- Sets up port-forwarding to Keycloak automatically
- Configures environment variables
- Runs the e2e test suite with a 30-minute timeout
Manual setup (for development):
# 1. Ensure cluster and operator are running
make kind-all
# 2. In a separate terminal, start port-forward
kubectl port-forward -n keycloak svc/keycloak 8080:80
# 3. Run tests with required environment variables
export USE_EXISTING_CLUSTER=true
export KEYCLOAK_URL="http://localhost:8080" # For test's direct Keycloak access
export KEYCLOAK_INTERNAL_URL="http://keycloak.keycloak.svc.cluster.local" # For operator (inside cluster)
go test -v -timeout 30m ./test/e2e/...
Note: Tests that require direct Keycloak access (drift detection, cleanup verification) will be automatically skipped if port-forward is not available. This allows running basic E2E tests without port-forwarding, while advanced tests require it.
E2E Test Configuration
| Variable | Description | Default |
|---|---|---|
USE_EXISTING_CLUSTER | Set to true to use current kubeconfig | false |
KEYCLOAK_INSTANCE_NAME | Name of existing KeycloakInstance to use | (creates new) |
KEYCLOAK_INSTANCE_NAMESPACE | Namespace of existing instance | keycloak-operator-e2e |
OPERATOR_NAMESPACE | Namespace where operator is deployed | keycloak-operator |
KEYCLOAK_URL | URL for test’s direct Keycloak access (via port-forward) | http://localhost:8080 |
KEYCLOAK_INTERNAL_URL | URL operator uses to connect (in-cluster) | http://keycloak.keycloak.svc.cluster.local |
TEST_NAMESPACE | Namespace for test resources | keycloak-operator-e2e |
KEEP_TEST_NAMESPACE | Don’t delete namespace after tests | false |
Test Categories
| Category | Requires Port-Forward | Description |
|---|---|---|
| Basic CRUD | No | Create, update, delete resources via Kubernetes API |
| Status verification | No | Verify .status.ready and conditions |
| Drift detection | Yes | Tests that modify Keycloak directly and verify reconciliation |
| Cleanup verification | Yes | Tests that verify resources are deleted from Keycloak |
| Edge cases | Mixed | Some require direct access, some don’t |
Writing Tests
Unit Test Example
func TestRealmController_Reconcile(t *testing.T) {
// Setup
scheme := runtime.NewScheme()
_ = keycloakv1beta1.AddToScheme(scheme)
realm := &keycloakv1beta1.KeycloakRealm{
ObjectMeta: metav1.ObjectMeta{
Name: "test-realm",
Namespace: "default",
},
Spec: keycloakv1beta1.KeycloakRealmSpec{
InstanceRef: "test-instance",
},
}
client := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(realm).
Build()
// Test reconciliation...
}
E2E Test Example
func TestKeycloakRealmE2E(t *testing.T) {
skipIfNoCluster(t)
realm := &keycloakv1beta1.KeycloakRealm{
ObjectMeta: metav1.ObjectMeta{
Name: "e2e-realm",
Namespace: testNamespace,
},
Spec: keycloakv1beta1.KeycloakRealmSpec{
InstanceRef: instanceName,
Definition: rawJSON(`{"realm": "e2e-realm", "enabled": true}`),
},
}
require.NoError(t, k8sClient.Create(ctx, realm))
t.Cleanup(func() {
k8sClient.Delete(ctx, realm)
})
// Wait for ready
err := wait.PollUntilContextTimeout(ctx, interval, timeout, true,
func(ctx context.Context) (bool, error) {
updated := &keycloakv1beta1.KeycloakRealm{}
if err := k8sClient.Get(ctx, client.ObjectKeyFromObject(realm), updated); err != nil {
return false, nil
}
return updated.Status.Ready, nil
})
require.NoError(t, err)
}
// Example: Test requiring direct Keycloak access (drift detection)
func TestDriftDetection(t *testing.T) {
skipIfNoCluster(t)
skipIfNoKeycloakAccess(t) // Skips if port-forward not available
// ... test that modifies Keycloak directly ...
}
CI/CD
Tests run automatically in GitHub Actions:
- Unit tests on every PR
- E2E tests on merge to main
Test Utilities
Common test utilities are in test/e2e/suite_test.go:
skipIfNoCluster(t): Skip test ifUSE_EXISTING_CLUSTERis not setskipIfNoKeycloakAccess(t): Skip test if direct Keycloak access (port-forward) is unavailablegetInternalKeycloakClient(t): Create authenticated Keycloak client for direct API accessrawJSON(s string): Createruntime.RawExtensionfrom JSON stringcanConnectToKeycloak(): Check if direct Keycloak connection is available
Contributing
Thank you for your interest in contributing to the Keycloak Operator!
Code of Conduct
Please be respectful and constructive in all interactions.
How to Contribute
Reporting Issues
- Search existing issues first
- Provide clear reproduction steps
- Include relevant logs and configuration
Submitting Changes
-
Fork the repository
-
Create a feature branch:
git checkout -b feature/my-feature -
Make your changes following the code style
-
Add tests for new functionality
-
Run checks:
make fmt make vet make lint make test -
Commit with a clear message:
git commit -m "feat: add support for X" -
Push and create a Pull Request
Commit Messages
Follow Conventional Commits:
feat:New featurefix:Bug fixdocs:Documentation onlyrefactor:Code change without feature/fixtest:Adding testschore:Maintenance
Code Style
- Follow standard Go conventions
- Use
gofmtandgolangci-lint - Add comments for exported types/functions
- Keep functions focused and small
Testing Requirements
- Unit tests for new logic
- E2E tests for new CRD features and Keycloak interactions
Development Setup
See Local Setup for environment setup.
Pull Request Process
- Ensure all tests pass
- Update documentation if needed
- Request review from maintainers
- Address feedback
- Squash commits if requested
Getting Help
- Open an issue for questions
- Check existing documentation
- Review similar PRs for patterns
License
By contributing, you agree that your contributions will be licensed under the MIT License.