From 464fc90519088e289c84c5a8da248490665414f3 Mon Sep 17 00:00:00 2001 From: Pedro Ruivo Date: Wed, 9 Oct 2024 20:40:24 +0100 Subject: [PATCH] Fail to start if work cache is not replicated Keycloak will now fail to start if the work cache is replicated. Listeners require the data to be local. Closes #33702 Signed-off-by: Pedro Ruivo Signed-off-by: Alexander Schwartz Co-authored-by: Alexander Schwartz --- .../topics/changes/changes-26_1_0.adoc | 6 ++++++ ...aultInfinispanConnectionProviderFactory.java | 2 +- .../storage/infinispan/CacheManagerFactory.java | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/documentation/upgrading/topics/changes/changes-26_1_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_1_0.adoc index 34985c25e3..3fc7372481 100644 --- a/docs/documentation/upgrading/topics/changes/changes-26_1_0.adoc +++ b/docs/documentation/upgrading/topics/changes/changes-26_1_0.adoc @@ -3,3 +3,9 @@ Previous releases ignored any change to `conf/cache-ispn.xml` if the `--cache-config-file` option was not provided. Starting from this release, when `--cache-config-file` is not set, the default Infinispan XML configuration file is `conf/cache-ispn.xml` as this is both the expected behavior and the implied behavior given the docs of the current and previous releases. + += Embedded Infinispan: `work` cache must be replicated + +The embedded `work` cache needs to be configured as a `replicated-cache` for cache invalidation to work as expected. + +Starting from this release, {project_name} check this at startup and will fail to start if it is not configured as such. diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index 78b510aa61..b344c745ed 100755 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -371,7 +371,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon workBuilder.simpleCache(false); workBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC); } - defineClusteredCache(cacheManager, WORK_CACHE_NAME, builder.build()); + defineClusteredCache(cacheManager, WORK_CACHE_NAME, workBuilder.build()); } return cacheManager; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java index 00934507eb..b8ea6482b8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java @@ -87,6 +87,7 @@ import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.L import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.USER_SESSION_CACHE_NAME; +import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.WORK_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.skipSessionsCacheIfRequired; import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.SCRAM_SHA_512; @@ -314,6 +315,7 @@ public class CacheManagerFactory { } configureCacheMaxCount(builder, CachingOptions.CLUSTERED_MAX_COUNT_CACHES); configureSessionsCaches(builder); + validateWorkCacheConfiguration(builder); } configureCacheMaxCount(builder, CachingOptions.LOCAL_MAX_COUNT_CACHES); checkForRemoteStores(builder); @@ -514,6 +516,21 @@ public class CacheManagerFactory { } } + private static void validateWorkCacheConfiguration(ConfigurationBuilderHolder builder) { + var cacheBuilder = builder.getNamedConfigurationBuilders().get(WORK_CACHE_NAME); + if (cacheBuilder == null) { + throw new RuntimeException("Unable to start Keycloak. '%s' cache is missing".formatted(WORK_CACHE_NAME)); + } + if (builder.getGlobalConfigurationBuilder().cacheContainer().transport().getTransport() == null) { + // non-clustered, Keycloak started in dev mode? + return; + } + var cacheMode = cacheBuilder.clustering().cacheMode(); + if (!cacheMode.isReplicated()) { + throw new RuntimeException("Unable to start Keycloak. '%s' cache must be replicated but is %s".formatted(WORK_CACHE_NAME, cacheMode.friendlyCacheModeString().toLowerCase())); + } + } + private static String requiredStringProperty(String propertyName) { return Configuration.getOptionalKcValue(propertyName).orElseThrow(() -> new RuntimeException("Property " + propertyName + " required but not specified")); }