Keycloak on Distributed SQL (CockroachDB) - Part 2/2

Keycloak on Distributed SQL (CockroachDB) - Part 2/2

In part one of this post, we configured a single Keycloak node to store it's data in a resilient and available CockroachDB. In this post we cover how to add more nodes to the cluster and ensure that the keycloak nodes are able to register themselves on CockroachDB which, then allows Keycloak to share cache information.

Target Architecture

For this implementation, the target architecture will be as shown below:


In this post we will be adding regions two (us-east-1) and three (eu-west-2)

Adding a node for Resilience and Availability

From the previous post, we now have a single keycloak node connecting to a load balanced distributed CockroachDB. However, in this configuration it is not resilient, to resolve this we will now add nodes in two more regions, each one connecting to local CockroachDB nodes which are part of the existing Cockroach cluster.

As AWS does not allow multicast between EC2 nodes, we will need to use a different method to allow Keycloak to discover other nodes and share data between them. We will do this first with TCPPING and then use the Cockroach DB.

Stop Keycloak and copy the conf/cache-ispn.xml file to a new file which for this example we called conf/cache-ispn-tcpping.xml. We then edit the conf/cache-ispn-tcpping.xml file to include the following configuration(note, this will require different IP addresses in other installations):

<!-- custom stack goes into the jgroups element -->
<jgroups>
	<stack name="tcpping" extends="tcp">
	<TCP bind_port="7800"/>
	<TCPPING
initial_hosts="10.15.2.27[7800],10.15.0.25[7800],10.15.1.27[7800]"
	port_range="0"
stack.combine="REPLACE"
stack.position="MPING"
	/>
	</stack>
</jgroups>
<cache-container name="keycloak">
	<transport lock-timeout="60000" stack="tcpping"/>

Note that the <transport> tag now has an additional value stack=”tcpping”.

We now edit the cache settings in the conf/keycloak.conf file by including the following lines:

# cache
cache=ispn
cache-config-file=cache-ispn-tcpping.xml

Again we start the Keycloak server and we can see from the output that the JGroups channel is being started with the “tcpping” stack:

ubuntu@keycloak01: —Ikeycloak-20.O.3 
2023—02—17 21 : 02 : 04, 014 INFO [io.quarkus. deployment. QuarkusAugmentor] (main) Quarkus augmentation completed 
Server configuration updated and persisted. Run the following command to review the configuration: 
kc.sh show—config 
Next time you run the server, just run: 
kc.sh start —-optimized 
2023—02—17 21 : 02 INFO [org.keyctoak.quarkus. runtime. hostname. DefaultHostnameProvider] (main) Hostname 
name: keycIoak01, Strict HTTPS: true, Path: <request>, Strict BackChannet: false, Admin URL: <unset>, Admin: 
e 
2023-02-17 WARN [org. 
moval 
2023-02-17 WARN [org. 
at state enabled 
2023-02-17 INFO [org. 
alti ng. core . JBossUserMarshatIer 
2023-02-17 INFO [org. 
3.0.10.Fina1 
2023-02-17 ,944 INFO [org. 
2023-02-17 INFO [org. 
reating cluster as coordi nator 
2023-02-17 INFO [org. 
1-3547610] (1) [keyctoak01-35476] 
2023-02-17 INFO [org. 
infinispan. PERSISTENCE] ISPN000554: jboss—marshattin 
infinispan.CONFIG] Unable to persist Inf 
infinispan.CONTAINER] ISPN000556: Starting user mars 
infinispan.CONTAINER] ISPN000128: Infinispan version 
infinispan.CLUSTER] ISPN000078: Starting JGroups cha 
jgroups.protocots . pbcast.GMS] no m 
infinispan.CLUSTER] Received new cluster 
infinispan.CLUSTER] ISPN000079: Channel 'ISPN' local 
ysicat addresses are 
2023—02—17 21 : 02 : 10,632 INFO [org.keyctoak.connections.infinispan.DefauttInfinispanConnectionProviderFactory 
76, Site name: null

We now need to start adding more nodes to confirm they are communicating and sharing cache information.

In another region (AWS us-east-1 in this example) create a new Keycloak node:

  • Install openssl
  • Install java
  • Install keycloak
  • Set UDP buffers
  • Generate the SSL certificates for the new host
  • Copy the conf directory from the 1st node to the second node
  • Edit the conf/keycloak.conf file to change the db-url value to the local load balancer for the database.
  • Edit the conf/keycloak.conf file to change the hostname value to the new node.

We can then start the second node:

ubuntu@keycloak02: -Ikeycloak-20.O.3 
Changes detected in confi guration. Updating the server image. 
Updating the configuration and installing your custom providers, if any. Please wait. 
2023—02—17 21:44:38, 728 INFO [io.quarkus.depIoyment.QuarkusAugmentor] (main) Quarkus augmentation completed 
Server configuration updated and persisted. Run the following command to review the configuration: 
kc.sh show—config 
Next time you run the server, just run: 
kc.sh start ——optimi zed 
2023-02-17 21:44: 
name: keyc10ak02, 
2023-02-17 21:44: 
movat 
2023—02—17 21 : 44. 
al state enabled 
2023-02-17 21:44. 
40 , 387 INFO [org.keyctoak.quarkus. runtime . hostname. DefauttHostnameProvider] (main) Hostname 
Strict HTTPS: true, Path: <request>, Strict BackChannet: false, Admin URL: <unset>, Admin: 
42,711 WARN [org. 
•42,751 WARN [org. 
•42,764 INFO [org. 
alti ng. core. JBossUserMarsha1-Ier 
2023-02-17 INFO [org. 
3.0.10. Final 
2023-02-17 INFO [org. 
2023-02-17 INFO [org. 
infinispan. PERSISTENCE] jboss—marshatlin 
infinispan.CONFIG] ISPN000569: Unable to persist Inf 
infinispan.CONTAINER] ISPN000556: Starting user mars 
infinispan.CONTAINER] ISPN000128: Infinispan version 
infinispan.CLUSTER] ISPN000078: Starting JGroups cha 
infinispan.CLUSTER] Received new cluster 
1-3547611] (2) [keyctoak01-35476, keyctoak02-45710] 
2023-02-17 INFO [org.infinispan.CLUSTER] (keycloak-cache-init) ISPN000079: Channel 'ISPN' local 
ysicat addresses are 
2023—02—17 21 INFO [org.keyctoak.broker.provider.AbstractldentityProviderMapper] (main) Registerin 
er. mappersync. Confi gSyncEventLi stener

From the startup messages we can see that both nodes, keycloak01 and keycloak02, are referenced in the logs.

We can now log in once on host keycloak01 and again on host keycloak02 as one of the users we created earlier. When logged in as Admin on either node we can see both of the sessions:

ajs-GUl-Bastion 
_J KeycloakAdministrationConsole x L Z K%doakAccountMargement x I Z KeycloakAccountManagement x + 
Not secure I 
ac 
new-realm 
Manage 
Clients 
Client scopes 
Realm roles 
Users 
Groups 
Sessions 
Events 
Configure 
Realm settings 
Authentication 
Identity providers 
Users > User details 
user.kcl 
Details Attributes 
Q Search session 
Started 
2/17/2023, PM 
2/17/2023, PM 
Credentials 
Role mapping 
Logout all sessions 
Last access 
2/17/2023, 94901 PM 
2/17/2023, PM 
Groups 
Consents 
IP address 
10150.10 
Identity provider links 
Clients 
account—console 
Sessior 
security-admin—console

Making the Keycloak nodes self-register and add a new region

We now have the 2 keycloak nodes using the Infinispan cluster, but the config has the IPs of the hosts hard-coded in the configuration file. This is not particularly flexible in the long run for operations so the final step is to use the CockroachDB for the keycloak nodes to register themselves to avoid this hard-coding.

Stop all Keycloak servers and on the first node with postgres installed, copy the conf/cache-ispn-tpcping.xml to conf/cache-ispn-jdbcping.xml and edit to replace the jgroups stack and <transport> tag as shown:.

<!-- custom stack goes into the jgroups element -->
<jgroups>
	<stack name="jdbcping" extends="tcp">
	<JDBC_PING connection_driver="org.postgresql.Driver"
connection_username="keycloak"
connection_password="keycloak"
connection_url="jdbc:postgresql://localhost:5432/jgroups"
	initialize_sql="CREATE TABLE IF NOT EXISTS JGROUPSPING (own_addr varchar(200) NOT NULL, bind_addr VARCHAR(200) NOT NULL, created timestamp NOT NULL, cluster_name varchar(200) NOT NULL, ping_data BYTEA, constraint PK_JGROUPSPING PRIMARY KEY (own_addr, cluster_name));"
	insert_single_sql="INSERT INTO JGROUPSPING (own_addr, bind_addr, created, cluster_name, ping_data) values (?,'${jboss.bind.address:127.0.0.1}',NOW(), ?, ?);"
	delete_single_sql="DELETE FROM JGROUPSPING WHERE own_addr=? AND cluster_name=?;"
select_all_pingdata_sql="SELECT ping_data FROM JGROUPSPING WHERE cluster_name=?;"
info_writer_sleep_time="500"
remove_all_data_on_view_change="true"
stack.combine="REPLACE"
	stack.position="MPING" />
	</stack>
</jgroups>
<cache-container name="keycloak">
	<transport lock-timeout="60000" stack="jdbcping"/>

We now change the conf/keycloak.conf to point to the new configuration

cache-config-file=cache-ispn-jdbcping.xml

Build and start the Keycloak server on node 1:

ubuntu@keycIoak01: —Ikeycloak-20.O.3 
./start—kc . sh 
tail —f keycloak. log 
2023—02—17 22: 14:46, 187 INFO [org.keyctoak.quarkus. runtime . hostname . DefaultHostnameProviderJ (main) Hostname 
name: keyctoak01, Strict HTTPS: true, Path: <request>, Strict BackChannet: false, Admin URL: <unset>, Admin: < 
e 
2023-02-17 WARN [org. 
movat 
2023-02-17 WARN [org. 
at state enabled 
2023-02-17 INFO [org. 
al-Ii ng. core. JBossUserMarshaIIer 
2023-02-17 INFO [org. 
3.0.10. Final 
2023-02-17 INFO [org. 
2023-02-17 INFO [org. 
ating cluster as coordinator 
2023-02-17 22:14:49, 125 INFO [org. 
1-2120010] (1) [keyc10ak01-21200] 
2023-02-17 INFO [org. 
infinispan. PERSISTENCE] jboss—marshatting 
infinispan. CONFIG] ISPN000569: Unable to persist Infi 
infinispan. CONTAINER] ISPNO@0556: Starting user marsh 
infinispan. CONTAINER] Infinispan version: 
infinispan. CLUSTER] ISPN000078: Starting JGroups chan 
j groups. protocols.pbcast.GMS] no me 
infinispan. CLUSTER] ISPN000094: Received new cluster 
infinispan. CLUSTER] ISPN000079: Channel 'ISPNS local 
ysical addresses are [10.15.2.27:7800] 
2023—02—17 22: 14:49 , 658 INFO [org.keyctoak.connections.infinispan.DefauttInfinispanConnectionProviderFactory] 
00, Site name: null 
2023—02—17 22: 14:49 , 663 INFO [org. keycIoak.broker.provider.AbstractIdentityProviderMapper] (main) Registering 
er . mappersync. Confi gSyncEventLi stener 
2023-02-17 22:14:55, 212 INFO [io.quarkus] (main) Keyctoak 20.0.3 on JVM (powered by Quarkus 2.13.6. Final) Sta 
tps://O.o.o.o: 8443 
2023—02—17 22: 14:55, 213 INFO [io. quarkus] (main) Profile prod activated. 
2023—02—17 22: 14:55, 213 INFO [io.quarkus] (main) Installed features: [agroat, cdi, hibernate—orm, jdbc—h2, jd 
I, jdbc—oracle, jdbc—postgresqt, keyctoak, togging—gel f, narayana—jta, reactive—routes, resteasy, resteasy—jac 
n, smattrye—heatth, smaltrye—metrics, vault, vertx]

Again, from the startup messages we can see that Keycloak is now using the “jdbcping” stack. Now to check data is in the jgroups db in postgres

ubuntu@keycIoak01: —Ikeycloak-20.O.3 
psqt 
——host=tocathost —d j groups —U keyctoak 
Password for user keyctoak: 
psqt (14.6 (Ubuntu 14.6—0ubuntuO.22.04.1)) 
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: 
Type "help" for help. 
off) 
j groups 
Schema 
public 
(I row) 
List of relations 
Name 
I Type I Owner 
j groupsping I table I keyctoak 
1
own _ addr 
_ data 
c8e41d14-978d-4016-bb20-6aa13c7952c9 1 
bind _ addr 
127.0.0.1 
ubuntu@keycIoak01: —IkeycIoak-20.O.3 
I cluster _ name I 
created 
2023 
-02-17 22:14:51.170426 | ISPN 
I \x02bb206aa13c 
579636c6f616b30312d323132303010040a0f021b1e78ffff 
(1 row) 
(END)

Again, we are using the postgres database to create the data structure as this cannot be done directly in CockroachDB. Once we have the data in postgres we need to stop keycloak and extract the data from the postgres database and import it into CockroachDB.

pg_dump --host=localhost -U keycloak -d jgroups > jgroups.dmp
SET 
= heap; 
public; 
Owner : 
ubuntu@keycIoak01: —Ikeycloak-20.O.3 
keyctoak 
—— Name: jgroupsping; Type: TABLE; Schema: 
CREATE TABLE public.jgroupsping ( 
own _ addr character varying(200) NOT NULL, 
bind _ addr character varyi NOT NULL, 
created timestamp without time zone NOT NULL, 
cluster _ name character varying(200) NOT NULL, 
ping_data bytea 
ALTER TABLE public.jgroupsping OWNER TO keyctoak; 
— Data for Name: jgroupsping; Type: TABLE DATA; Schema: public; Owner: keyctoak 
COPY public.jgroupsping (own _ addr, bind _ addr, created, 
cluster _ name, ping_data) FROM stdin; 
—— Name: jgroupsping pk_jgroupsping; Type: 
ALTER TABLE ONLY public.jgroupsping 
CONSTRAINT ; 
Schema : 
public; 
Owner : 
keyctoak

We created the CockroachDB user and database for jgroups earlier in the process, so we can proceed direxctly to populate database using our sql client:

cockroach sql --host=ajs-db01:26257 --certs-dir=certs -u keycloak -d jgroups -f ../new_kc_dump/jgroups.dmp

We confirm the database has been created in the Cockroach cluster:

ubuntu@ajs-bastion: —Icockroach 
cockroach sqI 
--host=aj s-db01:26257 
—-certs-di r=certs 
# Welcome to the CockroachDB SQL shell. 
# All statements must be terminated by a semicolon. 
# To exit, type: 
Connecting to server as user "keyctoak" . 
Enter password: 
—u keycloak 
—d j groups 
# Server version: CockroachDB CCL v22.2.2 built 2023/01/04 17:23:00, gol.19.1) (same ver 
# Cluster ID: 130db927-c130-47c4-9d69-cf14b36ca70d 
# Organization: Adam. Spindler 
No entry for terminal type "xterm—256cotor" • 
using dumb terminal settings. 
# Enter \ ? for a brief introduction. 
keycloak@aj : 26257/ j groups> \d 
schema _ name I table _ name I type I owner 
I estimated_row_count I locality 
public 
(1 row) 
I jgroupsping I table I keyctoak I 
o I NULL 
(execution 304ms / network Oms) 
Time: 304ms total 
keycloak@aj : 26257/jgroups> select * from jgroupsping; 
own _ addr I bind _ addr I created I cluster _ name I ping_data 
(O rows)

Now we have the data in the CockroachDB cluster we can re-point to cockroachdb by editing the conf/cache-ispn-jcdbping.xml file and replacing the postgres connection string with the appropriate CockroachDB connection string:

connection_url="jdbc:postgresql://ajs-db-demo-int-1b85bcdea0b73e74.elb.us-west-1.amazonaws.com:26257/jgroups?sslmode=require&amp;sslrootcert=${kc.home.dir}/crdb_certs/ca.crt"

Note the database url string has now has the database value changed to “jgroups” which is different to the “keycloak” database in the conf/keycloak.conf file.

Build and start keycloak sever again and confirm the data is being put into Cockroach jgroups database:

ubuntu@ajs-bastion: —Icockroach 
cockroach sqt 
——host=aj s—db01 : 26257 
——certs—di r=certs 
# Welcome to the CockroachDB SQL shell. 
# Alt statements must be terminated by a semicolon. 
# To exit, type: \q. 
Connecting to server as user "keyctoak" . 
Enter password: 
—u keyctoak 
—d j groups 
# Server version: CockroachDB CCL v22.2.2 built 2023/01/04 17:23:00, gol.19.I) (same ver 
# Cluster ID: 130db927-c130-47c4-9d69-cf14b36ca70d 
# Organization: Adam. Spindler 
No entry for terminal type "xterm—256cotor" • 
using dumb terminal settings. 
# Enter \ ? for a brief introduction. 
keycloak@aj s—db01 : 26257/ j groups> \d 
schema _ name I table _ name I type I owner 
I estimated_row_count I locality 
public 
(I row) 
I jgroupsping I table I keyctoak I 
Time: 171ms total (execution 171ms / network Oms) 
: 26257/jgroups> select * from jgroupsping; 
o I NULL 
created 
own _ addr 
g_data 
I bind _ addr I 
I cluster _ name I

We now copy the conf/cache-ispn-jdbcping.xml to the second node and edit it to include the load balancer for the local CockroachDB servers in the connection_url value.

connection_url="jdbc:postgresql://ajs-lb-int-688c72c587b28502.elb.us-east-1.amazonaws.com:26257/keycloak?sslmode=require&amp;sslrootcert=${kc.home.dir}/crdb_certs/ca.crt"

Now change the conf/keycloak.conf on node 2 to point to the new cache-ispn-jdbcping.xml file.

cache-config-file=cache-ispn-jdbcping.xml

Again we build and start keycloak on the second node to check both nodes are registered in cockroach.

ubuntu@ajs-bastion: —Icockroach 
——certs—di r=certs 
cockroach sqt 
——host=aj s—db01 : 26257 
# Welcome to the CockroachDB SQL shell. 
# Alt statements must be terminated by a semicolon. 
# To exit, type: \q. 
Connecting to server as user "keyctoak" . 
Enter password: 
# Server version: CockroachDB CCL v22.2.2 built 
—u keyctoak 
j groups 
(same ver 
# Cluster ID: 130db927-c130-47c4-9d69-cf14b36ca70d 
# Organization: Adam. Spindler 
No entry for terminal type "xterm—256cotor" • 
using dumb terminal settings. 
# Enter \ ? for a brief introduction. 
: 26257/jgroups> select * from jgroupsping; 
own _ addr 
g_data 
I bind _ addr 
2023/01/04 gol.19.1) 
cluster _ name I 
created 
2023-02-17 
22:56: 
2023-02-17 
4081e17f-1422-4aac-b42f-b77767a95bdO | 127.0.0.1 
6579636c6f616b30322d323830363910040a0f011b1e78ffff 
81788037-e65f-484d-b1dd-8a33410e57ae | 127.0.0.1 
6579636c6f616b30312d343638363210040aOf021b1e78ffff 
(2 rows) 
Time: Ims total (execution Ims / network Oms) 
17.448471 
22 : 55 : 37.242025 
ISPN 
ISPN 
I \x02b42fb7776 
I \x02b1dd8a334

Now we have both nodes using CockroachDB to register themselves there is no need for hard-coding of host IPs in configuration files, so we can add new nodes simply.

  • Install java
  • Set buffers
  • Install keycloak
  • Create open ssl certificates
  • Copy cockroachdb ca.crt across
  • Copy config files across
  • Change hostname and load balancer in conf/keycloak.conf
  • Change load balancer in conf/cache-ispn-jdbcping.xml
  • Build keycloak
  • Start keycloak

Confirm all nodes registered in cockroach and users created in one node are visible to the other nodes.

ubuntu@ajs-bastion: —Icockroach 
——certs—di r=certs 
cockroach sqt 
——host=aj s—db01 : 26257 
# Welcome to the CockroachDB SQL shell. 
# Alt statements must be terminated by a semicolon. 
# To exit, type: \q. 
Connecting to server as user "keyctoak" . 
Enter password: 
# Server version: CockroachDB CCL v22.2.2 built 
—u keyctoak 
j groups 
(same ver 
# Cluster ID: 130db927-c130-47c4-9d69-cf14b36ca70d 
# Organization: Adam. Spindler 
No entry for terminal type "xterm—256cotor" • 
using dumb terminal settings. 
# Enter \ ? for a brief introduction. 
: 26257/jgroups> select * from jgroupsping; 
own _ addr 
pi ng_data 
I bind _ addr 
created 
2023/01/04 gol.19.1) 
cluster _ name I 
4081e17f-1422-4aac-b42f-b77767a95bdO | 127.0.0.1 
6579636c6f616b30322d323830363910040a0f011b1e78ffff 
81788037-e65f-484d-b1dd-8a33410e57ae | 127.0.0.1 
6579636c6f616b30312d343638363210040aOf021b1e78ffff 
e89f4a06-8ece-4037-87cd-c670ab6572f7 | 127.0.0.1 
(3 rows) 
2023-02-17 
2023-02-17 
2023-02-17 
22:58:02.206324 
22 : 58 : 02.786036 
ISPN 
ISPN 
ISPN 
I \x02b42fb7776 
I \x02b1dd8a334 
I \x0287cdc670a

We now have a 3-node keycloak cluster in 3 regions, connecting to a CockroachDB distributed cluster also across 3 regions, providing resilience against failures in any AWS region.