How to save cached Keycloak data to a persistent data store? - jboss

We are running Keycloak (v4.4, standalone mode) inside of 2 Docker containers. We wish these containers to be stateless, so we must persist all cached data to a backing store (either database or other caching solution such as Redis). We can not allow cached data to only exist in-memory, because either of our containers may be destroyed at any time.
Ideally, we would like to persist cached data to our own Redis instance. Since Keycloak uses Infinispan, it seems like this is the way to configure Infinispan to use Redis: http://infinispan.org/docs/cachestores/redis/.
Naively, I tried to have Keycloak store session information in Redis by updating my standalone-4.4.0.xml file to look like this (notice the redis-store element on line 5):
<subsystem xmlns="urn:jboss:domain:infinispan:6.0">
<cache-container name="keycloak">
<local-cache name="sessions">
<persistence passivation="false">
<redis-store xmlns="urn:infinispan:config:store:redis:8.0"
topology="server" socket-timeout="10000" connection-timeout="10000">
<redis-server host="server1" />
<connection-pool min-idle="6" max-idle="10" max-total="20" min-evictable-idle-time="30000" time-between-eviction-runs="30000" />
</redis-store>
</persistence>
</local-cache>
<local-cache name="realms">
<object-memory size="10000"/>
</local-cache>
<local-cache name="users">
<object-memory size="10000"/>
</local-cache>
<local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="clientSessions"/>
<local-cache name="offlineClientSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="work"/>
<local-cache name="authorization">
<object-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<object-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<local-cache name="actionTokens">
<object-memory size="-1"/>
<expiration max-idle="-1" interval="300000"/>
</local-cache>
</cache-container>
<cache-container name="server" default-cache="default" module="org.wildfly.clustering.server">
<local-cache name="default">
<transaction mode="BATCH"/>
</local-cache>
</cache-container>
<cache-container name="web" default-cache="passivation" module="org.wildfly.clustering.web.infinispan">
<local-cache name="passivation">
<redis-store xmlns="urn:infinispan:config:store:redis:8.0"
topology="server" socket-timeout="10000" connection-timeout="10000">
<redis-server host="server1" />
<connection-pool min-idle="6" max-idle="10" max-total="20" min-evictable-idle-time="30000" time-between-eviction-runs="30000" />
</redis-store>
</persistence>
</local-cache>
</cache-container>
<cache-container name="ejb" aliases="sfsb" default-cache="passivation" module="org.wildfly.clustering.ejb.infinispan">
<local-cache name="passivation">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store passivation="true" purge="false"/>
</local-cache>
</cache-container>
<cache-container name="hibernate" module="org.infinispan.hibernate-cache">
<local-cache name="entity">
<transaction mode="NON_XA"/>
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="local-query">
<object-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="timestamps"/>
</cache-container>
</subsystem>
But when I start Keycloak, I get this error:
'persistence' isn't an allowed element here.
Question: Is there a straightforward way to configure Keycloak to save cached data in Redis or another persistent data store?

The subsystem version urn:jboss:domain:infinispan:6.0 doesn't know about this schema of your xml, so you would have to either update the subsystem or if you using the latest image of Keycloak (6.0.1) maybe would be easier to just implement a new InfinispanConnectionProviderFactory, which just involves basically doing this with Wildfly:
/subsystem=keycloak-server/spi=connectionsInfinispan/:remove()
/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=custom)
/subsystem=keycloak-server/spi=connectionsInfinispan/provider=custom/:add(properties={},enabled=true)
For that, of course, you would have to implement an extension and deploy it. But then at the code level, you can use the full power of the latest Infinispan.
I see that you want to use Redis, that is another big problem with it, please read this answer https://stackoverflow.com/a/57362238/571689 where I tell you the following problems that you will bump into.

Related

Configure Infinispan for Keycloak 17

I want to run Keycloak 17 (Quarkus Edition) in HA mode with the provided infinispan. Because we are running Keycloak on serveral stages, I want to specify a infinispan cluster name. As I understood from the documentation I should configure this in the given infinispan config xml ./conf/cache-ispn.xml
I altered
<transport lock-timeout="60000"/>
to
<transport cluster="myClusterName" lock-timeout="60000"/>
After that I ran .\kc.bat build --cache=ispn --cache-config-file=conf/cache-ispn.xml
and started up the server with .\kc.bat start
Sadly the output logging shows this:
[org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000078: Starting JGroups channel `ISPN`
[org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000094: Received new cluster view for channel ISPN: [MyHostName-14281|0] (1) [MyHostName-14281]
As seen in the logs, the cluster name still is the default "ISPN".
I already consulted the infinispan docs here: https://infinispan.org/docs/stable/titles/configuring/configuring.html
as well as the Keycloak docs:
https://www.keycloak.org/server/caching
https://www.keycloak.org/server/configuration
Can anyone help me out? Is this a bug related to Keycloak 17 or am I missing something in the infinispan config?
Full Infinispan Config:
<?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:11.0 http://www.infinispan.org/schemas/infinispan-config-11.0.xsd"
xmlns="urn:infinispan:config:11.0">
<cache-container name="keycloak">
<transport cluster="myClusterName" lock-timeout="60000"/>
<local-cache name="realms">
<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">
<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">
<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">
<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>
I figured it out:
First I copied the cache-ispn.xml to a new file in the same directory and named it cache.xml
I changed the build paramter --cache-config-file=conf/cache-ispn.xml to --cache-config-file=cache.xml
So I just removed the folder specification as Keycloak seems to auto set the config directory to the conf folder.

When does keycloak delete expired sessions?

I noticed that sessions that pass the SSO session idle and SSO session max aren't immediately deleted. They seem to be invalidated and therefor useless, but they are not getting immediately removed. I can view them in the sessions tab of the admin console.
Since I can't find an explanation for this, or how this mechanism works internally (didn't look into the code), I was wondering, if anyone could elaborate on what is going on? Is everything working as it should?
Keycloak relies heavily on Infinispan for caching. Many types of entities have dedicated caches configured directly to them, and sessions are not excluded.
When starting Keycloak, you specifiy a configuration file/operation mode ( via the -c parameter). For example, when I run my keycloak via docker I get the following command line:
java -D[Standalone] -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED -Dorg.jboss.boot.log.file=/opt/jboss/keycloak/standalone/log/server.log -Dlogging.configuration=file:/opt/jboss/keycloak/standalone/configuration/logging.properties -jar /opt/jboss/keycloak/jboss-modules.jar -mp /opt/jboss/keycloak/modules org.jboss.as.standalone -Djboss.home.dir=/opt/jboss/keycloak -Djboss.server.base.dir=/opt/jboss/keycloak/standalone -Djboss.bind.address=172.19.0.3 -Djboss.bind.address.private=172.19.0.3 -c=standalone-ha.xml -Dkeycloak.profile.feature.token_exchange=enabled -Dkeycloak.profile.feature.admin_fine_grained_authz=enabled
you can see -D[Standalone] (for the operation mode) and -c=standalone-ha.xml, which points to the configuration XML file.
In it, you can see a section in the likes of:
<subsystem xmlns="urn:jboss:domain:infinispan:11.0">
<cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
<local-cache name="realms">
<heap-memory size="10000"/>
</local-cache>
<local-cache name="users">
<heap-memory size="10000"/>
</local-cache>
<local-cache name="sessions"/>
<local-cache name="authenticationSessions"/>
<local-cache name="offlineSessions"/>
<local-cache name="clientSessions"/>
<local-cache name="offlineClientSessions"/>
<local-cache name="loginFailures"/>
<local-cache name="work"/>
<local-cache name="authorization">
<heap-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<heap-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<local-cache name="actionTokens">
<heap-memory size="-1"/>
<expiration interval="300000" max-idle="-1"/>
</local-cache>
</cache-container>
...
...
...
</subsystem>
You can try and tweak the various session caches expiration/lifespan attributes.
Have a look at Cache-Configuration section of the manual, and also on the xmlns infinispan-config specification

How to enable Wildfly 10 InMemorySessionManager?

I'm using Widlfy 10, but do not want to use the DistributableSessions that are used by Wildfly out of the box (I am having some session handling issues and need to debug things at a basic level). I see that Undertow has an InMemorySessionManager which I would rather use instead. But I haven't been able to figure out how to specify a different SessionManager.
I've tried to configure my Wildfly cache as a local cache:
<subsystem xmlns="urn:jboss:domain:infinispan:4.0">
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>
<replicated-cache name="default" mode="SYNC">
<transaction mode="BATCH"/>
</replicated-cache>
</cache-container>
<cache-container name="web" default-cache="passivation" module="org.wildfly.clustering.web.infinispan">
<local-cache name="passivation">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store passivation="true" purge="false"/>
</local-cache>
<local-cache name="persistent">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store passivation="false" purge="false"/>
</local-cache>
</cache-container>
...
...
However, in debugging my application, I still see that Wildfly is using the DistributableSessionManager and DistributableSessions instead.
Is there anyway to enable the Undertwo's InMemorySessionManager instead? Do I have to go through the effort of creating my own ServletExtension and Factory and configuring it in the META-INF/services/io.undertow.servlet.ServletExtension or is there an out-of-the-box way of enable functionality that already exists via the config file? Or do the required classes already exist as part of the Undertow/Wildfly packaging?
There are only conditions that result in the use of the distributed session manager:
in web.xml
Using shared sessions across web application within an ear, via shared-session-config.xml
Given that you've already stated that #1 is not the case, I'll assume #2. To disable the use of the distributed session manager for shared sessions, remove the org.wildfly.clustering.web.undertow module from your distribution.

Set infinispan entity cache eviction and expiration policies via cli console

I need to add some cli command to change my infinispan entity settings from
<invalidation-cache name="entity" mode="SYNC">
<transaction mode="NON_XA"/>
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</invalidation-cache>
to
<invalidation-cache name="entity" mode="ASYNC">
<transaction mode="NON_XA"/>
<eviction strategy="LRU" max-entries="10000"/>
<expiration lifespan="100000"/>
</invalidation-cache>
For now I have two commands that removing existing setting and creates a new one but without specify eviction and expiration policies.
/profile=full-ha/subsystem=infinispan/cache-container=hibernate/invalidation-cache=entity:remove
/profile=full-ha/subsystem=infinispan/cache-container=hibernate/invalidation-cache=entity:add(mode=ASYNC)
how can I specify eviction and expiration policies.
CLI command which changes only the attribute value:
/profile=full-ha/subsystem=infinispan/cache-container=hibernate/invalidation-cache=entity:write-attribute(name=mode, value=ASYNC)

Modeshape workspace creation

We are working with this platform:
JBoss 6.1.0.GA
Modeshape 3.6.0
I just need to create a new workspace and to put inside images,javascripts, and other files I need for a webapp we are developing.
I tried to connect via webdav to our modeshape repository and create a new test directory inside, but I always receive this exception:
2015-02-03 16:47 WARN [org.modeshape.web.jcr.webdav.ModeShapeWebdavStore] (http-/0.0.0.0:8021-1) Cannot obtain a session for the repository 'repository': The workspace test was not found
I looked on stackoverflow and on the official guide of modeshape, but I still cannot catch how to do this "easy" task.
It seems there's no documentation that explains how to manually create a new workspace in a repository.
I add the configurations from standalone.xml I'm using for cache:
<subsystem xmlns="urn:jboss:domain:infinispan:1.4">
<cache-container name="hibernate" default-cache="local-query" module="org.jboss.as.jpa.hibernate:4">
<local-cache name="entity">
<transaction mode="NON_XA"/>
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="local-query">
<transaction mode="NONE"/>
<eviction strategy="LRU" max-entries="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="timestamps">
<transaction mode="NONE"/>
<eviction strategy="NONE"/>
</local-cache>
</cache-container>
<cache-container name="modeshape" default-cache="repository" module="org.modeshape">
<local-cache name="repository">
<transaction mode="NON_XA"/>
<string-keyed-jdbc-store datasource="java:/jdbc/blablablaDatasource" shared="true" passivation="false" purge="false">
<property name="databaseType">
oracle
</property>
<property name="createTableOnStart">
true
</property>
<string-keyed-table prefix="CONTENT_REPO_STRING">
<id-column name="id_column" type="VARCHAR2(255)"/>
<data-column name="data_column" type="BLOB"/>
<timestamp-column name="timestamp_column" type="NUMBER(20)"/>
</string-keyed-table>
</string-keyed-jdbc-store>
</local-cache>
</cache-container>
<cache-container name="binary_cache_container" default-cache="binary_fs">
<local-cache name="binary_fs">
<transaction mode="NON_XA"/>
<string-keyed-jdbc-store datasource="java:/jdbc/blablablaDatasource" shared="true" preload="false" passivation="false" purge="false">
<write-behind flush-lock-timeout="1" modification-queue-size="1024" shutdown-timeout="25000" thread-pool-size="1"/>
<property name="databaseType">
oracle
</property>
<string-keyed-table prefix="CONTENT_REPO">
<id-column name="id_column" type="VARCHAR(255)"/>
<data-column name="data_column" type="BLOB"/>
<timestamp-column name="timestamp_column" type="NUMBER(20)"/>
</string-keyed-table>
</string-keyed-jdbc-store>
</local-cache>
<local-cache name="binary_fs_meta">
<transaction mode="NON_XA"/>
<string-keyed-jdbc-store datasource="java:/jdbc/blablablaDatasource" shared="true" preload="false" passivation="false" purge="false">
<write-behind flush-lock-timeout="1" modification-queue-size="1024" shutdown-timeout="25000" thread-pool-size="1"/>
<property name="databaseType">
oracle
</property>
<string-keyed-table prefix="CONTENT_REPO">
<id-column name="id_column" type="VARCHAR(255)"/>
<data-column name="data_column" type="BLOB"/>
<timestamp-column name="timestamp_column" type="NUMBER(20)"/>
</string-keyed-table>
</string-keyed-jdbc-store>
</local-cache>
</cache-container>
</subsystem>
and also for the modeshape conf:
<subsystem xmlns="urn:jboss:domain:modeshape:1.0">
<repository name="repository" security-domain="modeshape-internal-security">
<workspaces default-workspace="default" allow-workspace-creation="true">
<workspace name="ops">
<initial-content>
initial-content-default.xml
</initial-content>
</workspace>
<workspace name="other"/>
<workspace name="extra">
<initial-content>
initial-content-default.xml
</initial-content>
</workspace>
<workspace name="default"/>
</workspaces>
<indexing rebuild-upon-startup="ALWAYS"/>
<cache-binary-storage data-cache-name="binary_fs" metadata-cache-name="binary_fs_meta" cache-container="binary_cache_container"/>
<sequencers>
<sequencer name="fixed-width-text-sequencer" classname="org.modeshape.sequencer.text.FixedWidthTextSequencer" module="org.modeshape.sequencer.text" commentMarker="#" path-expression="/files(//*.txt[*])/jcr:content[#jcr:data] => /derived/text/fixedWidth/$1"/>
<sequencer name="xml-sequencer" classname="xml" module="org.modeshape.sequencer.xml" path-expression="/files(//)*.xml[*]/jcr:content[#jcr:data] => /derived/xml/$1"/>
<sequencer name="image-sequencer" classname="image" module="org.modeshape.sequencer.image" path-expression="/files(//*.(png|jpg|gif)[*])/jcr:content[#jcr:data] => /derived/image/$1"/>
</sequencers>
<text-extractors>
<text-extractor name="tika-extractor" classname="tika" module="org.modeshape.extractor.tika"/>
</text-extractors>
</repository>
</subsystem>
You can create a new workspace programmatically using the standard JCR API (see this StackOverflow question, but you can also define workspaces in the ModeShape configuration file.
Since you're deploying ModeShape to JBoss EAP, you can configure new workspaces in the ModeShape subsystem configuration within the installation's standalone-modeshape.xml file. Here's an example (that actually is in that configuration file) to define 3 workspaces named default, other, and extra upon startup, defines some initial content for the workspace named default, and it enables the programmatic creation of workspaces.
<repository name="artifacts">
<!-- ... -->
<!-- Define 3 workspaces to exist upon startup -->
<workspaces default-workspace="default" allow-workspace-creation="false">
<workspace name="default">
<initial-content>initial-content-default.xml</initial-content>
</workspace>
<workspace name="other"/>
<workspace name="extra"/>
</workspaces>
<!-- ... -->
<repository name="artifacts">
The structure of this XML fragment is dictated by the modeshape_1_0.xsd file in your EAP installation (or the modeshape_2_0.xsd file in Wildfly installations).
For those not deploying ModeShape in JBoss EAP (or Wildfly for ModeShape 4.x), you can do the same thing in ModeShape's JSON configuration file. For example, this defines exactly the same workspaces described above:
"workspaces" : {
"predefined" : ["other", "extra"],
"default" : "default",
"allowCreation" : true,
"initialContent" : {
"default" : "initial-content-default.xml"
}
},
See ModeShape's JSON schema for more details and options.
Also, be sure that when you log into a Session that you correctly specify the workspace name.
I managed to get it work only changing the configuration to this one:
JBoss 6.3.0.GA
Modeshape 3.8.1