mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Add default configuration for cache 'crl'
Fixes #36752 Signed-off-by: Pedro Ruivo <pruivo@redhat.com> Signed-off-by: Michal Hajas <mhajas@redhat.com> Signed-off-by: Pedro Ruivo <pruivo@users.noreply.github.com> Co-authored-by: Michal Hajas <mhajas@redhat.com> Co-authored-by: Stian Thorgersen <stian@redhat.com>
This commit is contained in:
parent
d522fd265d
commit
beb20dc425
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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<String, Supplier<ConfigurationBuilder>> DEFAULT_CONFIGS = Map.of(
|
||||
CRL_CACHE_NAME, InfinispanUtil::getCrlCacheConfig
|
||||
);
|
||||
private static final Supplier<ConfigurationBuilder> TO_NULL = () -> null;
|
||||
|
||||
private final CompletableFuture<EmbeddedCacheManager> cacheManagerFuture;
|
||||
private final CompletableFuture<RemoteCacheManager> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<Properties> propertiesConsumer, File propertiesFile) {
|
||||
Properties properties = new Properties();
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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<Path> 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());
|
||||
}
|
||||
|
||||
|
||||
@ -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<String> featuresDisabled = new HashSet<>();
|
||||
private final LogBuilder log = new LogBuilder();
|
||||
private final Set<Dependency> dependencies = new HashSet<>();
|
||||
private final Set<Path> 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<String> toArgs() {
|
||||
List<String> toArgs() {
|
||||
log.build();
|
||||
|
||||
List<String> args = new LinkedList<>();
|
||||
@ -179,10 +196,14 @@ public class KeycloakServerConfigBuilder {
|
||||
return args;
|
||||
}
|
||||
|
||||
public Set<Dependency> toDependencies() {
|
||||
Set<Dependency> toDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
Set<Path> toConfigFiles() {
|
||||
return configFiles;
|
||||
}
|
||||
|
||||
private Set<String> toFeatureStrings(Profile.Feature... features) {
|
||||
return Arrays.stream(features).map(f -> {
|
||||
if (f.getVersion() > 1) {
|
||||
|
||||
@ -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<Path> 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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2019 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<infinispan
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="urn:infinispan:config:15.0 http://www.infinispan.org/schemas/infinispan-config-15.0.xsd"
|
||||
xmlns="urn:infinispan:config:15.0">
|
||||
|
||||
<cache-container name="keycloak">
|
||||
<transport lock-timeout="60000"/>
|
||||
<local-cache name="realms" simple-cache="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users" simple-cache="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<distributed-cache name="sessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="authenticationSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="clientSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineClientSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="loginFailures" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<local-cache name="authorization" simple-cache="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<replicated-cache name="work">
|
||||
<expiration lifespan="-1"/>
|
||||
</replicated-cache>
|
||||
<local-cache name="keys" simple-cache="true">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="3600000"/>
|
||||
<memory max-count="1000"/>
|
||||
</local-cache>
|
||||
<distributed-cache name="actionTokens" owners="2">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="-1" lifespan="-1" interval="300000"/>
|
||||
<memory max-count="-1"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
||||
Loading…
x
Reference in New Issue
Block a user