This release unifies resource identity across all CRDs: every resource now declares its Keycloak identifier in a dedicated, immutable spec field instead of inside the raw definition blob. It also scopes namespaced references to their own namespace, adds declarative role and group assignment on KeycloakUser (including client service accounts), and fixes duplicate user-profile components and an organization reconcile loop.
Breaking changes
Typed identifier fields on all resource CRDs (#101)
Every resource CRD now identifies its Keycloak object through a dedicated spec field. The field is required, is validated at admission, and is immutable once set (changing an identifier previously orphaned the old Keycloak object silently).
| CRD | Identifier field |
|---|---|
KeycloakRealm, ClusterKeycloakRealm |
spec.realmName |
KeycloakClient |
spec.clientId |
KeycloakUser |
spec.username (omit for service accounts via clientRef) |
KeycloakGroup, KeycloakRole, KeycloakClientScope, KeycloakComponent, KeycloakOrganization, KeycloakProtocolMapper, KeycloakIdentityProviderMapper |
spec.name |
KeycloakIdentityProvider, KeycloakRequiredAction |
spec.alias |
Migration
Add the spec field carrying the same value the definition uses. An identifier inside spec.definition is tolerated only when it matches the spec field, so existing definitions do not need to be rewritten; a conflicting value is rejected.
# Before
spec:
definition:
realm: production
enabled: true
# After
spec:
realmName: production # new: required, immutable
definition:
realm: production # optional; must match spec.realmName if present
enabled: trueUnmigrated resources report InvalidIdentifier in their status and pause reconciliation until the spec field is added — nothing is deleted. The resolved identifier is surfaced in each resource's status (e.g. status.realmName) and is what dependent resources consume.
Also note: the KeycloakClient status field status.clientID was renamed to status.clientId.
Namespaced references no longer cross namespaces (#90)
The namespace property is removed from all namespaced refs (realmRef, instanceRef, clientRef, clientScopeRef, userRef, roleRef, groupRef, parentGroupRef, identityProviderRef); they now always resolve in the referring resource's own namespace. The field is silently pruned by the API server, not rejected — audit your manifests before upgrading.
Cross-namespace sharing remains fully supported through the cluster-scoped kinds: point resources in any namespace at a shared ClusterKeycloakRealm (clusterRealmRef) or ClusterKeycloakInstance (clusterInstanceRef), or co-locate the resources in one namespace.
Role and group assignments move out of the KeycloakUser definition (#94)
realmRoles, clientRoles, and groups keys inside spec.definition are now rejected with InvalidDefinition. They were previously ignored (Keycloak's user endpoint does not apply them), which commonly bit users pasting exported user representations. They are now typed spec fields that are actually reconciled — see Highlights below.
# Before (keys were silently ignored)
spec:
username: jdoe
definition:
enabled: true
realmRoles: ["offline_access"]
groups: ["developers"]
# After (authoritatively reconciled)
spec:
username: jdoe
definition:
enabled: true
realmRoles:
- offline_access
groups:
- developersStricter admission validation for references (#105)
CRD schemas now enforce what the API documentation always required: exactly one of realmRef/clusterRealmRef (or clientRef on users), at most one of role/roleRef, exactly one role-mapping subject, and so on. Manifests that were previously accepted with ambiguous or missing references are now rejected at admission time.
Highlights
- Identifier unification — uniform identity handling across all resource CRDs: typed, immutable identifier fields with CEL validation, resolved identifiers surfaced in status for dependent controllers, and deletion paths that no longer issue Keycloak calls with empty identifiers. See the new Spec Layout section for the architecture behind this. (#101)
- Declarative user roles and groups —
spec.realmRoles,spec.clientRoles, andspec.groupsonKeycloakUserare reconciled through Keycloak's dedicated role-mapping and group-membership endpoints, for regular users and service accounts alike. Each field is authoritative when set (an empty list removes everything) and unmanaged when omitted. Do not combine them withKeycloakRoleMappingresources for the same user. (#94) - Role mappings for service accounts —
KeycloakRoleMappinggainssubject.serviceAccountRefto assign roles to a client's service account without an intermediateKeycloakUser. (#94) - User-profile component adoption —
KeycloakComponentnow adopts the unnameddeclarative-user-profilecomponent Keycloak creates when the user profile is saved through the Admin UI or/users/profileAPI, instead of creating a duplicate. (#104) - Organization drift detection —
KeycloakOrganizationcompares desired and current state before updating, ending the continuous PUT loop that flooded logs and Keycloak. (#96) - Nightly container builds (#102) and GitHub Actions pinned to immutable SHAs (#103).
Upgrade Guide
1. Upgrade the operator first
The new identifier fields do not exist in the v0.9.0 CRD schemas and would be pruned if you applied migrated manifests before upgrading. Upgrade the operator (which updates the CRDs), then migrate manifests. Existing resources keep running; unmigrated ones pause reconciliation with InvalidIdentifier until step 2 — nothing is deleted.
helm upgrade keycloak-operator oci://ghcr.io/hostzero-gmbh/charts/keycloak-operator \
--namespace keycloak-operator \
--version 0.10.0
2. Migrate manifests
For every resource:
- Add the typed identifier field (see the table above) with the value currently used inside
spec.definition. - Remove
namespacefrom any refs; switch cross-namespace setups toclusterRealmRef/clusterInstanceRef. - On
KeycloakUser, moverealmRoles/clientRoles/groupsout ofspec.definitioninto the spec fields. - Ensure exactly one realm/instance/subject reference is set per resource.
What's Changed
- Feat/same namespace refs by @fkold in #90
- fix: prevent continious update organization loop by @nejec in #96
- chore: add nightly container builds by @nejec in #102
- ci: pin GitHub Actions to immutable SHAs by @luxass in #103
- fix(crd): enforce mutually exclusive references by @luxass in #105
- chore(deps): bump softprops/action-gh-release from 3.0.0 to 3.0.1 by @dependabot[bot] in #108
- chore(deps): bump actions/checkout from 6.0.3 to 7.0.0 by @dependabot[bot] in #107
- chore(deps): bump the kubernetes group with 3 updates by @dependabot[bot] in #114
- chore(deps): bump the github-actions group with 9 updates by @dependabot[bot] in #115
- chore(deps): bump the github-actions group with 2 updates by @dependabot[bot] in #119
- chore(deps): bump github.com/prometheus/client_golang from 1.23.2 to 1.24.1 by @dependabot[bot] in #118
- chore(deps): bump github.com/go-logr/logr from 1.4.3 to 1.4.4 by @dependabot[bot] in #117
- chore(deps): bump the kubernetes group with 3 updates by @dependabot[bot] in #116
- Feat: identifier unification by @fkold in #101
- feat: reconcile realm roles, client roles, and groups on KeycloakUser by @mohamedhabas11 in #94
- fix: adopt existing user profile components by @luxass in #104
New Contributors
- @fkold made their first contribution in #90
- @nejec made their first contribution in #96
- @mohamedhabas11 made their first contribution in #94
Full Changelog: v0.9.0...v0.10.0