A Kubernetes operator for managing resources in Keycloak; The project aims to enhance Keycloak's integration with Kubernetes and to consequently allow the definition of Keycloak resources in a (more) declarative way.
⚠️ This is a WIP project.
While there is a little bit of overlap with the Keycloak Operator, this project mostly focusses on different concerns and can perfectly be used in conjunction with the Keycloak Operator (which is more focused on the deployment of Keycloak itself).
- Sync client credentials from Keycloak to Kubernetes secrets (Implemented ✅)
- Create and actively manage Keycloak realms from Kubernetes CRs (Implemented ✅)
- Create and actively manage Keycloak clients from Kubernetes CRs (Implemented ✅)
- Realm import, which runs every reconciliation in order to cover for management of resources in Keycloak that are not yet supported by the operator. (Implemented ✅)
- Management of client scopes (Partially implemented, not documented ⏳)
- Management of users (Partially implemented, not documented ⏳)
- Management of groups (Partially implemented, not working, not documented ⏳)
helm repo add rightcrowd https://rightcrowd.github.io/helm-charts
helm repo update
helm install keycloak-realm-operator rightcrowd/keycloak-realm-operator
Example values file:
keycloak:
url: https://keycloak.thecakeshop.com
username:
valueFrom:
secretKeyRef:
name: keycloak-admin-credentials
key: username
password:
valueFrom:
secretKeyRef:
name: keycloak-admin-credentials
key: password
The operator can be used to create and manage Keycloak realms.
To accomplish this, the KeycloakRealm
custom resource is used.
The ID of the realm
The display name of the realm
Wether or not to delete the realm from Keycloak when the corresponding CR is deleted
Wether or not to take management of the realm if it were to already exist while not created by the operator
An object matching the RealmRepresentation class, dictating the configuration of the realm
By specifying this field, one ore more partial realm imports are performed sequentially as part of every reconciliation loop. This can be useful to set up resources not directly supported by the operator.
- Depending on how it's used, this could override other assets, including configuration or resources which are actively managed by the operator.
Example: It's perfectly possible to specify a client in the realmImport that is also defined using the KeycloakClient CR. If the
ifResourceExists
field is set toOVERWRITE
, this would lead to an endless client update loop. - There is no deletion mechanism. The operator does not actively manage what's specified in the import; It simply periodically imports it. If a resource was present in the import before and is deleted afterwards, it will not be deleted by the operator.
The 'import' field should be an object matching the PartialImportRepresentation class.
Example:
realmImports:
- ifResourceExists: OVERWRITE
import:
eventsListeners:
- jboss-logging
- metrics-listener
roles:
realm:
- name: offline_access
description: ${role_offline-access}
composite: false
clientRole: false
attributes: {}
apiVersion: k8s.rightcrowd.com/v1alpha1
kind: KeycloakRealm
metadata:
name: funny-realm
spec:
realmId: funny
displayName: Funny Haha
pruneRealm: true
claimRealm: true
representation:
loginWithEmailAllowed: false
registrationEmailAsUsername: true
emailTheme: keycloak
displayNameHtml: "<h5>Funny Realm</h5>"
The operator can be used to create and manage Keycloak clients.
To accomplish this, the KeycloakClient
custom resource is used.
The ID of the realm
The ID of the client
The name of the client
By default, the client secret is generated by Keycloak. However, a secret can be provided from Kubernetes as well by specifying this field.
secret:
value: supersecret123
secret:
valueFrom:
secretKeyRef:
name: the-k8s-secret-we-want-to-get-the-value-from
namespace: the-namespace-of-that-k8s-secret
key: secretKey
Wether or not to delete the client from Keycloak when the corresponding CR is deleted
Wether or not to take management of the client if it were to already exist while not created by the operator
An array of names of the scopes to be assigned to the client (as optional)
An array of names of the scopes to be assigned to the client (as default)
An object matching the ClientRepresentation class, dictating the configuration of the client
apiVersion: k8s.rightcrowd.com/v1alpha1
kind: KeycloakClient
metadata:
name: funny-client
spec:
realm: funny
clientId: funny-client
name: Funny Client
prune: true
claim: true
scopes:
default:
- test-scope
representation:
implicitFlowEnabled: true
The operator can be used to sync Keycloak client credentials to Kubernetes secrets.
To accomplish this, the KeycloakClientCredential
custom resource is used.
The name of the Kubernetes secret where the client credentials should be stored. The secret will be created in the same namespace as the KeycloakClientCredential
resource.
The data of the secret can be sculpted using the optional targetSecretTemplate
field, which accepts golang template syntax. If the targetSecretTemplate
field is not provided, the following template will be used:
- key: realm
template: {{ .realm }}
- key: clientId
template: {{ .clientId }}
- key: clientSecret
template: {{ .clientSecret }}
The fallbackStrategy
field can be used to control the behavior of the operator when the client is not found in Keycloak. The following values are supported:
error
: The operator will fail when the client is not found in Keycloak.skip
: The operator will skip the reconciliation when the client is not found in Keycloak. This is the default value.
The example below syncs the credentials for Keycloak client that-one-client
in realm rightcrowd-core
to Kubernetes secret supersecret-keycloak-client-secret
.
The KeycloakClientCredential
is to be deployed in the namespace where the related secret should be created.
apiVersion: k8s.rightcrowd.com/v1alpha1
kind: KeycloakClientCredential
metadata:
name: supersecret-keycloak-client-sync
spec:
realm: funny
clientId: funny-client
targetSecretName: supersecret-keycloak-client-secret
## ⬇️ Optional
fallbackStrategy: skip # Default is "skip". Can be set to "error" to fail the reconciliation if the client is not found in Keycloak.
targetSecretTemplate:
- key: realm
template: "{{ .realm }}"
- key: clientId
template: "{{ .clientId }}"
- key: clientSecret
template: "{{ .clientSecret }}"
- key: connectionString
template: "https://{{ .clientId }}:{{ .clientSecret }}@funny-shop-api"
Based on this definition, the operator will fetch the credentials from Keycloak and create a Kubernetes secret looking as follows:
apiVersion: v1
kind: Secret
metadata:
name: supersecret-cake-shop-credentials
namespace: cake-shop
type: Opaque
data:
realm: ZnVubnk=
clientId: ZnVubnktY2xpZW50
clientSecret: U2VlbXMgbGlrZSB5b3UncmUgd29ya2luZyBvbiBzb21lIGNvb2wgc3R1ZmYg8J+RgCBDaGVjayBvdXQgb3VyIGNhcmVlcnMgcGFnZSEgaHR0cHM6Ly93d3cucmlnaHRjcm93ZC5jb20vY2FyZWVycw==
connectionString: aHR0cHM6Ly9mdW5ueS1jbGllbnQ6U2VlbXMgbGlrZSB5b3UncmUgd29ya2luZyBvbiBzb21lIGNvb2wgc3R1ZmYg8J+RgCBDaGVjayBvdXQgb3VyIGNhcmVlcnMgcGFnZSEgaHR0cHM6Ly93d3cucmlnaHRjcm93ZC5jb20vY2FyZWVyc0BmdW5ueS1zaG9wLWFwaQ==