Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

ResourceDescription
KeycloakInstanceConnection to a Keycloak server (namespaced)
ClusterKeycloakInstanceConnection to a Keycloak server (cluster-scoped)
KeycloakRealmRealm configuration (namespaced)
ClusterKeycloakRealmRealm configuration (cluster-scoped)
KeycloakClientOAuth2/OIDC client configuration
KeycloakClientScopeClient scope configuration
KeycloakProtocolMapperToken claim mappers for clients/scopes
KeycloakUserUser management
KeycloakUserCredentialUser password management
KeycloakGroupGroup management
KeycloakRoleRealm and client role definitions
KeycloakRoleMappingRole-to-user/group assignments
KeycloakIdentityProviderExternal identity provider configuration
KeycloakComponentLDAP federation, key providers, etc.
KeycloakOrganizationOrganization management (Keycloak 26+)

Enterprise Support

Hostzero

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:

ServiceDescription
Enterprise SupportSLA-backed support with guaranteed response times
Security ConsultingHardening, compliance audits, and KRITIS certification support
On-Premises DeploymentAir-gapped and sovereign cloud deployments
Incident Response24/7 emergency support for production environments
TrainingWorkshops 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:

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
  • kubectl installed and configured
  • helm installed (optional, for Helm installation)
  • A Keycloak instance (or use the provided Kind setup)

Step 1: Install the Operator

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

Helm Chart Installation

The Keycloak Operator Helm chart provides a flexible way to deploy the operator with customizable settings.

Installation

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

ParameterDescriptionDefault
replicaCountNumber of operator replicas1
image.repositoryContainer image repositoryghcr.io/hostzero-gmbh/keycloak-operator
image.tagContainer image tagChart appVersion
image.pullPolicyImage pull policyIfNotPresent

Resources

ParameterDescriptionDefault
resources.limits.cpuCPU limit500m
resources.limits.memoryMemory limit256Mi
resources.requests.cpuCPU request100m
resources.requests.memoryMemory request128Mi

Features

ParameterDescriptionDefault
leaderElection.enabledEnable leader electiontrue
metrics.enabledEnable metrics endpointtrue
metrics.serviceMonitor.enabledCreate Prometheus ServiceMonitorfalse

CRDs

ParameterDescriptionDefault
crds.installInstall CRDs with Helmtrue
crds.keepKeep CRDs on uninstalltrue

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 kind or go 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:

  1. Create a Kind cluster
  2. Build the operator image
  3. Load the image into Kind
  4. Install CRDs
  5. Deploy the operator via Helm
  6. Deploy Keycloak for testing
  7. 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-forward as an alternative.

Deploy the Operator

make kind-deploy

This builds the operator image, loads it into Kind, and deploys via Helm.

Useful Commands

CommandDescription
make kind-createCreate the Kind cluster
make kind-deleteDelete the Kind cluster
make kind-resetDelete and recreate the cluster
make kind-statusShow cluster status
make kind-deployBuild and deploy operator
make kind-deploy-keycloakDeploy Keycloak
make kind-logsTail operator logs
make kind-port-forwardPort-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:

OptionDescriptionDefault
--metrics-bind-addressAddress for metrics endpoint:8080
--health-probe-bind-addressAddress for health probes:8081
--leader-electEnable leader electionfalse

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

VariableDescriptionDefault
POD_NAMESPACENamespace where the operator is runningInjected by Kubernetes
POD_NAMEName of the operator podInjected by Kubernetes

Logging

VariableDescriptionDefault
LOG_LEVELLog level (debug, info, error)info
LOG_FORMATLog format (json, console)json

Metrics

VariableDescriptionDefault
METRICS_BIND_ADDRESSAddress for metrics endpoint:8080

Health Probes

VariableDescriptionDefault
HEALTH_PROBE_BIND_ADDRESSAddress 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

CRDDescriptionScope
KeycloakInstanceConnection to a Keycloak serverNamespaced
ClusterKeycloakInstanceCluster-scoped Keycloak connectionCluster

Realm Resources

CRDDescriptionParent
KeycloakRealmRealm configurationKeycloakInstance
ClusterKeycloakRealmCluster-scoped realmClusterKeycloakInstance

OAuth & Client Resources

CRDDescriptionParent
KeycloakClientOAuth2/OIDC clientKeycloakRealm
KeycloakClientScopeClient scope configurationKeycloakRealm
KeycloakProtocolMapperToken claim mappersKeycloakClient or KeycloakClientScope

Identity Resources

CRDDescriptionParent
KeycloakUserUser managementKeycloakRealm or KeycloakClient¹
KeycloakUserCredentialUser password managementKeycloakUser
KeycloakGroupGroup managementKeycloakRealm

Role & Access Control

CRDDescriptionParent
KeycloakRoleRealm and client rolesKeycloakRealm or KeycloakClient
KeycloakRoleMappingRole-to-subject mappingsKeycloakUser or KeycloakGroup

Federation & Infrastructure

CRDDescriptionParent
KeycloakComponentLDAP federation, key providersKeycloakRealm
KeycloakIdentityProviderExternal identity providersKeycloakRealm
KeycloakOrganizationOrganization 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

AliasFull Name
kcikeycloakinstances
kubectl get kci

Notes

  • The operator validates the connection on creation and periodically thereafter
  • Connection failures are reflected in the status.ready field
  • 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

FieldTypeDescriptionRequired
baseUrlstringURL of the Keycloak serverYes
credentials.secretRef.namestringName of the credentials secretYes
credentials.secretRef.namespacestringNamespace of the credentials secretYes
credentials.secretRef.usernameKeystringKey for username in secretNo (default: “username”)
credentials.secretRef.passwordKeystringKey for password in secretNo (default: “password”)
realmstringAdmin realm nameNo (default: “master”)
client.idstringClient ID for authenticationNo
client.secretstringClient secret (if confidential)No
token.secretNamestringSecret to cache access tokensNo

Status

FieldTypeDescription
readybooleanWhether connection to Keycloak is established
versionstringDetected Keycloak server version
statusstringCurrent status (Ready, ConnectionFailed, Error)
messagestringAdditional status information
conditions[]ConditionKubernetes conditions

Behavior

Connection Verification

The operator periodically verifies the connection to Keycloak by:

  1. Authenticating with the provided credentials
  2. Fetching server info to detect the version
  3. Updating the ready status 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

AspectKeycloakInstanceClusterKeycloakInstance
ScopeNamespacedCluster
Secret namespaceOptional (defaults to same)Required
Accessible fromSame namespace onlyAny namespace
Short namekcickci
Use caseSingle namespaceMulti-namespace/platform

Notes

  • Only one ClusterKeycloakInstance with 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:

PropertyTypeDescription
realmstringRealm name (required)
displayNamestringDisplay name for the realm
enabledbooleanWhether the realm is enabled
registrationAllowedbooleanAllow user registration
loginWithEmailAllowedbooleanAllow login with email
ssoSessionIdleTimeoutintegerSSO session idle timeout (seconds)
accessTokenLifespanintegerAccess token lifespan (seconds)

Short Names

AliasFull Name
kcrkeycloakrealms
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 ClusterKeycloakInstance for 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

FieldTypeDescriptionRequired
clusterInstanceRef.namestringReference to ClusterKeycloakInstanceOne of these
instanceRef.namestringReference to namespaced KeycloakInstanceOne of these
instanceRef.namespacestringNamespace of the KeycloakInstanceRequired if instanceRef
realmNamestringOverride realm name in KeycloakNo (defaults to metadata.name)
definitionobjectKeycloak RealmRepresentationYes

Definition Fields

The definition field accepts any valid Keycloak RealmRepresentation. Common fields include:

FieldTypeDescription
realmstringRealm identifier (required)
enabledbooleanWhether realm is enabled
displayNamestringDisplay name
sslRequiredstringSSL requirement: all, external, none
registrationAllowedbooleanAllow user self-registration
loginWithEmailAllowedbooleanAllow login with email
verifyEmailbooleanRequire email verification
resetPasswordAllowedbooleanEnable password reset
bruteForceProtectedbooleanEnable brute force protection
accessTokenLifespanintegerAccess token lifetime (seconds)
ssoSessionIdleTimeoutintegerSSO session idle timeout (seconds)
loginThemestringLogin page theme

Status

FieldTypeDescription
readybooleanWhether the realm is synced
statusstringCurrent status (Ready, InstanceNotReady, CreateFailed, etc.)
messagestringAdditional status information
resourcePathstringKeycloak API path for this realm
realmNamestringActual realm name in Keycloak
instanceobjectResolved instance reference
conditions[]ConditionKubernetes conditions

Behavior

Instance Resolution

The controller supports two instance reference types:

  1. clusterInstanceRef: References a ClusterKeycloakInstance by name
  2. instanceRef: References a namespaced KeycloakInstance by name and namespace

One of these must be specified.

Realm Synchronization

On each reconciliation:

  1. Connect to Keycloak using the referenced instance
  2. Check if the realm exists
  3. Create or update the realm with the specified definition
  4. Update status with the resource path

Cleanup

When a ClusterKeycloakRealm is deleted:

  1. The finalizer removes the realm from Keycloak
  2. All resources in Keycloak (clients, users, etc.) within that realm are deleted
  3. 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

AspectKeycloakRealmClusterKeycloakRealm
ScopeNamespacedCluster
Instance refSame namespace or cross-namespaceCluster or any namespaced
Accessible fromSame namespaceAny namespace
Short namekcrmckcrm
Use caseSingle namespaceMulti-namespace/platform

Notes

  • Only one ClusterKeycloakRealm with 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 definition are 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:

PropertyTypeDescription
clientIdstringClient identifier (required)
namestringDisplay name
enabledbooleanWhether client is enabled
publicClientbooleanPublic or confidential client
standardFlowEnabledbooleanEnable Authorization Code flow
directAccessGrantsEnabledbooleanEnable Resource Owner Password flow
serviceAccountsEnabledbooleanEnable service account
redirectUrisstring[]Valid redirect URIs
webOriginsstring[]Allowed CORS origins
rootUrlstringRoot URL for relative URIs

Short Names

AliasFull Name
kcckeycloakclients
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

PropertyTypeDescription
namestringScope name (required)
descriptionstringDescription
protocolstringProtocol (openid-connect, saml)
protocolMappersarrayProtocol mapper configurations
attributesmapAdditional attributes

Short Names

AliasFull Name
kccskeycloakclientscopes
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:

ReferenceUse Case
clientRefMapper applies to a specific client only
clientScopeRefMapper 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:

FieldTypeDescription
namestringMapper name (required)
protocolstringProtocol (usually “openid-connect” or “saml”)
protocolMapperstringMapper type (see common types below)
configobjectMapper-specific configuration

Common Protocol Mapper Types

OpenID Connect

Mapper TypeDescription
oidc-usermodel-attribute-mapperMaps user attribute to token claim
oidc-usermodel-property-mapperMaps user property to token claim
oidc-group-membership-mapperIncludes group membership in token
oidc-role-name-mapperMaps role names
oidc-hardcoded-claim-mapperAdds hardcoded claim
oidc-audience-mapperAdds audience to token
oidc-full-name-mapperMaps full name

SAML

Mapper TypeDescription
saml-user-attribute-mapperMaps user attribute
saml-group-membership-mapperMaps group membership
saml-role-list-mapperMaps roles

Short Names

AliasFull Name
kcpmkeycloakprotocolmappers
kubectl get kcpm

Notes

  • Mapper names must be unique within the client or client scope
  • The config values 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:

PropertyTypeDescription
usernamestringUsername (required)
emailstringEmail address
firstNamestringFirst name
lastNamestringLast name
enabledbooleanWhether user is enabled
emailVerifiedbooleanEmail verified flag
attributesmapCustom user attributes
groupsstring[]Group paths to join
requiredActionsstring[]Required actions on login

Short Names

AliasFull Name
kcukeycloakusers
kubectl get kcu

Parent Reference

A KeycloakUser can belong to one of three parent types:

ReferenceUse CaseParent Type
realmRefRegular realm usersKeycloakRealm
clusterRealmRefUsers in cluster-scoped realmsClusterKeycloakRealm
clientRefService account usersKeycloakClient

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 KeycloakRoleMapping to 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 groups are 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

FieldTypeDescriptionRequired
userRefResourceRefReference to the KeycloakUser resourceYes
userSecret.secretNamestringName of the Kubernetes SecretYes
userSecret.createbooleanCreate secret if it doesn’t existNo (default: false)
userSecret.usernameKeystringKey in secret for usernameNo (default: “username”)
userSecret.passwordKeystringKey in secret for passwordNo (default: “password”)
userSecret.passwordPolicy.lengthintLength of generated passwordNo (default: 16)
userSecret.passwordPolicy.symbolsbooleanInclude symbols in passwordNo (default: true)

Status

FieldTypeDescription
readybooleanWhether the credential is synced
statusstringCurrent status (Synced, Error, SecretError)
secretCreatedbooleanWhether the secret was created by the operator
messagestringAdditional status information
lastPasswordSyncstringTimestamp of last password sync

Behavior

Secret Creation

When create: true is set:

  1. The operator creates a new Secret if it doesn’t exist
  2. A password is generated according to the password policy
  3. The username is set to match the Keycloak user’s username

Password Sync

When the Secret exists (created or pre-existing):

  1. The operator reads the password from the Secret
  2. The password is set in Keycloak for the referenced user
  3. The lastPasswordSync timestamp is updated

Cleanup

When the KeycloakUserCredential is deleted:

  • If secretCreated: true in 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

PropertyTypeDescription
namestringGroup name (required)
pathstringFull group path (auto-generated)
attributesmapCustom group attributes
realmRolesstring[]Realm roles assigned to group
clientRolesmapClient roles assigned to group

Short Names

AliasFull Name
kcgkeycloakgroups
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:

ReferenceScopeUse Case
realmRefRealm roleShared across all clients in the realm
clusterRealmRefRealm roleFor cluster-scoped realms
clientRefClient roleSpecific to a single client

Note: Exactly one of these must be specified.

Definition Properties

The definition field accepts any valid Keycloak RoleRepresentation:

FieldTypeDescription
namestringRole name (required)
descriptionstringRole description
compositebooleanWhether this is a composite role
compositesobjectComposite role definitions (realm/client roles)
attributesobjectCustom attributes

Status Fields

FieldTypeDescription
readybooleanWhether the role is synchronized
statusstringCurrent status (e.g., “Ready”, “Error”)
messagestringHuman-readable status message
roleNamestringThe role name in Keycloak
observedGenerationintegerLast observed generation

Short Names

AliasFull Name
kcrlkeycloakroles
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

FieldTypeDescriptionRequired
subject.userRefResourceRefReference to KeycloakUserEither userRef or groupRef
subject.groupRefResourceRefReference to KeycloakGroupEither userRef or groupRef
roleRefResourceRefReference to KeycloakRole resourceEither roleRef or role
role.namestringKeycloak role name (inline)Either roleRef or role
clientRefResourceRefReference to KeycloakClient for client rolesNo (realm role if omitted)

Status

FieldTypeDescription
readybooleanWhether the mapping is synced
statusstringCurrent status (Synced, Error, SubjectError, RoleError)
messagestringAdditional status information
subjectIdstringKeycloak ID of the user/group
roleIdstringKeycloak ID of the role
mappingTypestringType: UserRealmRole, UserClientRole, GroupRealmRole, GroupClientRole

Behavior

Role Resolution

Using roleRef:

  1. The operator looks up the referenced KeycloakRole resource
  2. It retrieves the Keycloak role ID from the role’s status
  3. This is the recommended approach for roles managed by the operator

Using role.name:

  1. The operator queries Keycloak for a role with the given name
  2. This is useful for built-in roles like offline_access

Mapping Types

SubjectClientRefResult
userRef-User realm role mapping
userRefsetUser client role mapping
groupRef-Group realm role mapping
groupRefsetGroup client role mapping

Cleanup

When the KeycloakRoleMapping is deleted:

  1. The finalizer removes the role mapping from Keycloak
  2. 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 userRef or groupRef can be specified
  • Only one of roleRef or role can 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 inline role.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:

FieldTypeDescription
namestringComponent name (required)
providerIdstringProvider ID (e.g., “ldap”, “rsa-generated”)
providerTypestringProvider type (e.g., “org.keycloak.storage.UserStorageProvider”)
parentIdstringParent component ID (defaults to realm ID)
configobjectProvider-specific configuration (array of strings per key)

Common Provider Types

Provider TypeUse Case
org.keycloak.storage.UserStorageProviderLDAP, custom user storage
org.keycloak.keys.KeyProviderCryptographic keys (RSA, AES, etc.)
org.keycloak.storage.ldap.mappers.LDAPStorageMapperLDAP attribute mappers

Short Names

AliasFull Name
kccokeycloakcomponents
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 priority config

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:

PropertyTypeDescription
aliasstringUnique alias (required)
displayNamestringDisplay name
providerIdstringProvider type (oidc, saml, google, etc.)
enabledbooleanWhether provider is enabled
trustEmailbooleanTrust email from provider
storeTokenbooleanStore provider tokens
configmapProvider-specific configuration

Short Names

AliasFull Name
kcidpkeycloakidentityproviders
kubectl get kcidp

Notes

  • Store sensitive values like client secrets in Kubernetes Secrets and reference them
  • Consider using syncMode: IMPORT to 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:

PropertyTypeDescription
namestringOrganization name (required)
aliasstringURL-friendly identifier
descriptionstringDescription of the organization
enabledbooleanWhether organization is enabled
domainsarrayAssociated email domains
attributesmapCustom organization attributes

Domain Properties

PropertyTypeDescription
namestringDomain name (e.g., “example.com”)
verifiedbooleanWhether the domain is verified

Short Names

AliasFull Name
kcorgkeycloakorganizations
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 id field cannot be changed
  • The alias is 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

MetricTypeLabelsDescription
keycloak_operator_reconcile_totalCountercontroller, resultTotal number of reconciliations per controller
keycloak_operator_reconcile_duration_secondsHistogramcontrollerTime spent in reconciliation
keycloak_operator_reconcile_errors_totalCountercontroller, error_typeTotal errors by controller and type
keycloak_operator_last_reconcile_timestamp_secondsGaugecontrollerTimestamp of last successful reconciliation

Resource Metrics

MetricTypeLabelsDescription
keycloak_operator_resources_managedGaugeresource_type, namespaceNumber of managed resources
keycloak_operator_resources_readyGaugeresource_type, namespaceNumber of resources in ready state

Keycloak Connection Metrics

MetricTypeLabelsDescription
keycloak_operator_keycloak_connection_statusGaugeinstance, namespaceConnection status (1=connected, 0=disconnected)
keycloak_operator_keycloak_api_requests_totalCounterinstance, method, endpoint, statusTotal Keycloak API requests
keycloak_operator_keycloak_api_latency_secondsHistograminstance, method, endpointKeycloak API latency

Controller Metrics

MetricTypeLabelsDescription
keycloak_operator_workqueue_depthGaugecontrollerWork queue depth per controller

Error Types

The error_type label can have the following values:

  • fetch_error - Failed to fetch the Kubernetes resource
  • connection_error - Failed to connect to Keycloak
  • instance_not_ready - Referenced KeycloakInstance is not ready
  • realm_not_ready - Referenced KeycloakRealm is not ready
  • invalid_definition - Invalid resource definition (JSON parsing failed)
  • keycloak_api_error - Keycloak API call failed
  • secret_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:

  1. Overview

    • Total managed resources by type
    • Ready vs non-ready resources
    • Keycloak instance connection status
  2. Reconciliation Performance

    • Reconciliation rate per controller
    • Reconciliation duration (p50, p95, p99)
    • Error rate over time
  3. Errors & Issues

    • Error breakdown by type
    • Recent error spikes
    • Connection failures over time
  4. Keycloak API

    • API request rate by endpoint
    • API latency distribution
    • Error responses by status code

Key Metrics to Watch

MetricNormal RangeAction if Abnormal
Connection status1Check Keycloak availability, credentials
Error rate< 5%Review logs, check Keycloak health
Reconcile duration p99< 10sCheck Keycloak performance
Queue depth< 50Scale operator or reduce resources
Resources not ready0Check 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:

ControllerCRDResponsibilities
Instance ControllerKeycloakInstance, ClusterKeycloakInstanceConnection management, health checking
Realm ControllerKeycloakRealm, ClusterKeycloakRealmRealm CRUD, configuration sync
Client ControllerKeycloakClientClient CRUD, secret management
ClientScope ControllerKeycloakClientScopeScope CRUD
ProtocolMapper ControllerKeycloakProtocolMapperToken claim mapper configuration
User ControllerKeycloakUserUser CRUD
UserCredential ControllerKeycloakUserCredentialPassword management
Group ControllerKeycloakGroupGroup CRUD, hierarchy management
Role ControllerKeycloakRoleRealm and client role management
RoleMapping ControllerKeycloakRoleMappingRole-to-subject assignments
IdentityProvider ControllerKeycloakIdentityProviderExternal IDP configuration
Component ControllerKeycloakComponentLDAP, key providers, etc.
Organization ControllerKeycloakOrganizationOrganization 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:

  1. When a CR is created, a finalizer is added
  2. When a CR is deleted, the controller:
    • Removes the resource from Keycloak
    • Removes the finalizer
  3. 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.enabled Helm 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

ResourcesSync PeriodMax Concurrent Requests
< 505m (default)10 (default)
50-20015-30m10
200-50030m5-10
500+1h5

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

  1. Clone the repository:

    git clone https://github.com/Hostzero-GmbH/keycloak-operator.git
    cd keycloak-operator
    
  2. Install dependencies:

    go mod download
    
  3. 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 kind or go install sigs.k8s.io/kind@latest)
  • kubectl
  • Helm

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:

  1. Configure kubeconfig for your cluster
  2. Install CRDs: make install
  3. Create a KeycloakInstance pointing to your Keycloak
  4. Run locally: make run

Development Commands

CommandDescription
make buildBuild the operator binary
make runRun the operator locally
make installInstall CRDs to cluster
make generateGenerate DeepCopy methods
make manifestsGenerate CRD manifests
make fmtFormat code
make vetRun go vet
make lintRun 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

  1. Set breakpoints in your IDE
  2. Run with debug configuration:
    dlv debug ./cmd/main.go
    

Remote Debugging

For debugging in-cluster:

  1. Build with debug symbols:

    CGO_ENABLED=0 go build -gcflags="all=-N -l" -o manager ./cmd/main.go
    
  2. Use kubectl port-forward to access debugger port

Environment Variables

VariableDescriptionDefault
KUBECONFIGPath to kubeconfig~/.kube/config
KEYCLOAK_URLKeycloak URL for testshttp://localhost:8080
LOG_LEVELLog levelinfo

Testing

The Keycloak Operator has two levels of testing:

  1. Unit Tests: Fast, isolated tests using envtest
  2. 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:

  1. 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)
  2. 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:

  1. Sets up port-forwarding to Keycloak automatically
  2. Configures environment variables
  3. 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

VariableDescriptionDefault
USE_EXISTING_CLUSTERSet to true to use current kubeconfigfalse
KEYCLOAK_INSTANCE_NAMEName of existing KeycloakInstance to use(creates new)
KEYCLOAK_INSTANCE_NAMESPACENamespace of existing instancekeycloak-operator-e2e
OPERATOR_NAMESPACENamespace where operator is deployedkeycloak-operator
KEYCLOAK_URLURL for test’s direct Keycloak access (via port-forward)http://localhost:8080
KEYCLOAK_INTERNAL_URLURL operator uses to connect (in-cluster)http://keycloak.keycloak.svc.cluster.local
TEST_NAMESPACENamespace for test resourceskeycloak-operator-e2e
KEEP_TEST_NAMESPACEDon’t delete namespace after testsfalse

Test Categories

CategoryRequires Port-ForwardDescription
Basic CRUDNoCreate, update, delete resources via Kubernetes API
Status verificationNoVerify .status.ready and conditions
Drift detectionYesTests that modify Keycloak directly and verify reconciliation
Cleanup verificationYesTests that verify resources are deleted from Keycloak
Edge casesMixedSome 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 if USE_EXISTING_CLUSTER is not set
  • skipIfNoKeycloakAccess(t): Skip test if direct Keycloak access (port-forward) is unavailable
  • getInternalKeycloakClient(t): Create authenticated Keycloak client for direct API access
  • rawJSON(s string): Create runtime.RawExtension from JSON string
  • canConnectToKeycloak(): 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

  1. Fork the repository

  2. Create a feature branch:

    git checkout -b feature/my-feature
    
  3. Make your changes following the code style

  4. Add tests for new functionality

  5. Run checks:

    make fmt
    make vet
    make lint
    make test
    
  6. Commit with a clear message:

    git commit -m "feat: add support for X"
    
  7. Push and create a Pull Request

Commit Messages

Follow Conventional Commits:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation only
  • refactor: Code change without feature/fix
  • test: Adding tests
  • chore: Maintenance

Code Style

  • Follow standard Go conventions
  • Use gofmt and golangci-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

  1. Ensure all tests pass
  2. Update documentation if needed
  3. Request review from maintainers
  4. Address feedback
  5. 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.