Keycloak X Metal Sculpture

Keycloak.X, aber sicher – ohne bekannte Sicherheitslücken!

Keine Kommentare

TLDR: Wie man die bekannten CVEs (Common Vulnerabilities and Exposures) mit einer eigenen Keycloak-Distribution auf null* reduziert.

Einführung

Keycloak (s. Website) wird durch die Umstellung auf Quarkus einfacher und robuster, so das Versprechen. Wie man sich Schritt für Schritt einem produktiven Setup nähern kann, haben wir bereits im Blogpost „From Keycloak to Keycloak.X (englisch)“ mit einer früheren Version von Keycloak.X gezeigt. Inzwischen wurde Version 18.0.0 veröffentlicht und die Roadmap für das Keycloak Projekt weiter konkretisiert. Darin liest man, dass im September 2022 die letzte Wildfly-Distribution erscheinen soll – ab dann gibt es nur noch die Quarkus-basierte Keycloak-Distribution.

Der vorliegende Artikel beschreibt einen Ansatz, mit dem die Performance und die Sicherheit eines Keycloak-Systems durch eine eigene Distribution verbessert werden kann. Dazu ist die vollständige Kontrolle über die Erzeugung einer eigenen Keycloak-Distribution notwendig.

Aspekte einer eigenen Keycloak-Distribution

Durch die Erzeugung einer eigenen, auf die jeweiligen Bedürfnisse zugeschnittene, Keycloak-Distribution, kann sich die Sicherheit und/oder die Performance des laufenden Keycloak-Systems verbessern.
Als Gegenargument hören wir häufig, dass eine eigene Distribution zu unnötiger und erhöhter Komplexität führt. Außerdem scheint allgemein eine Empfehlung zur Nutzung offizieller Images zu gelten, damit dieser Teil der Verantwortung nicht selbst zu tragen ist. Wir argumentieren hier für die explizite Übernahme diese Verantwortung im Falle von Keycloak und sehen große Vorteile in diesem Schritt.

Die eigene Distribution kann in folgenden Punkten unterstützen:

  1. Verwendung einer optimierten Konfiguration für schnellen Serverstart
  2. Unterstützung eigener Extensions und Themes
  3. Nur tatsächlich verwendete Quarkus Extensions aktiviert
  4. Zusätzlich benötigte Quarkus Extensions werden unterstützt
  5. Bibliotheken können auf ein aktuelles Patch-Level angehoben werden

Eigenschaften der Standard-Distribution

Zur Betrachtung der Eigenschaften der Standard-Keycloak-Distribution verwenden wir das folgende Standard Keycloak Docker-Image: quay.io/keycloak/keycloak:18.0.0.

Ein Docker-Container mit dem Image kann dann auf die folgende Weise gestartet werden:

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

Über den Parameter --auto-build weisen wir Keycloak dazu an, Build-time Konfiguration anzuwenden.

Aktivierte Extensions im Standard-Image

Das vorhergehende Kommando gibt folgende Liste aktivierter Quarkus Extensions (Keycloak Features) während des Keycloak-Serverstarts aus:

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]

Wir sehen hier, dass Keycloak standardmäßig Unterstützung für zahlreiche Datenbanken aktiviert: MSSQL, Oracle, MySQL, MariaDB, H2 (alte 1.x-Version mit vielen CVEs). Diese möchten wir im weiteren Verlauf auf eine einzige erforderliche Datenbank begrenzen: PostgreSQL.

Fehlende Extensions im Standard Image

Quarkus bietet ein breites Spektrum an Funktionalität, die über Quarkus Extensions aktiviert werden können. In der Keycloak-Distribution ist bereits eine Vorauswahl erfolgt.

Nach einem Weg, diese Funktionen zu aktivieren, wurde bereits auf in einer Keycloak Discussion gefragt und aus der Community gab es bereits eine Lösung dazu. Das dort beschriebene Vorgehen funktioniert, aber ist nicht im Maven Build automatisiert.

Gefundene Sicherheitslücken im Standard-Image

In unserem Beispiel verwenden wir das Tool trivy von Aquasec zum Scannen von Docker Images nach bekannten CVEs. Das Tool kann man bequem als Docker Container ausführen.

Wir verwenden hier einen kleinen Java-CLI Wrapper, um den Trivy scan auszuführen:

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

Ergebnis des Trivy Scans mit Standard-Keycloak-Docker-Image als 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)

Hinweis: Diese Ergebnisse verändern sich mit der Zeit:

  • Neue Sicherheitslücken werden gefunden
  • Das allgemeine CVE-Scoring ändert sich aufgrund neuer Erkenntnisse
  • Es findet ein erneutes Release des Docker-Images mit aktualisierten OS-Komponenten statt

Eine eigene Distribution bauen

Um eine eigene Keycloak-Distribution mit den oben genannten Anpassungen zu bauen, kombinieren wir Teile der Keycloak.X-Server-Distribution mit der Keycloak-Quarkus-Server-Anwendung, die vom Keycloak-Projekt auch in der eigenen Distribution verwendet wird.
Dazu erstellen wir ein eigenes Maven-Projekt.
Über das Maven Dependency Management binden wir die Keycloak-Quarkus-Distribution als .zip Archiv ein. Dieses Archiv wird dann mit dem maven-dependency-plugin in das target-Verzeichnis entpackt, wobei wir das lib Verzeichnis der Distribution explizit ausklammern. Als nächsten Schritt wird die keycloak-quarkus-server Maven Dependency inkludiert, die es uns erlaubt, die Abhängigkeiten von der Keycloak-Quarkus-Server-Applikation anzupassen.

Um in der erzeugten Keycloak-Distribution weitere Konfigurationen hinterlegen zu können, wird über das maven-resources-plugin der Inhalt des Verzeichnisses
src/main/copy-to-keycloak über die entpackte Keycloak-Distribution kopiert.

Eine eigene Distribution können wir mit dem folgenden Kommando erzeugen:

mvn clean package

Danach finden wir im Verzeichnis target/keycloak-18.0.0 unsere eigene Keycloak-Distribution, die bereits genutzt werden kann.

Extensions und Themes

Dieser Ansatz erlaubt auch die Nutzung von eigenen Extensions und Themes.
Im Beispiel haben wir einen eigenen Event Listener Provider sowie ein einiges „Custom“ Theme eingebracht.

Testing mit Keycloak-Testcontainers

Unsere eigenen Erweiterungen können mithilfe der Keycloak-Testcontainers-Bibliothek in der Form von Integrationstests automatisiert testen.
Der Einfachheit halber verwenden wir hier für die Tests das Standard-Keycloak-Docker-Image. Mit ein wenig zusätzlicher Konfiguration und Build-Orchestrierung könnte man hier auch das zuvor erstellte Custom Image verwenden.

Eigenes Docker Image

Die eigene Keycloak.X Distribution lässt sich analog zu dem Standard-Keycloak.X-Docker-Image in ein eigenes Docker Image einbringen. In unserem Beispiel verwenden wir hierzu das Maven Docker Plugin.

Den Docker Image build starten wir dann über folgendes Kommando:

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

Nicht benötigte Quarkus Extensions entfernen

Keycloak verwendet zahlreiche Bibliotheken, die über Quarkus Extensions eingebunden werden. Je nach Umgebung werden einige von diesen Extensions nicht benötigt, z. B. wenn nur mit einer PostgreSQL-Datenbank gearbeitet wird, dann wird z. B. die Unterstützung für Oracle und andere Datenbanken nicht benötigt. In diesem Fall können Quarkus Extensions über entsprechende Maven Dependency Exclusions entfernt werden.

Wollen wir beispielsweise die Unterstützung für die Oracle Datenbank entfernen, so können wir folgende Exclusions auf die org.keycloak:keycloak-quarkus-server:18.0.0 Maven Dependency anwenden:

    <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>

Mit dieser Technik lassen sich auch nicht benötigte vulnerable Bibliotheken entfernen.
So verwendet Keycloak derzeit standardmäßig eine alte 1.x-Version der H2-Datenbank, die von zahlreichen CVEs betroffen ist (Hinweis: Sobald Keycloak auf die neue Quarkus Version >2.8.2 aktualisiert wird, wird auch H2 auf eine neue Version 2.x ohne bekannte CVEs angehoben). Wenn man Keycloak jedoch stattdessen nur mit einer anderen Datenbank wie PostgreSQL verwendet, kann man auch die H2 Extension entfernen.

Um die Unterstützung für die H2 Datenbank zu entfernen, können wir folgende Maven Dependency Exclusions verwenden:

<!-- 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>

Zusätzlich muss dann in der Datei src/main/resources/META-INF/keycloak.conf
noch einen Eintrag wie db=postgres ergänzt werden, da sich ansonsten der Keycloak Distribution Build über die fehlende H2-Bibliothek beschwert.

Starten wir unsere so erzeugte Distribution als Docker-Container (siehe unten) mit folgendem Kommando …

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

… sehen wir in der Container Log-Ausgabe, dass die nicht benötigten Datenbank-Extensions verschwunden sind und nur noch jdbc-postgresql übrig geblieben ist.

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]

Weitere Extensions aktivieren und konfigurieren

Dieser Ansatz erlaubt es uns auch, neue weitere Quarkus Extensions für Keycloak zu nutzen. Als Beispiel wollen wir Unterstützung für zentralisiertes Logging mittels GELF in Keycloak aktivieren.

Dazu fügen wir unserem Maven-Projekt folgende Abhängigkeiten hinzu:

<!-- Additional Quarkus Extensions: 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 Extensions: End -->

Wenn wir unsere Distribution damit bauen, wird das neue Quarkus GELF Extensions erkannt und entsprechend aktiviert.

Diese Quarkus-spezifischen Extensions kann man dann mittels der Datei quarkus.properties im conf Verzeichnis der Keycloak-Installation konfigurieren.

Eine Beispielkonfiguration in quarkus.properties für 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

Starten wir unsere so erzeugte Distribution wieder als Docker-Container …

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

… so sehen wir, dass die gewünschte logging-gelf Extension von der Quarkus-Laufzeit erkannt wurde.

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]

Verwendete Bibliotheken patchen

Wie bereits erwähnt, sind für einige von der aktuellen Keycloak-Distribution verwendeten Java-Bibliotheken CVEs bekannt. Für manche dieser Bibliotheken existieren bereits kompatible Patch-Versionen. Mit dem gezeigten Ansatz lassen sich diese Bibliotheken einfach über Maven Dependency Management aktualisieren. Die neuen Dependency Versionen werden dann bei der Auflösung der Abhängigkeiten im Build der eigenen Keycloak Distribution entsprechend aktualisiert und auf das aktuellste (kompatible) Patch-Niveau gehoben.
Keycloak 18.0.0 enthält aktuell mehrere vulnerable Bibliotheken, beispielsweise eine Version der XStream-Bibliothek (1.4.18). Diese können wir über eine überschriebene Maven Dependency entsprechend aktualisieren.

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

Hinweis: In unserem Beispiel auf GitHub haben wir alle CVEs erfolgreich durch Dependency Upgrades mitigieren können.
Hinweis: Da mit jeder neuen Keycloak-Version wieder neue Versionen von Bibliotheken einhergehen, ist es empfehlenswert, die überschriebenen Managed Dependencies nach einem Upgrade der Keycloak-Version wieder zu entfernen und einen neuen Image-Scan laufen zu lassen. Nach einem erneuten Image-Scan erhält man dann ggf. wieder eine neue Liste von vulnerablen Bibliotheken, die man dann auf dem gezeigten Weg wieder patchen kann.

Weitere Sicherheitslücken im Basis-Image

Durch entsprechende Dependency Upgrades und Dependency Exclusions können wir alle Java-Bibliotheken auf einen derzeit sicheren Stand bringen. Es werden keine CVEs mehr für Java-Bibliotheken gemeldet. Allerdings sind im ubi8-minimal Docker Image weiterhin vulnerable Komponenten enthalten.
Mit folgendem Kommando können wir den Docker-Image-Scan durchführen:

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

Ergebnis des Trivy Scans mit custom ubi8-minimal Image als 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)

Wenn wir auch die gemeldeten CVEs aus dem Basis-Image loswerden möchten, dann ist es eine Möglichkeit, das Basis-Image gegen eines ohne CVEs auszutauschen, beispielsweise Images auf Basis von Alpine. Das Image alpine:3.15.4 enthält laut Trivy-Scan derzeit keine bekannten CVEs.

Mit folgendem Kommando können wir ein Alpine-basiertes Docker image bauen:

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

Ein erneuter Scan des neuen Docker Images mit Trivy liefert dann erfreuliche Ergebnisse: 0 CVEs \o/.

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

Ergebnis des Trivy-Scans mit Alpine Docker Image als 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)

Abschluss

In diesem Artikel haben wir einen Ansatz zur Erstellung von eigenen Keycloak-Distributionen vorgestellt. Dieser Ansatz ermöglicht es, eigene Erweiterungen und Themes einfach mit auszuliefern, statt dies z. B. im Deployment zur Laufzeit vorzunehmen. Weiterhin können damit auch nicht benötigten Quarkus Extensions entfernt und auch neue Quarkus Extensions in Keycloak ergänzt werden.

Eine weitere Anpassungsmöglichkeit sind feingranulare Upgrades von Bibliotheken ohne bekannte Sicherheitslücken. Durch die zusätzliche Verwendung eines anderen Docker-Basis-Images konnten wir ein Docker Image mit einer Keycloak-Distribution erzeugen, die keine bekannten CVEs enthält.

Neben der höheren Sicherheit durch eine verringerte Angriffsoberfläche ist der Fußabdruck durch die reduzierte Menge von Extensions nochmals verbessert.

Dieser Ansatz erlaubt ein dynamisches Paketierung von Keycloak Distributionen gemäß der eigenen Anforderungen. Es wäre wünschenswert, dass das Keycloak-Projekt diesen oder einen ähnlichen Ansatz von Haus aus unterstützt, um sicherere und schlankere Keycloak-Installationen zu ermöglichen.

Das Beispiel zur Erzeugung einer eignen Keycloak Distribution findet man unter folgendem Link auf Github.
Im Branch keycloak-custom-server/zero-cves ist das Beispiel für unsere Scans enthalten.

Disclaimer

Keycloak ist ein komplexes Open-Source-Softwareprodukt, das sich auf eine Vielzahl an Bibliotheken stützt. Leider bleibt es dabei nicht aus, dass dafür auch CVEs bekannt werden – aber das ist bei jedem größeren Softwareprojekt so.
Wir freuen uns sehr über das erreichte Ziel: Keycloak einsetzen und dabei keine bekannten Sicherheitslücken auszuliefern. Vorherige Ansätze mit „Suchen“ auf dem Verzeichnis und anschließendem Löschen und Ersetzen von Bibliotheken hatten das gleiche Ziel, fühlten sich aber immer sehr fragil an und waren es auch. Wir hoffen, dass es weitere Fortschritte und Verbesserungsvorschläge zu dem gezeigten Ansatz gibt und freuen uns auf Feedback.

*) Null CVEs bezieht sich auf das Ergebnis des Image Scans mit Aquasec/Trivy und ist eine Momentaufnahme zum Zeitpunkt des Experiments. Ein Scan mit anderen Tools zu einem anderen Zeitpunkt könnte wieder neue CVEs zum Vorschein bringen, wenn neue CVEs bekannt werden. Wir empfehlen die Durchführung von kontinuierlichen Vulnerability Scans erzeugter Artefakte wie eigener Bibliotheken und Docker Images.

Sebastian ist Identity und Access Management Consultant bei der codecentric AG.
Eines seiner Schwerpunktthemen ist der Einsatz von Keycloak als SSO-Komponente und deren Integration in Unternehmensabläufe. Er hat viel Erfahrung als Softwareentwickler in Teams, die nach Agilität streben und Open-Source-Software hauptsächlich auf der JVM einsetzen. Die Softwarebranche ist seit fast 20 Jahren sein Arbeitsfeld.
Er hilft bei der Organisatoren der Java User Group Darmstadt. Er lebt mit seiner Frau und zwei Kindern in Wiesbaden.

Thomas Darimont arbeitet als Fellow bei der codecentric AG in Münster. Dort hilft er Kunden bei der Implementierung zentralisierter Identity-Management-Plattformen. Davor arbeitete er als Principal Software Engineer im Spring Data Team bei Pivotal.
Er hat mehr als fünfzehn Jahre Erfahrung in der Entwicklung von Enterprise Applications und Open-Source-Projekten mit Java und .NET. Seine Arbeitsschwerpunkte liegen in den Bereichen Softwarearchitektur, Spring-Ökosystem, Performance Tuning und Security. In seiner Freizeit organisiert er gerne Community Meetups und arbeitet an Open-Source-Projekten wie Keycloak, Spring und Cloud Foundry mit.

Über 1.000 Abonnenten sind up to date!

Die neuesten Tipps, Tricks, Tools und Technologien.
Jede Woche direkt in deine Inbox.

Kostenfrei anmelden und immer auf dem neuesten Stand bleiben!
(Keine Sorge, du kannst dich jederzeit abmelden.)

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht.