From beb20dc425f3e86bfe564537f31f501db9ac6aba Mon Sep 17 00:00:00 2001 From: Pedro Ruivo Date: Mon, 3 Feb 2025 08:53:31 +0000 Subject: [PATCH] Add default configuration for cache 'crl' Fixes #36752 Signed-off-by: Pedro Ruivo Signed-off-by: Michal Hajas Signed-off-by: Pedro Ruivo Co-authored-by: Michal Hajas Co-authored-by: Stian Thorgersen --- ...ltInfinispanConnectionProviderFactory.java | 9 +- .../infinispan/InfinispanUtil.java | 10 +++ .../infinispan/CacheManagerFactory.java | 41 +++++++-- .../it/utils/RawKeycloakDistribution.java | 8 ++ .../server/DistributionKeycloakServer.java | 5 ++ .../server/EmbeddedKeycloakServer.java | 29 +++++++ .../server/KeycloakServerConfigBuilder.java | 25 +++++- .../server/RemoteKeycloakServer.java | 9 ++ ...nfinispanXMLBackwardCompatibilityTest.java | 34 ++++++++ .../infinispan-xml-kc26.xml | 85 +++++++++++++++++++ 10 files changed, 239 insertions(+), 16 deletions(-) create mode 100644 tests/base/src/test/java/org/keycloak/tests/infinispan/InfinispanXMLBackwardCompatibilityTest.java create mode 100644 tests/base/src/test/resources/embedded-infinispan-config/infinispan-xml-kc26.xml 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 aefff605f5b..fb449586878 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 @@ -68,7 +68,6 @@ import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.A import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES; -import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_DEFAULT_MAX; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.JGROUPS_BIND_ADDR; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.JGROUPS_UDP_MCAST_ADDR; @@ -480,13 +479,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon } protected Configuration getCrlCacheConfig() { - ConfigurationBuilder cb = createCacheConfigurationBuilder(); - - cb.memory() - .whenFull(EvictionStrategy.REMOVE) - .maxCount(CRL_CACHE_DEFAULT_MAX); - - return cb.build(); + return InfinispanUtil.getCrlCacheConfig().build(); } private void registerSystemWideListeners(KeycloakSession session) { diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java index d5e5ce7dcf0..e715afab665 100644 --- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java +++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanUtil.java @@ -185,6 +185,16 @@ public class InfinispanUtil { return cb; } + public static ConfigurationBuilder getCrlCacheConfig() { + var builder = createCacheConfigurationBuilder(); + + builder.memory() + .whenFull(EvictionStrategy.REMOVE) + .maxCount(InfinispanConnectionProvider.CRL_CACHE_DEFAULT_MAX); + + return builder; + } + /** * Replaces the {@link TimeService} in infinispan with the one that respects Keycloak {@link Time}. * @param 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 b41ece36df9..5e9c0904681 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 @@ -29,6 +29,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; @@ -36,7 +37,6 @@ import io.agroal.api.AgroalDataSource; import io.micrometer.core.instrument.Metrics; import io.quarkus.arc.Arc; import jakarta.persistence.EntityManager; - import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.RemoteCacheManagerAdmin; @@ -72,6 +72,8 @@ import org.keycloak.common.Profile; import org.keycloak.common.util.MultiSiteUtils; import org.keycloak.config.CachingOptions; import org.keycloak.config.MetricsOptions; +import org.keycloak.connections.infinispan.InfinispanConnectionProvider; +import org.keycloak.connections.infinispan.InfinispanUtil; import org.keycloak.connections.jpa.JpaConnectionProvider; import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.infinispan.util.InfinispanUtils; @@ -100,6 +102,8 @@ import static org.keycloak.config.CachingOptions.CACHE_REMOTE_USERNAME_PROPERTY; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CLUSTERED_CACHE_NAMES; +import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.CRL_CACHE_NAME; +import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOCAL_CACHE_NAMES; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_CLIENT_SESSION_CACHE_NAME; import static org.keycloak.connections.infinispan.InfinispanConnectionProvider.OFFLINE_USER_SESSION_CACHE_NAME; @@ -111,6 +115,11 @@ import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.SCRA public class CacheManagerFactory { private static final Logger logger = Logger.getLogger(CacheManagerFactory.class); + // Map with the default cache configuration if the cache is not present in the XML. + private static final Map> DEFAULT_CONFIGS = Map.of( + CRL_CACHE_NAME, InfinispanUtil::getCrlCacheConfig + ); + private static final Supplier TO_NULL = () -> null; private final CompletableFuture cacheManagerFuture; private final CompletableFuture remoteCacheManagerFuture; @@ -337,6 +346,13 @@ public class CacheManagerFactory { } Marshalling.configure(builder.getGlobalConfigurationBuilder()); + assertAllCachesAreConfigured(builder, + // skip revision caches, those are defined by DefaultInfinispanConnectionProviderFactory + Arrays.stream(LOCAL_CACHE_NAMES) + .filter(Predicate.not(InfinispanConnectionProvider.REALM_REVISIONS_CACHE_NAME::equals)) + .filter(Predicate.not(InfinispanConnectionProvider.AUTHORIZATION_REVISIONS_CACHE_NAME::equals)) + .filter(Predicate.not(InfinispanConnectionProvider.USER_REVISIONS_CACHE_NAME::equals)) + ); if (InfinispanUtils.isRemoteInfinispan()) { var builders = builder.getNamedConfigurationBuilders(); // remove all distributed caches @@ -352,6 +368,7 @@ public class CacheManagerFactory { builder.getGlobalConfigurationBuilder().nonClusteredDefault(); } else { // embedded mode! + assertAllCachesAreConfigured(builder, Arrays.stream(CLUSTERED_CACHE_NAMES)); if (builder.getNamedConfigurationBuilders().entrySet().stream().anyMatch(c -> c.getValue().clustering().cacheMode().isClustered())) { configureTransportStack(builder, em); configureRemoteStores(builder); @@ -600,14 +617,26 @@ public class CacheManagerFactory { private static void configureCacheMaxCount(ConfigurationBuilderHolder holder, String[] caches) { for (String cache : caches) { + var memory = holder.getNamedConfigurationBuilders().get(cache).memory(); String propKey = CachingOptions.cacheMaxCountProperty(cache); Configuration.getOptionalKcValue(propKey) .map(Integer::parseInt) - .ifPresent(maxCount -> holder.getNamedConfigurationBuilders() - .get(cache) - .memory() - .maxCount(maxCount) - ); + .ifPresent(memory::maxCount); + } + } + + private static void assertAllCachesAreConfigured(ConfigurationBuilderHolder holder, Stream caches) { + for (var it = caches.iterator() ; it.hasNext() ; ) { + var cache = it.next(); + var builder = holder.getNamedConfigurationBuilders().get(cache); + if (builder != null) { + continue; + } + builder = DEFAULT_CONFIGS.getOrDefault(cache, TO_NULL).get(); + if (builder == null) { + throw new IllegalStateException("Infinispan cache '%s' not found. Make sure it is defined in your XML configuration file.".formatted(cache)); + } + holder.getNamedConfigurationBuilders().put(cache, builder); } } diff --git a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index 894eb771b52..a79e3a1acd5 100644 --- a/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/junit5/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -631,6 +631,14 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { } } + public void copyConfigFile(Path configFilePath) { + try { + Files.copy(configFilePath, distPath.resolve("conf").resolve(configFilePath.getFileName())); + } catch (IOException cause) { + throw new RuntimeException("Failed to copy config file [" + configFilePath + "] to 'conf' directory", cause); + } + } + private void updateProperties(Consumer propertiesConsumer, File propertiesFile) { Properties properties = new Properties(); diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/DistributionKeycloakServer.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/DistributionKeycloakServer.java index 5e3bd3fda1e..19833e7c64a 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/DistributionKeycloakServer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/DistributionKeycloakServer.java @@ -6,6 +6,7 @@ import org.keycloak.it.utils.OutputConsumer; import org.keycloak.it.utils.RawKeycloakDistribution; import org.keycloak.testframework.config.Config; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.regex.Matcher; @@ -40,6 +41,10 @@ public class DistributionKeycloakServer implements KeycloakServer { keycloak.copyProvider(dependency.getGroupId(), dependency.getArtifactId()); } + for (Path configFile : keycloakServerConfigBuilder.toConfigFiles()) { + keycloak.copyConfigFile(configFile); + } + keycloak.run(keycloakServerConfigBuilder.toArgs()); } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/EmbeddedKeycloakServer.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/EmbeddedKeycloakServer.java index e68345d9fb8..6bfb91a31fd 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/EmbeddedKeycloakServer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/EmbeddedKeycloakServer.java @@ -3,12 +3,18 @@ package org.keycloak.testframework.server; import io.quarkus.maven.dependency.Dependency; import org.keycloak.Keycloak; import org.keycloak.common.Version; +import org.keycloak.platform.Platform; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; import java.util.concurrent.TimeoutException; public class EmbeddedKeycloakServer implements KeycloakServer { private Keycloak keycloak; + private Path homeDir; @Override public void start(KeycloakServerConfigBuilder keycloakServerConfigBuilder) { @@ -18,6 +24,29 @@ public class EmbeddedKeycloakServer implements KeycloakServer { builder.addDependency(dependency.getGroupId(), dependency.getArtifactId(), ""); } + Set configFiles = keycloakServerConfigBuilder.toConfigFiles(); + if (!configFiles.isEmpty()) { + if (homeDir == null) { + homeDir = Platform.getPlatform().getTmpDirectory().toPath(); + } + + Path conf = homeDir.resolve("conf"); + + if (!conf.toFile().exists()) { + conf.toFile().mkdirs(); + } + + for (Path configFile : configFiles) { + try { + Files.copy(configFile, conf.resolve(configFile.getFileName())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + + builder.setHomeDir(homeDir); keycloak = builder.start(keycloakServerConfigBuilder.toArgs()); } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java index e73b078ae33..058cfd44127 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/KeycloakServerConfigBuilder.java @@ -6,12 +6,16 @@ import io.smallrye.config.SmallRyeConfig; import org.eclipse.microprofile.config.spi.ConfigSource; import org.keycloak.common.Profile; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -23,6 +27,7 @@ public class KeycloakServerConfigBuilder { private final Set featuresDisabled = new HashSet<>(); private final LogBuilder log = new LogBuilder(); private final Set dependencies = new HashSet<>(); + private final Set configFiles = new HashSet<>(); private KeycloakServerConfigBuilder(String command) { this.command = command; @@ -70,6 +75,18 @@ public class KeycloakServerConfigBuilder { return this; } + public KeycloakServerConfigBuilder cacheConfigFile(String resourcePath) { + try { + Path p = Paths.get(Objects.requireNonNull(getClass().getResource(resourcePath)).toURI()); + configFiles.add(p); + option("cache-config-file", p.getFileName().toString()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + return this; + } + public class LogBuilder { private Boolean color; @@ -161,7 +178,7 @@ public class KeycloakServerConfigBuilder { } } - public List toArgs() { + List toArgs() { log.build(); List args = new LinkedList<>(); @@ -179,10 +196,14 @@ public class KeycloakServerConfigBuilder { return args; } - public Set toDependencies() { + Set toDependencies() { return dependencies; } + Set toConfigFiles() { + return configFiles; + } + private Set toFeatureStrings(Profile.Feature... features) { return Arrays.stream(features).map(f -> { if (f.getVersion() > 1) { diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/server/RemoteKeycloakServer.java b/test-framework/core/src/main/java/org/keycloak/testframework/server/RemoteKeycloakServer.java index 8e7dd72130a..8d3d9fd6eab 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/server/RemoteKeycloakServer.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/server/RemoteKeycloakServer.java @@ -4,6 +4,7 @@ import io.quarkus.maven.dependency.Dependency; import java.net.ConnectException; import java.net.URL; +import java.nio.file.Path; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -52,6 +53,14 @@ public class RemoteKeycloakServer implements KeycloakServer { sb.append("\n"); } } + Set configFiles = keycloakServerConfigBuilder.toConfigFiles(); + if (!configFiles.isEmpty()) { + sb.append("Copy following config files to your conf directory:\n"); + for (Path c : configFiles) { + sb.append(c.toAbsolutePath()); + sb.append("\n"); + } + } System.out.println(sb); } diff --git a/tests/base/src/test/java/org/keycloak/tests/infinispan/InfinispanXMLBackwardCompatibilityTest.java b/tests/base/src/test/java/org/keycloak/tests/infinispan/InfinispanXMLBackwardCompatibilityTest.java new file mode 100644 index 00000000000..2ad970d9ca4 --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/infinispan/InfinispanXMLBackwardCompatibilityTest.java @@ -0,0 +1,34 @@ +package org.keycloak.tests.infinispan; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testframework.annotations.InjectRealm; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.server.KeycloakServerConfig; +import org.keycloak.testframework.server.KeycloakServerConfigBuilder; + +@KeycloakIntegrationTest(config = InfinispanXMLBackwardCompatibilityTest.ServerConfigWithCustomInfinispanXML.class) +public class InfinispanXMLBackwardCompatibilityTest { + + private static final String CONFIG_FILE = "/embedded-infinispan-config/infinispan-xml-kc26.xml"; + + @InjectRealm + ManagedRealm realm; + + @Test + void testKeycloakStartedSuccessfullyWithOlderInfinispanXML() { + RealmRepresentation representation = realm.admin().toRepresentation(); + Assertions.assertNotNull(representation); + } + + + public static class ServerConfigWithCustomInfinispanXML implements KeycloakServerConfig { + + @Override + public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) { + return config.cacheConfigFile(CONFIG_FILE); + } + } +} diff --git a/tests/base/src/test/resources/embedded-infinispan-config/infinispan-xml-kc26.xml b/tests/base/src/test/resources/embedded-infinispan-config/infinispan-xml-kc26.xml new file mode 100644 index 00000000000..377591d7154 --- /dev/null +++ b/tests/base/src/test/resources/embedded-infinispan-config/infinispan-xml-kc26.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file