Repository navigation
feat(roles): skip import writes to protected roles - #1675
Open
RutgerLubbers wants to merge 3 commits into
Open
RutgerLubbers wants to merge 3 commits into
RutgerLubbers wants to merge 3 commits into
Conversation
Keycloak 26.6.4 blocks updates and deletes of the realm roles admin and create-realm and of all realm-management client roles, returning 403. Since a realm export always contains those roles, any genuine drift made the whole import abort. Add import.protected-roles.* to define roles the import never updates, deletes or composite-syncs. Creation is unaffected, and protection is not transitive: an unprotected role may still reference a protected one. Defaults protect admin, create-realm and every realm-management client role, so the fix applies without configuration. mode=add unions extra roles onto those defaults, mode=replace uses exactly what is configured, and mode=replace with empty lists restores the previous behaviour. This is a behaviour change: on Keycloak versions older than 26.6.4, updates to the default-protected roles are now skipped with an info log.
Inject the resolver into RoleImportService instead of constructing it per doImport call, and drop the threaded parameter from eight private methods. Move the two role-filter helpers onto the resolver. No behaviour change.
Use Stream.toList() over Collectors.toList(), extract the role-delete loop bodies into methods to drop the double continue, and clean up redundant test annotations and an unreachable checked exception. No behaviour change.
|
RutgerLubbers
commented
Sep 3, 2026
Comment on lines
228
to
230
| RoleRepresentation patchedRole = CloneUtil.patch(existingRole, roleToImport, propertiesWithDependencies); | ||
| if (roleToImport.getAttributes() != null) { | ||
| patchedRole.setAttributes(roleToImport.getAttributes()); |
Author
There was a problem hiding this comment.
This part is untouched.
Besides there is a guard against existingRole being null before this method is called. So the return is never null.
RutgerLubbers
commented
Sep 3, 2026
Comment on lines
247
to
248
| RoleRepresentation patchedRole = CloneUtil.patch(existingRole, roleToImport, propertiesWithDependencies); | ||
| String roleName = existingRole.getName(); |
Author
There was a problem hiding this comment.
See comment for lines 228-230,
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




What this PR does / why we need it:
Keycloak 26.6.4 stopped allowing changes to a few roles. The realm roles
adminandcreate-realm, and all roles of therealm-managementclient, now return 403 if youtry to update or delete them through the Admin API. Also see #1673 .
Every realm export contains those roles. Today we feed all of them into the update,
delete and composite-sync paths, so the moment one of them actually differs from the
target realm, Keycloak answers 403 and the whole import stops. Importing a realm export
is our main use case, so this is a hard blocker.
This PR adds
import.protected-roles.*— a set of roles the import never updates,deletes, or composite-syncs. It works out of the box: with no configuration at all, the
roles Keycloak blocks are already protected.
The same setting solves a second thing: keeping a break-glass superuser or similar
high-privilege role out of reach of whoever edits the config files.
Details:
creation — a protected role that doesn't exist yet is still created as before.
among its composites, and that gets synced normally. Only writes to the protected
role are blocked.
manage-usersis not protected just becauserealm-managementhas a role by thatname — matching is on the client + role pair.
info, in the same style as the existingadmin-permissionsclient skip.build against.
New settings:
import.protected-roles.modeadd(default) protects the built-ins plus anything you configure.replaceprotects only what you configure.import.protected-roles.realm-rolesimport.protected-roles.client-roles.<clientId>*protects all of them, including roles added later.Defaults, i.e. what you get with no configuration:
Full opt-out, restoring the old behaviour exactly:
Which issue this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)format, will close that issue when PR gets merged): fixes #1673How this PR was tested:
*wildcard,addvsreplace, the empty-replaceopt-out, unknown clients, and the name-collisioncase.
managed.role=full, and the log message.modevalue fails at startup.ImportRolesIT(orders 80–86) against a real Keycloak.These deliberately force a real difference on a protected role — an export that
already matches the server performs no writes at all, so a naive export→import→import
test would pass even without this change. Covered: the role's own fields, its
composites,
managed.role=fulldeletion, both modes, the opt-out, the namespacecollision, and a protected client that isn't in the realm.
ImportRolesIT: 49 tests, 0 failures, against Keycloak 26.5.5. Full unit suite green,along with checkstyle, license, spotbugs and pmd.
Will run tests against a container to verify the issue on our setup.
Screenshots / logs (if relevant):
Special notes for your reviewer:
This is a behaviour change, and the CHANGELOG says so. On Keycloak versions older
than 26.6.4, changing e.g. the description of the
adminrealm role used to work andwill now be skipped with an
infolog. That's the deliberate trade-off for making thedefaults work with zero configuration;
mode: replacewith empty lists is thedocumented way back.
The built-in defaults are constants in the code, not property defaults. If they
were declared as defaults in
application.properties,mode: replacewith an emptymap would be impossible to express — Spring can't unset an already-bound map key, so
the opt-out wouldn't actually opt out. Resolving them in code keeps
adda real unionand
replacea real replacement.PR Readiness Checklist:
mvn testor relevant subset)CHANGELOG.mdrelease notes have been updated to reflect any significant (and particularly user-facing) changes introduced by this PR