Skip to content

Commit

Permalink
Create CA certificate for JGroups encryption
Browse files Browse the repository at this point in the history
Closes keycloak#36750

Signed-off-by: Pedro Ruivo <[email protected]>
Signed-off-by: Pedro Ruivo <[email protected]>
Co-authored-by: Alexander Schwartz <[email protected]>
  • Loading branch information
pruivo and ahus1 authored Feb 13, 2025
1 parent 8a03661 commit 70e2a28
Show file tree
Hide file tree
Showing 39 changed files with 1,378 additions and 264 deletions.
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,41 @@ jobs:
with:
job-id: clustering-integration-tests

clustering-integration-tests-mtls:
name: Clustering IT (mTLS)
needs: build
runs-on: ubuntu-latest
timeout-minutes: 35
env:
MAVEN_OPTS: -Xmx1536m
steps:
- uses: actions/checkout@v4

- id: integration-test-setup
name: Integration test setup
uses: ./.github/actions/integration-test-setup

- name: Run cluster tests with mtls
run: |
./mvnw test ${{ env.SUREFIRE_RETRY }} -Pauth-server-cluster-quarkus,db-postgres "-Dwebdriver.chrome.driver=$CHROMEWEBDRIVER/chromedriver" -Dsession.cache.owners=2 -Dtest=RealmInvalidationClusterTest -Dauth.server.jgroups.mtls=true -pl testsuite/integration-arquillian/tests/base
- name: Upload JVM Heapdumps
if: always()
uses: ./.github/actions/upload-heapdumps

- uses: ./.github/actions/upload-flaky-tests
name: Upload flaky tests
env:
GH_TOKEN: ${{ github.token }}
with:
job-name: Clustering IT (mTLS)

- name: Surefire reports
if: always()
uses: ./.github/actions/archive-surefire-reports
with:
job-id: clustering-integration-tests-mtls

fips-unit-tests:
name: FIPS UT
runs-on: ubuntu-latest
Expand Down
12 changes: 12 additions & 0 deletions docs/guides/server/caching.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,18 @@ It requires a keystore with the certificate to use: `cache-embedded-mtls-key-sto
The truststore contains the valid certificates to accept connection from, and it can be configured with `cache-embedded-mtls-trust-store-file` (path to the truststore), and `cache-embedded-mtls-trust-store-password` (password to decrypt it).
To restrict unauthorized access, use a self-signed certificate for each {project_name} deployment.

[NOTE]
====
**Zero Configuration Encryption**
{project_name} offers a zero-configuration approach to encrypting network communication between nodes.
This feature automatically generates self-signed certificates, eliminating the need for manual certificate creation and management.
The generated certificate and associated keys are stored within the database of each {project_name} instance.
To enable zero-configuration TLS encryption, set the `cache-embedded-mtls-enabled` option to true.
No other `cache-embedded-mtls-*` must be set to enable the zero-configuration mode.
====

For JGroups stacks with `UDP` or `TCP_NIO2`, see the http://jgroups.org/manual5/index.html#ENCRYPT[JGroups Encryption documentation] on how to set up the protocol stack.

For more information about securing cache communication, see the {infinispan_embedding_docs}#secure-cluster-transport[Encrypting cluster transport] documentation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2025 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.storage.configuration.jpa;

import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

import jakarta.persistence.EntityManager;
import jakarta.persistence.LockModeType;
import org.keycloak.storage.configuration.ServerConfigStorageProvider;
import org.keycloak.storage.configuration.jpa.entity.ServerConfigEntity;

/**
* A {@link ServerConfigStorageProvider} that stores its data in the database, using the {@link EntityManager}.
*/
public class JpaServerConfigStorageProvider implements ServerConfigStorageProvider {

private final EntityManager entityManager;

public JpaServerConfigStorageProvider(EntityManager entityManager) {
this.entityManager = Objects.requireNonNull(entityManager);
}

@Override
public Optional<String> find(String key) {
return Optional.ofNullable(getEntity(key, LockModeType.READ))
.map(ServerConfigEntity::getValue);
}

@Override
public void store(String key, String value) {
var entity = getEntity(key, LockModeType.WRITE);
if (entity == null) {
entity = new ServerConfigEntity();
entity.setKey(Objects.requireNonNull(key));
entity.setValue(Objects.requireNonNull(value));
entityManager.persist(entity);
return;
}
entity.setValue(Objects.requireNonNull(value));
entityManager.merge(entity);
}

@Override
public void remove(String key) {
var entity = getEntity(key, LockModeType.WRITE);
if (entity != null) {
entityManager.remove(entity);
}
}

@Override
public String loadOrCreate(String key, Supplier<String> valueGenerator) {
var entity = getEntity(key, LockModeType.WRITE);
if (entity != null) {
return entity.getValue();
}
var value = Objects.requireNonNull(valueGenerator.get());
entity = new ServerConfigEntity();
entity.setKey(Objects.requireNonNull(key));
entity.setValue(value);
entityManager.persist(entity);
return value;
}

@Override
public void close() {
//no-op
}

private ServerConfigEntity getEntity(String key, LockModeType lockModeType) {
return entityManager.find(ServerConfigEntity.class, Objects.requireNonNull(key), lockModeType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2025 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.storage.configuration.jpa;

import java.util.Set;

import jakarta.persistence.EntityManager;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
import org.keycloak.storage.configuration.ServerConfigStorageProviderFactory;

/**
* A {@link ServerConfigStorageProviderFactory} that instantiates {@link JpaServerConfigStorageProvider}.
*/
public class JpaServerConfigStorageProviderFactory implements ServerConfigStorageProviderFactory {

@Override
public JpaServerConfigStorageProvider create(KeycloakSession session) {
return new JpaServerConfigStorageProvider(getEntityManager(session));
}

@Override
public void init(Config.Scope config) {

}

@Override
public void postInit(KeycloakSessionFactory factory) {

}

@Override
public void close() {

}

@Override
public String getId() {
return "jpa";
}

@Override
public Set<Class<? extends Provider>> dependsOn() {
return Set.of(JpaConnectionProvider.class);
}

private static EntityManager getEntityManager(KeycloakSession session) {
return session.getProvider(JpaConnectionProvider.class).getEntityManager();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2025 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.storage.configuration.jpa.entity;

import java.util.Objects;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Version;

/**
* A JPA entity to store the key-value configuration.
*/
@SuppressWarnings("unused")
@Table(name = "SERVER_CONFIG")
@Entity
public class ServerConfigEntity {

@Id
@Column(name = "SERVER_CONFIG_KEY")
private String key;

@Column(name = "VALUE")
private String value;

@Version
@Column(name = "VERSION")
private int version;

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

public int getVersion() {
return version;
}

public void setVersion(int version) {
this.version = version;
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;

ServerConfigEntity that = (ServerConfigEntity) o;
return version == that.version && Objects.equals(key, that.key) && Objects.equals(value, that.value);
}

@Override
public int hashCode() {
int result = Objects.hashCode(key);
result = 31 * result + Objects.hashCode(value);
result = 31 * result + version;
return result;
}
}
32 changes: 32 additions & 0 deletions model/jpa/src/main/resources/META-INF/jpa-changelog-26.2.0.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ * Copyright 2024 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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

<changeSet author="keycloak" id="26.2.0-36750">
<createTable tableName="SERVER_CONFIG">
<column name="SERVER_CONFIG_KEY" type="VARCHAR(255)">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="VALUE" type="CLOB">
<constraints nullable="false"/>
</column>
<column name="VERSION" type="INT" defaultValueNumeric="0"/>
</createTable>
</changeSet>

</databaseChangeLog>
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,6 @@
<include file="META-INF/jpa-changelog-25.0.0.xml"/>
<include file="META-INF/jpa-changelog-26.0.0.xml"/>
<include file="META-INF/jpa-changelog-26.1.0.xml"/>
<include file="META-INF/jpa-changelog-26.2.0.xml"/>

</databaseChangeLog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#
# Copyright 2025 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.
#

org.keycloak.storage.configuration.jpa.JpaServerConfigStorageProviderFactory
3 changes: 3 additions & 0 deletions model/jpa/src/main/resources/default-persistence.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@
<class>org.keycloak.models.jpa.entities.OrganizationEntity</class>
<class>org.keycloak.models.jpa.entities.OrganizationDomainEntity</class>

<!-- Server Configuration -->
<class>org.keycloak.storage.configuration.jpa.entity.ServerConfigEntity</class>

<exclude-unlisted-classes>true</exclude-unlisted-classes>

<properties>
Expand Down
Loading

0 comments on commit 70e2a28

Please sign in to comment.