keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java 10(+9 -1)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java 9(+8 -1)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java 7(+7 -0)
model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java 178(+178 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java
index 5513777..c1f8914 100644
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java
@@ -18,6 +18,7 @@
package org.keycloak.connections.infinispan;
import org.infinispan.Cache;
+import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.manager.EmbeddedCacheManager;
/**
@@ -26,11 +27,13 @@ import org.infinispan.manager.EmbeddedCacheManager;
public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
private final EmbeddedCacheManager cacheManager;
+ private final RemoteCacheProvider remoteCacheProvider;
private final String siteName;
private final String nodeName;
- public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager, String nodeName, String siteName) {
+ public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager, RemoteCacheProvider remoteCacheProvider, String nodeName, String siteName) {
this.cacheManager = cacheManager;
+ this.remoteCacheProvider = remoteCacheProvider;
this.nodeName = nodeName;
this.siteName = siteName;
}
@@ -41,6 +44,11 @@ public class DefaultInfinispanConnectionProvider implements InfinispanConnection
}
@Override
+ public <K, V> RemoteCache<K, V> getRemoteCache(String cacheName) {
+ return remoteCacheProvider.getRemoteCache(cacheName);
+ }
+
+ @Override
public String getNodeName() {
return nodeName;
}
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 7493cae..0053791 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
@@ -56,6 +56,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
protected EmbeddedCacheManager cacheManager;
+ protected RemoteCacheProvider remoteCacheProvider;
+
protected boolean containerManaged;
private String nodeName;
@@ -66,7 +68,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
public InfinispanConnectionProvider create(KeycloakSession session) {
lazyInit();
- return new DefaultInfinispanConnectionProvider(cacheManager, nodeName, siteName);
+ return new DefaultInfinispanConnectionProvider(cacheManager, remoteCacheProvider, nodeName, siteName);
}
@Override
@@ -74,6 +76,9 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
if (cacheManager != null && !containerManaged) {
cacheManager.stop();
}
+ if (remoteCacheProvider != null) {
+ remoteCacheProvider.stop();
+ }
cacheManager = null;
}
@@ -104,6 +109,8 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
}
logger.infof("Node name: %s, Site name: %s", nodeName, siteName);
+
+ remoteCacheProvider = new RemoteCacheProvider(config, cacheManager);
}
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
index 00f60a7..cb23b6f 100755
--- a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -18,6 +18,7 @@
package org.keycloak.connections.infinispan;
import org.infinispan.Cache;
+import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.provider.Provider;
/**
@@ -68,6 +69,12 @@ public interface InfinispanConnectionProvider extends Provider {
<K, V> Cache<K, V> getCache(String name);
/**
+ * Get remote cache of given name. Could just retrieve the remote cache from the remoteStore configured in given infinispan cache and/or
+ * alternatively return the secured remoteCache (remoteCache corresponding to secured hotrod endpoint)
+ */
+ <K, V> RemoteCache<K, V> getRemoteCache(String name);
+
+ /**
* @return Address of current node in cluster. In non-cluster environment, it returns some other non-null value (eg. hostname with some random value like "host-123456" )
*/
String getNodeName();
diff --git a/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java
new file mode 100644
index 0000000..ea1ba9f
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/connections/infinispan/RemoteCacheProvider.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2017 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.
+ */
+
+package org.keycloak.connections.infinispan;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.RealmCallback;
+
+import org.infinispan.client.hotrod.RemoteCache;
+import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.hotrod.configuration.Configuration;
+import org.infinispan.client.hotrod.configuration.ConfigurationBuilder;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.common.util.reflections.Reflections;
+import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
+
+/**
+ * Get either just remoteCache associated with remoteStore associated with infinispan cache of given name. If security is enabled, then
+ * return secured remoteCache based on the template provided by remoteStore configuration but with added "authentication" configuration
+ * of secured hotrod endpoint (RemoteStore doesn't yet allow to configure "security" of hotrod endpoints)
+ *
+ * TODO: Remove this class once we upgrade to infinispan version, which allows to configure security for remoteStore itself
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RemoteCacheProvider {
+
+ protected static final Logger logger = Logger.getLogger(RemoteCacheProvider.class);
+
+ private final Config.Scope config;
+ private final EmbeddedCacheManager cacheManager;
+
+ private final Map<String, RemoteCache> availableCaches = new HashMap<>();
+
+ // Enlist secured managers, which are managed by us and should be shutdown on stop
+ private final List<RemoteCacheManager> managedManagers = new LinkedList<>();
+
+ public RemoteCacheProvider(Config.Scope config, EmbeddedCacheManager cacheManager) {
+ this.config = config;
+ this.cacheManager = cacheManager;
+ }
+
+ public RemoteCache getRemoteCache(String cacheName) {
+ if (availableCaches.get(cacheName) == null) {
+ synchronized (this) {
+ if (availableCaches.get(cacheName) == null) {
+ RemoteCache remoteCache = loadRemoteCache(cacheName);
+ availableCaches.put(cacheName, remoteCache);
+ }
+ }
+ }
+
+ return availableCaches.get(cacheName);
+ }
+
+ public void stop() {
+ // TODO:mposolda
+ logger.infof("Shutdown %d registered secured remoteCache managers", managedManagers.size());
+
+ for (RemoteCacheManager mgr : managedManagers) {
+ mgr.stop();
+ }
+ }
+
+
+ protected RemoteCache loadRemoteCache(String cacheName) {
+ RemoteCache remoteCache = InfinispanUtil.getRemoteCache(cacheManager.getCache(cacheName));
+
+ if (config.getBoolean("remoteStoreSecurityEnabled", false)) {
+ // TODO:mposolda
+ logger.info("Remote store security is enabled");
+ RemoteCacheManager securedMgr = createSecuredRemoteCacheManager(config, remoteCache.getRemoteCacheManager());
+ managedManagers.add(securedMgr);
+ return securedMgr.getCache(remoteCache.getName());
+ } else {
+ // TODO:mposolda
+ logger.info("Remote store security is disabled");
+ return remoteCache;
+ }
+ }
+
+
+ protected RemoteCacheManager createSecuredRemoteCacheManager(Config.Scope config, RemoteCacheManager origManager) {
+ String serverName = config.get("remoteStoreSecurityServerName", "keycloak-server");
+ String realm = config.get("remoteStoreSecurityRealm", "ApplicationRealm");
+
+ String securedHotRodEndpoint = config.get("remoteStoreSecurityHotRodEndpoint");
+ String username = config.get("remoteStoreSecurityUsername");
+ String password = config.get("remoteStoreSecurityPassword");
+
+ // TODO:mposolda
+ logger.infof("Server: '%s', Realm: '%s', Username: '%s', Secured HotRod endpoint: '%s'", serverName, realm, username, securedHotRodEndpoint);
+
+ // Create configuration template from the original configuration provided at remoteStore level
+ Configuration origConfig = origManager.getConfiguration();
+
+ ConfigurationBuilder cfgBuilder = new ConfigurationBuilder()
+ .read(origConfig);
+
+ // Workaround as I need a way to override servers and it's not possible to remove existing :/
+ try {
+ Field serversField = cfgBuilder.getClass().getDeclaredField("servers");
+ Reflections.setAccessible(serversField);
+ List origServers = Reflections.getFieldValue(serversField, cfgBuilder, List.class);
+ origServers.clear();
+ } catch (NoSuchFieldException nsfe) {
+ throw new RuntimeException(nsfe);
+ }
+
+ // Create configuration based on the configuration template from remoteStore. Just add security and override secured endpoint
+ Configuration newConfig = cfgBuilder
+ .addServers(securedHotRodEndpoint)
+ .security()
+ .authentication()
+ .serverName(serverName) //define server name, should be specified in XML configuration on JDG side
+ .saslMechanism("DIGEST-MD5") // define SASL mechanism, in this example we use DIGEST with MD5 hash
+ .callbackHandler(new LoginHandler(username, password.toCharArray(), realm)) // define login handler, implementation defined
+ .enable()
+ .build();
+
+ return new RemoteCacheManager(newConfig);
+ }
+
+
+ private static class LoginHandler implements CallbackHandler {
+ final private String login;
+ final private char[] password;
+ final private String realm;
+
+ private LoginHandler(String login, char[] password, String realm) {
+ this.login = login;
+ this.password = password;
+ this.realm = realm;
+ }
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback) {
+ ((NameCallback) callback).setName(login);
+ } else if (callback instanceof PasswordCallback) {
+ ((PasswordCallback) callback).setPassword(password);
+ } else if (callback instanceof RealmCallback) {
+ ((RealmCallback) callback).setText(realm);
+ } else {
+ throw new UnsupportedCallbackException(callback);
+ }
+ }
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java
index 4ecd445..ba13171 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/remotestore/RemoteCacheSessionsLoader.java
@@ -32,7 +32,6 @@ import org.keycloak.models.sessions.infinispan.changes.SessionEntityWrapper;
import org.keycloak.models.sessions.infinispan.initializer.BaseCacheInitializer;
import org.keycloak.models.sessions.infinispan.initializer.OfflinePersistentUserSessionLoader;
import org.keycloak.models.sessions.infinispan.initializer.SessionLoader;
-import org.keycloak.models.sessions.infinispan.util.InfinispanUtil;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -87,7 +86,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
@Override
public void init(KeycloakSession session) {
- RemoteCache remoteCache = InfinispanUtil.getRemoteCache(getCache(session));
+ RemoteCache remoteCache = getRemoteCache(session);
RemoteCache<String, String> scriptCache = remoteCache.getRemoteCacheManager().getCache("___script_cache");
@@ -100,7 +99,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
@Override
public int getSessionsCount(KeycloakSession session) {
- RemoteCache remoteCache = InfinispanUtil.getRemoteCache(getCache(session));
+ RemoteCache remoteCache = getRemoteCache(session);
return remoteCache.size();
}
@@ -109,7 +108,7 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
Cache cache = getCache(session);
Cache decoratedCache = cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.SKIP_CACHE_STORE, Flag.IGNORE_RETURN_VALUES);
- RemoteCache<?, ?> remoteCache = InfinispanUtil.getRemoteCache(cache);
+ RemoteCache<?, ?> remoteCache = getRemoteCache(session);
log.debugf("Will do bulk load of sessions from remote cache '%s' . First: %d, max: %d", cache.getName(), first, max);
@@ -167,4 +166,10 @@ public class RemoteCacheSessionsLoader implements SessionLoader {
public void afterAllSessionsLoaded(BaseCacheInitializer initializer) {
}
+
+ // Get remoteCache, which may be secured
+ private RemoteCache getRemoteCache(KeycloakSession session) {
+ InfinispanConnectionProvider ispn = session.getProvider(InfinispanConnectionProvider.class);
+ return ispn.getRemoteCache(cacheName);
+ }
}
diff --git a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
index 12ffb33..eb58977 100755
--- a/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
+++ b/testsuite/utils/src/main/resources/META-INF/keycloak-server.json
@@ -94,7 +94,13 @@
"l1Lifespan": "${keycloak.connectionsInfinispan.l1Lifespan:600000}",
"remoteStoreEnabled": "${keycloak.connectionsInfinispan.remoteStoreEnabled:false}",
"remoteStoreHost": "${keycloak.connectionsInfinispan.remoteStoreServer:localhost}",
- "remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}"
+ "remoteStorePort": "${keycloak.connectionsInfinispan.remoteStorePort:11222}",
+ "remoteStoreSecurityEnabled": "${keycloak.connectionsInfinispan.remoteStoreSecurityEnabled:false}",
+ "remoteStoreSecurityServerName": "${keycloak.connectionsInfinispan.remoteStoreSecurityServerName:keycloak-server}",
+ "remoteStoreSecurityRealm": "${keycloak.connectionsInfinispan.remoteStoreSecurityRealm:ApplicationRealm}",
+ "remoteStoreSecurityHotRodEndpoint": "${keycloak.connectionsInfinispan.remoteStoreSecurityHotRodEndpoint}",
+ "remoteStoreSecurityUsername": "${keycloak.connectionsInfinispan.remoteStoreSecurityUsername}",
+ "remoteStoreSecurityPassword": "${keycloak.connectionsInfinispan.remoteStoreSecurityPassword}"
}
},