Multi-Tenant Configuration Guide¶
Configure the operator for multi-tenant environments where multiple teams manage their own realms and clients independently.
Team Responsibilities & Onboarding¶
When introducing the Keycloak Operator to your organization, clear role definitions are essential for a smooth "Realm-as-Tenant" adoption.
Roles & Responsibilities Matrix¶
| Responsibility | Platform Team 🛠️ | Realm Owner (App Team A) 👑 | Client Owner (App Team B) 👤 |
|---|---|---|---|
| Scope | Cluster Infrastructure & Operator | Specific Realm (Tenant) | Specific Application (Client) |
| Infrastructure | Deploys Operator & CRDs. Ensures Keycloak availability. Manages global limits/quotas. |
N/A | N/A |
| Security Boundary | Manages ClusterRoles. No access to Realm secrets by default. |
Owns the Realm boundary. Controls clientAuthorizationGrants.Manages Realm Users/Groups. |
Manages Client Secrets. Configures Redirect URIs. |
| Onboarding | Creates Namespaces. Grants Realm Creation rights. |
Creates Realm CR. Approves Client access via PRs. |
Creates Client CR in own Namespace. Requests access to Realm. |
| Troubleshooting | Operator logs, Pod health, Network policies. | Realm configuration errors. Auth flows, Theme issues. |
Client connection errors. Invalid Redirect URIs. |
Onboarding New Teams¶
1. For Platform Engineers:
* Explain: "We provide the Keycloak service and the operator. You own your configuration."
* Configure: Set up namespaces and RBAC so teams can create KeycloakRealm resources.
* Security: You don't need to see their secrets. The Operator only accesses namespaces where it is explicitly invited via the Realm Helm chart.
2. For Realm Owners (Tenant Managers):
* Explain: "You own a 'Virtual Keycloak'. You control who can connect to it."
* Action: Deploy your Realm using the keycloak-realm Helm chart.
* Gatekeeping: Use the clientAuthorizationGrants list in your Realm YAML to whitelist other team's namespaces. You are the gatekeeper.
3. For Application Developers (Client Owners):
* Explain: "You define your app's auth client as code, right next to your deployment."
* Action: Deploy a KeycloakClient CR in your namespace.
* Access: If you need to connect to a shared Realm, ask the Realm Owner to add your namespace to their allowlist.
Architecture¶
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#00b8d9','primaryTextColor':'#fff','primaryBorderColor':'#0097a7','lineColor':'#00acc1','secondaryColor':'#006064','tertiaryColor':'#fff'}}}%%
graph LR
subgraph platform["👥 Platform Team"]
deploy["Deploy Operator"]
instance["Create Keycloak Instance"]
rbac["Grant RBAC Permissions"]
end
subgraph app["👥 Application Teams"]
realms["Create Realms<br/><small>(RBAC-controlled)</small>"]
grants["Set clientAuthorizationGrants"]
clients["Manage Clients<br/><small>(grant list-controlled)</small>"]
end
deploy --> instance
instance --> rbac
rbac -.->|authorize| realms
realms --> grants
grants --> clients
style platform fill:#263238,stroke:#00acc1,stroke-width:2px,color:#fff
style app fill:#263238,stroke:#00acc1,stroke-width:2px,color:#fff
style deploy fill:#00838f,stroke:#006064,color:#fff
style instance fill:#00838f,stroke:#006064,color:#fff
style rbac fill:#00838f,stroke:#006064,color:#fff
style realms fill:#00838f,stroke:#006064,color:#fff
style grants fill:#00838f,stroke:#006064,color:#fff
style clients fill:#00838f,stroke:#006064,color:#fff
Key Concepts:
- Realm Creation: Controlled by Kubernetes RBAC (RoleBinding)
- Client Creation: Controlled by realm's
clientAuthorizationGrantslist - Declarative: All authorization via manifest fields, no secrets/tokens
- GitOps-Friendly: Everything in version control
Platform Team Setup¶
Helm is the primary path for multi-tenant installs. The raw RBAC examples later in this guide are for clusters that intentionally manage the surrounding permissions outside the charts.
1. Deploy Shared Keycloak¶
Install the operator with Keycloak instance using Helm:
helm install keycloak-operator oci://ghcr.io/vriesdemichael/charts/keycloak-operator \
--namespace platform \
--set keycloak.managed=true \
--set keycloak.replicas=3 \
--set keycloak.database.cnpg.enabled=true \
--set keycloak.ingress.enabled=true \
--set keycloak.ingress.host=keycloak.company.com \
--set keycloak.ingress.path=/ \
--set keycloak.ingress.tlsEnabled=true
2. Create Namespaces for Teams¶
# Create namespaces
kubectl create namespace team-alpha
kubectl create namespace team-beta
kubectl create namespace team-gamma
# Label for organization
kubectl label namespace team-alpha team=alpha env=prod
kubectl label namespace team-beta team=beta env=prod
kubectl label namespace team-gamma team=gamma env=prod
3. Grant Realm Creation Permissions¶
Create ClusterRole for realm management:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: keycloak-realm-manager
rules:
- apiGroups: ["vriesdemichael.github.io"]
resources: ["keycloakrealms"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["vriesdemichael.github.io"]
resources: ["keycloakclients"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Grant to each team:
# Team Alpha
kubectl create rolebinding realm-manager-alpha \
--clusterrole=keycloak-realm-manager \
--serviceaccount=team-alpha:default \
--namespace=team-alpha
# Team Beta
kubectl create rolebinding realm-manager-beta \
--clusterrole=keycloak-realm-manager \
--serviceaccount=team-beta:default \
--namespace=team-beta
# Team Gamma
kubectl create rolebinding realm-manager-gamma \
--clusterrole=keycloak-realm-manager \
--serviceaccount=team-gamma:default \
--namespace=team-gamma
4. Grant Operator Namespace Access¶
If you use the keycloak-realm and keycloak-client charts with their default rbac.create=true setting, they create the namespace RoleBindings for you. The manual commands below are only for the advanced/manual path.
The namespace-access ClusterRole is created by the operator Helm chart. Its actual name depends on the Helm release and namespace, so for the example below the chart produces keycloak-operator-platform-namespace-access.
The operator needs to read secrets in each team namespace:
# Create RoleBinding for each team namespace
for TEAM in team-alpha team-beta team-gamma; do
kubectl create rolebinding keycloak-operator-access \
--clusterrole=keycloak-operator-platform-namespace-access \
--serviceaccount=platform:keycloak-operator \
--namespace=$TEAM
done
Secrets the operator reads from tenant namespaces must also carry the label vriesdemichael.github.io/keycloak-allow-operator-read=true.
Application Team Usage¶
Create a Realm¶
Each team creates realms in their own namespace using the Helm chart:
helm install alpha-production oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace team-alpha \
--set realmName=alpha-production \
--set operatorRef.namespace=platform \
--set clientAuthorizationGrants[0]=team-alpha \
--set clientAuthorizationGrants[1]=team-alpha-dev \
--set security.registrationAllowed=false \
--set security.resetPasswordAllowed=true \
--set security.verifyEmail=true
Create Clients in Authorized Namespaces¶
Team Alpha can create clients in their namespace using the Helm chart:
helm install alpha-app oci://ghcr.io/vriesdemichael/charts/keycloak-client \
--namespace team-alpha \
--set clientId=alpha-app \
--set realmRef.name=alpha-production \
--set realmRef.namespace=team-alpha \
--set publicClient=false \
--set redirectUris[0]=https://app.team-alpha.com/callback
Cross-Namespace Client Creation¶
If a realm grants access, other namespaces can create clients:
# In team-alpha-dev namespace
helm install dev-app oci://ghcr.io/vriesdemichael/charts/keycloak-client \
--namespace team-alpha-dev \
--set clientId=dev-app \
--set realmRef.name=alpha-production \
--set realmRef.namespace=team-alpha \
--set redirectUris[0]=https://dev.team-alpha.com/callback
Multi-Realm Scenarios¶
Shared Platform Realm¶
Platform team creates a central realm that all teams can use:
helm install company-sso oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace platform \
--set realmName=company-sso \
--set operatorRef.namespace=platform \
--set clientAuthorizationGrants[0]=team-alpha \
--set clientAuthorizationGrants[1]=team-beta \
--set clientAuthorizationGrants[2]=team-gamma \
--set security.registrationAllowed=false \
--set security.resetPasswordAllowed=true
All teams can now create clients in this realm.
Team-Specific Realms with Selective Sharing¶
Team creates realm and selectively grants access:
helm install beta-services oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace team-beta \
--set realmName=beta-services \
--set operatorRef.namespace=platform \
--set clientAuthorizationGrants[0]=team-beta \
--set clientAuthorizationGrants[1]=team-beta-staging \
--set clientAuthorizationGrants[2]=partner-integration-team
Security Model¶
Realm Creation Authorization¶
Who can create realms?
- Users/ServiceAccounts with RoleBinding granting create permission on KeycloakRealm
- Platform team controls via RBAC policies
Verification:
# Check if team can create realms
kubectl auth can-i create keycloakrealms.vriesdemichael.github.io \
--as=system:serviceaccount:team-alpha:default \
--namespace=team-alpha
Client Creation Authorization¶
Who can create clients?
- Namespaces listed in realm's clientAuthorizationGrants
- Defined by realm owner (application team)
- Fully declarative (no secret distribution)
Verification:
# Check realm's grants
kubectl get keycloakrealm alpha-production -n team-alpha \
-o jsonpath='{.spec.clientAuthorizationGrants}' | jq
Namespace Isolation¶
- Teams cannot access other teams' namespaces (Kubernetes RBAC)
- Teams cannot modify other teams' realms (resource ownership)
- Clients can only reference realms that grant their namespace access
- Operator enforces authorization at reconciliation time
GitOps Workflow¶
ArgoCD Application Structure¶
gitops-repo/
├── platform/
│ ├── keycloak-operator.yaml
│ └── shared-realms/
│ └── company-sso.yaml
├── team-alpha/
│ ├── realms/
│ │ └── alpha-production.yaml
│ └── clients/
│ ├── web-app.yaml
│ └── mobile-app.yaml
└── team-beta/
├── realms/
│ └── beta-services.yaml
└── clients/
└── api-gateway.yaml
Updating Authorization Grants¶
In GitOps workflows, update the realm chart values in Git and let ArgoCD or Flux apply the change instead of patching live objects by hand.
# team-alpha/realms/alpha-production.values.yaml
realmName: alpha-production
operatorRef:
namespace: platform
clientAuthorizationGrants:
- team-alpha
- team-alpha-dev
- new-namespace
To grant a new namespace access:
# Update realm via helm upgrade
helm upgrade alpha-production oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace team-alpha \
--reuse-values \
--set clientAuthorizationGrants[0]=team-alpha \
--set clientAuthorizationGrants[1]=team-alpha-dev \
--set clientAuthorizationGrants[2]=new-namespace
Changes apply immediately - new namespace can create clients.
Common Patterns¶
Development/Staging/Production Separation¶
# Production realm - strict grants
helm install prod-realm oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace team-prod \
--set realmName=prod-realm \
--set operatorRef.namespace=platform \
--set clientAuthorizationGrants[0]=team-prod
# Staging realm - more permissive
helm install staging-realm oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace team-staging \
--set realmName=staging-realm \
--set operatorRef.namespace=platform \
--set clientAuthorizationGrants[0]=team-staging \
--set clientAuthorizationGrants[1]=team-dev \
--set clientAuthorizationGrants[2]=qa-team
Partner Integration¶
helm install partner-api oci://ghcr.io/vriesdemichael/charts/keycloak-realm \
--namespace platform \
--set realmName=partner-api \
--set operatorRef.namespace=platform \
--set clientAuthorizationGrants[0]=partner-a \
--set clientAuthorizationGrants[1]=partner-b \
--set clientAuthorizationGrants[2]=internal-gateway \
--set security.registrationAllowed=false \
--set security.bruteForceProtected=true
Troubleshooting¶
Permission Denied Creating Realm¶
# Check RBAC permissions
kubectl auth can-i create keycloakrealms.vriesdemichael.github.io \
--namespace=team-alpha
# Check RoleBindings
kubectl get rolebinding -n team-alpha \
-o json | jq '.items[] | select(.subjects[]?.kind=="ServiceAccount")'
Solution: Create RoleBinding granting realm creation permission.
Client Creation Fails - Not Authorized¶
# Check realm's grants
kubectl get keycloakrealm <realm> -n <realm-namespace> \
-o jsonpath='{.spec.clientAuthorizationGrants[*]}'
# Check operator logs
kubectl logs -n platform -l app=keycloak-operator \
| grep -i "authorization\|grant"
Solution: Add client's namespace to realm's clientAuthorizationGrants.
Operator Can't Read Secrets¶
# Check operator has access to namespace
kubectl auth can-i get secrets \
--as=system:serviceaccount:platform:keycloak-operator \
--namespace=team-alpha
Solution: Create RoleBinding for operator in the namespace.
Best Practices¶
✅ Use least-privilege grants - Only grant namespaces that need access
✅ Document grants - Comment why each namespace is granted
✅ Review regularly - Audit clientAuthorizationGrants periodically
✅ Separate environments - Different realms for dev/staging/prod
✅ Use GitOps - All changes via PR workflow
✅ Monitor authorization - Alert on denied client creation attempts