//

Keycloak.X, but secure – without vulnerable libraries

9.5.2022 | 11 minutes of reading time

TLDR: How to reduce the known CVEs (common vulnerabilities and exposures) to zero by creating your own Keycloak distribution* .

Introduction

Keycloak (see website) will become easier and more robust by switching to Quarkus, at least that’s the promise. We have already shown how to approach a productive setup step by step in the blog post From Keycloak to Keycloak.X with an earlier version of Keycloak.X. In the meantime, version 18.0.0 has been released and the roadmap for the Keycloak project has been further concretized. Among other things, it states that the last Wildfly distribution will be released in September 2022 – from then on there will only be the Quarkus-based Keycloak distribution.

This article describes an approach to improve the performance and security of a Keycloak system by creating a customized Keycloak distribution. This requires complete control over the creation of one’s own Keycloak distribution.

Aspects of a custom Keycloak distribution

Creating your own customized Keycloak distribution can improve the security and/or performance of the running Keycloak system. As a counter-argument, we often hear that having one’s own distribution leads to unnecessary and increased complexity. In addition, there seems to be a general recommendation to use official images so that this part of the responsibility does not have to be borne by oneself. We argue here for the explicit assumption of this responsibility in the case of Keycloak and see great advantages in this step.

A custom distribution can support in the following:

  1. Use of an optimized configuration for fast server start-up
  2. Support of own extensions and themes
  3. Only actually used Quarkus extensions activated
  4. Additionally needed Quarkus extensions are supported
  5. Libraries can be upgraded to a current patch level

Properties of the standard distribution

To look at the properties of the default Keycloak distribution, we use the following default Keycloak Docker image: quay.io/keycloak/keycloak:18.0.0.

A Docker container with the image can then be started in the following way:

docker run --rm -it quay.io/keycloak/keycloak:18.0.0 start \
   --auto-build \
   --http-enabled=true \
   --hostname-strict=false \
   --hostname-strict-https=false

We use the --auto-build parameter to tell Keycloak to apply build-time configuration.

Activated extensions in the standard image

The preceding command outputs the following list of activated Quarkus extensions (Keycloak features) during the Keycloak server start:

2022-05-07 10:44:39,393 INFO  [io.quarkus] (main) Installed features: 
[agroal, cdi, hibernate-orm, infinispan-client, jdbc-h2, jdbc-mariadb, 
jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, 
narayana-jta, reactive-routes, resteasy, resteasy-jackson, 
smallrye-context-propagation, smallrye-health, smallrye-metrics, vault, 
vertx]

We see here that Keycloak enables support for many databases by default: MSSQL, Oracle, MySQL, MariaDB, H2 (old 1.x version with many CVEs). We would like to limit this to a single required database in the further course: PostgreSQL.

Missing extensions in the standard image

Quarkus offers a wide range of functionality that can be activated via Quarkus extensions. A pre-selection has already been made in the Keycloak distribution.

A way to activate these functions has already been asked for in a Keycloak discussion and there was already a solution from the community. The procedure described in the Keycloak Discussion works, but may deter users due to its complexity.

Vulnerabilities found in the standard image

In our example, we use the tool Trivy from Aquasecurity to scan Docker images for known CVEs. You can easily run the tool as a Docker container.

We use a small Java CLI wrapper here to run the Trivy scan:

java bin/scanImage.java --image-name=quay.io/keycloak/keycloak:18.0.0

Result of the Trivy scan with standard Keycloak Docker image as gist .

quay.io/keycloak/keycloak:18.0.0 (redhat 8.5)
=============================================
Total: 104 (UNKNOWN: 0, LOW: 37, MEDIUM: 65, HIGH: 2, CRITICAL: 0)
 
Java (jar)
==========
Total: 5 (UNKNOWN: 1, LOW: 0, MEDIUM: 0, HIGH: 1, CRITICAL: 3)

Note: These results change over time:

  • New vulnerabilities are found
  • The general CVE scoring changes due to new findings
  • There is a re-release of the Docker image with updated OS components

Building your own Keycloak distribution

To build our own Keycloak distribution with the above-mentioned adaptations, we combine parts of the Keycloak.X server distribution with the Keycloak Quarkus server application, which is also used by the Keycloak project in its own distribution. To do this, we create our own Maven project. Using Maven Dependency Management, we include the Keycloak Quarkus distribution as a .zip archive.
This archive is then unpacked with the maven-dependency-plugin into the target directory, whereby we explicitly exclude the lib directory of the distribution. The next step is to include the keycloak-quarkus-server Maven dependency, which allows us to customize the dependencies of the Keycloak Quarkus Server application.

In order to be able to store further configurations in the generated Keycloak distribution, the content of the src/main/copy-to-keycloak directory is copied over the unpacked Keycloak distribution via the maven-resources-plugin.

We can create our own distribution with the following command:

mvn clean package

After that, we find our own Keycloak distribution in the directory
target/keycloak-18.0.0, which can already be used.

Adding extensions and themes

This approach also allows the use of custom extensions and themes. In the example, we have used our own event listener provider and a custom theme.

Testing with Keycloak Testcontainers

Our own extensions can be tested automatically with the help of the Keycloak Testcontainers library in the form of integration tests. For the sake of simplicity, we use the standard Keycloak Docker Image for the tests. With a little additional configuration and build orchestration, the previously created custom image could also be used here.

Creating a custom Docker image

Our own Keycloak.X distribution can be brought into one’s own Docker Image in the same way as the standard Keycloak.X Docker Image. In our example, we use the fabric8 Maven Docker Plugin for this.

We then start the Docker Image build using the following command:

mvn clean package docker:build 
-Ddocker.image=thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT

Removing unneeded Quarkus extensions

Keycloak uses numerous libraries that are integrated via Quarkus extensions. Depending on the environment, some of these extensions are not needed, e.g. if only a PostgreSQL database is used, then support for Oracle and other databases is not needed. In this case, Quarkus extensions can be removed via appropriate Maven Dependency Exclusions. For example, if we want to remove support for the Oracle database, we can apply the following exclusions to the org.keycloak:keycloak-quarkus-server:18.0.0 Maven Dependency:

    <dependency>
        <!-- Keycloak Quarkus Server Libraries-->
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-quarkus-server</artifactId>
        <version>${keycloak.version}</version>
        <!-- Exclude unused dependencies -->
 
        <exclusions>
            ...
            <!-- Exclude unused support for: Oracle -->
                <exclusion>
                    <groupId>com.oracle.database.jdbc</groupId>
                    <artifactId>ojdbc11</artifactId>
            </exclusion>
            <exclusion>
                    <groupId>io.quarkus</groupId>
                    <artifactId>quarkus-jdbc-oracle</artifactId>
            </exclusion>
            <exclusion>
                    <groupId>io.quarkus</groupId>
                    <artifactId>quarkus-jdbc-oracle-deployment</artifactId>
            </exclusion>
            ...
        </exclusions>
    </dependency>

This technique can also be used to remove vulnerable libraries that are not needed. For example, Keycloak currently uses an old 1.x version of the H2 database by default, which is affected by numerous CVEs (note: as soon as Keycloak is updated to the new Quarkus version >2.8.2, H2 will also be upgraded to a new 2.x version without known CVEs). However, if you only use Keycloak with another database like PostgreSQL instead, you can also remove the H2 extension.

In order to remove the support for the H2 database, we can apply the
following Maven Dependency Exclusions:

<!-- Exclude unused support for: H2 -->
<!-- Note: by default keycloak uses the h2 database as a database for 
     auto-build. To remove h2, one needs to configure another Database 
     in src/main/resources/META-INF/keycloak.conf -->
      <exclusion>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
      </exclusion>
      <exclusion>
         <groupId>io.quarkus</groupId>
         <artifactId>quarkus-jdbc-h2</artifactId>
      </exclusion>
      <exclusion>
         <groupId>io.quarkus</groupId>
         <artifactId>quarkus-jdbc-h2-deployment</artifactId>
      </exclusion>

In addition, an entry such as db=postgres must be added to the file
src/main/resources/META-INF/keycloak.conf. You have to add an entry like
db=postgres, otherwise the Keycloak distribution build will complain about the missing H2 library.

Let’s start our distribution created in this way as a Docker container (see below) with the following command:

docker run --rm -it \
    -p 8080:8080 \
    -e KEYCLOAK_ADMIN=keycloak \
    -e KEYCLOAK_ADMIN_PASSWORD=keycloak \
    thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT \
    start \
   --auto-build \
   --http-enabled=true \
   --http-relative-path=auth \
   --hostname-strict=false \
   --hostname-strict-https=false \
   --db=postgres \
   --db-url-host=172.17.0.1\
   --db-url-database=keycloak \
   --db-username=keycloak \
   --db-password=keycloak

We see in the container log output that the database extensions that are not needed have disappeared and only jdbc-postgresql remains.

2022-05-07 14:27:09,161 INFO  [io.quarkus] (main) Installed features: 
[agroal, cdi, hibernate-orm, jdbc-postgresql, keycloak, narayana-jta, 
reactive-routes, resteasy, resteasy-jackson, smallrye-context-propagation,
 smallrye-health, smallrye-metrics, vault, vertx]

Integrating additional Quarkus extensions

This approach also allows us to use new Quarkus extensions for Keycloak.
As an example, we want to enable support for centralized logging using GELF in Keycloak.

To do this, we add the following dependencies to our Maven project:

<!-- Additional Quarkus Features: Begin -->
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-logging-gelf</artifactId>
    <version>${quarkus.version}</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-logging-gelf-deployment</artifactId>
    <version>${quarkus.version}</version>
</dependency>
 
<!-- Additional Quarkus Features: End -->

When now build our custom distribution, the new Quarkus GELF extensions will be recognized and activated accordingly.

These Quarkus-specific extensions can then be configured using the
quarkus.properties file in the conf directory of the Keycloak installation.

An example configuration in quarkus.properties for GELF:

# Configure log streaming via gelf
quarkus.log.handler.gelf.enabled=true
quarkus.log.handler.gelf.host=localhost
quarkus.log.handler.gelf.port=12201
quarkus.log.handler.gelf.facility=iam

Let’s start our distribution created in this way as a Docker container again:

docker run --rm -it \
    -p 8080:8080 \
    -e KEYCLOAK_ADMIN=keycloak \
    -e KEYCLOAK_ADMIN_PASSWORD=keycloak \
    thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT \
    start \
   --auto-build \
   --http-enabled=true \
   --http-relative-path=auth \
   --hostname-strict=false \
   --hostname-strict-https=false \
   --db=postgres \
   --db-url-host=172.17.0.1\
   --db-url-database=keycloak \
   --db-username=keycloak \
   --db-password=keycloak

We see that the desired logging-gelf extension has been recognized by the Quarkus runtime.

2022-05-07 14:27:09,161 INFO  [io.quarkus] (main) Installed features: 
[agroal, cdi, hibernate-orm, jdbc-postgresql, keycloak, logging-gelf, 
narayana-jta, reactive-routes, resteasy, resteasy-jackson, 
smallrye-context-propagation, smallrye-health, smallrye-metrics, vault, 
vertx]

Patching used libraries

As already mentioned, CVEs are known for some Java libraries used by the current Keycloak distribution. Compatible patch versions already exist for some of these libraries. With the approach shown, these libraries can be easily updated via Maven Dependency Management. The new dependency versions are then updated accordingly when the dependencies are resolved in the build of the own Keycloak distribution and raised to the latest (compatible) patch level.

The latest available Keycloak release 18.0.0 contains several vulnerable libraries, for example a version of the XStream library (1.4.18) which we can update with a managed Maven dependency override:

<dependencyManagement>
  <dependencies>
<!-- CVE Patch overrides: Begin -->
     <dependency>
        <groupId>com.thoughtworks.xstream</groupId>
        <artifactId>xstream</artifactId>
        <version>1.4.19</version>
     </dependency>
 
   </dependencies>
</dependencyManagement>

Note: In our example on GitHub, we were able to successfully mitigate all CVEs through dependency upgrades.

Note: Since each new Keycloak version is accompanied by new versions of libraries, it is recommended to remove the overwritten managed dependencies after upgrading the Keycloak version and to run a new image scan. After a new image scan, you may receive a new list of vulnerable libraries that you can then patch again in the way shown.

Security vulnerabilities found in base image

Through appropriate dependency upgrades and dependency exclusions, we can bring all Java libraries to a currently secure state.
No more CVEs are reported for Java libraries. However, the ubi8-minimal Docker image still contains vulnerable components.

We can perform the Docker image scan with the following command:

java bin/scanImage.java 
--image-name=thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT

Result of the Trivy scan with custom ubi8-minimal image in a gist .

thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT (redhat 8.5)
===========================================================
Total: 104 (UNKNOWN: 0, LOW: 37, MEDIUM: 65, HIGH: 2, CRITICAL: 0)
 
Java (jar)
==========
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

If we also want to get rid of the reported CVEs from the base image, then one possibility is to exchange the base image for one without CVEs, for example image based on Alpine. According to Trivy scan, the image alpine:3.15.4 currently does not contain any known CVEs. Using the following command, we can build an Alpine based Docker image:

mvn clean package docker:build -Ddocker.file=keycloak/Dockerfile.alpine

A new scan of the new Docker image with Trivy then delivers pleasing results: 0 CVEs \o/.

java bin/scanImage.java --image-name=thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT

Result of the Trivy scan with Alpine Docker image as gist .

thomasdarimont/custom-keycloakx:1.0.0-SNAPSHOT (alpine 3.15.4)
==============================================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
 
Java (jar)
==========
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

Summary

In this article we have presented an approach for creating your own Keycloak distributions. This approach makes it possible to simply deliver your own extensions and themes instead of doing this, for example, during deployment at runtime. Furthermore, Quarkus extensions that are not needed can be removed and new Quarkus extensions can be added to Keycloak.

Another customization option is fine-grained upgrades of libraries without known security vulnerabilities. By additionally using a different Docker base image, we were able to create a Docker image with a Keycloak distribution that does not contain any known CVEs.

Besides the higher security due to a reduced attack surface, the footprint is again improved due to the reduced amount of extensions.

This approach allows dynamic packaging of Keycloak distributions according to one’s own requirements. It would be desirable for the Keycloak project to support this or a similar approach out of the box to enable more secure and streamlined Keycloak installations.

The example for creating your own Keycloak distribution can be found here on GitHub .
In the branch keycloak-custom-server/zero-cves you will find the version of the example we used for the scans.

Disclaimer

Keycloak is a complex open-source software product that relies on a large number of libraries. Unfortunately, this does not prevent CVEs from being discovered – but that is the case with every larger software project. We are very happy about our achievement: producing a custom Keycloak distribution without any known security vulnerabilities*. Other approaches like a searching / replacing / deleting vulnerable libraries had the same goal, but always felt quite fragile. We’re looking forward to your feedback.

*) Zero CVEs refers to the result of an image scan with Aquasec/Trivy and is a snapshot at the time of the experiment. A scan with other tools, e.g. Docker Scan , at another time could reveal new CVEs if new CVEs become known in the meantime. We recommend performing continuous vulnerability scans of generated artifacts such as custom libraries and Docker images.

share post

Likes

0

//

More articles in this subject area\n

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen

Wir helfen Deinem Unternehmen

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.