the/experts. Blog

Cover image for Keycloak - Configuration as Code Pt.2
Maik Kingma
Maik Kingma

Posted on • Updated on

Keycloak - Configuration as Code Pt.2

Keycloak is an open-source software solution for identity and access management, providing features such as single sign-on, user authentication, and authorization services. There are various ways how you can configure your Keycloak instance. In this hands-on blog series, we will work towards a fully automated configuration, using only Java code. Whether you are a Keycloak beginner or an experienced developer, this blog series will provide valuable insights into the configuration of Keycloak using Java code and how it can make your life easier.

What we have seen so far:

  1. Identity and Access Management with Keycloak - In this blog post, we get to know some basic building blocks that we have at our disposal in Keycloak.
  2. Keycloak - Configuration as Code Pt. 1 - In this blog post, we cover how to create the basic project setup for our 'Keycloak - Configuration as Code' endeavor.

We will continue where we left off. In case you want to skip the previous step, feel free to check out the base project setup here. From there we start with an empty Maven project containing two Maven submodules with their respective POMs. Our goal in this post is to extend our code base so that we can start up our Keycloak instance using nothing but the two commands

mvn clean package && docker compose up
Enter fullscreen mode Exit fullscreen mode

The Keycloak Distribution

Once again, there are multiple ways to get started. We could make use of the Keycloak docker base image, provided by Red Hat. However, that would leave us less flexible when it comes to fixing security issues (reported CVEs) in the dependency tree of Keycloak itself. Inspired by a blog post by Thomas Darimont and Sebastian Rose, two colleagues from Germany, on Keycloak.X, but secure – without vulnerable libraries, we are not going to use this base image. Instead, we add the Keycloak distribution as a dependency to our project. If you are interested in the benefits of doing so, feel free to read their interesting blog. In summary,

a custom distribution can support in the following:

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

(Quote from Keycloak.X, but secure – without vulnerable libraries)

Updating the Keycloak submodule

We start by updating the file keycloak/pom.xml with the following content:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>nl.the_experts.keycloak</groupId>
        <artifactId>keycloak-configascode-demo</artifactId>
        <version>0.0.1-local</version>
    </parent>
    <artifactId>keycloak</artifactId>
    <dependencies>
        <dependency>
            <!-- Keycloak Distribution -->
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-quarkus-dist</artifactId>
            <type>zip</type>
        </dependency>
        <dependency>
            <!-- Keycloak Quarkus Server Libraries-->
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-quarkus-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>keycloak-${project.version}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Enter fullscreen mode Exit fullscreen mode

What we added is the dependencies element with in it the Keycloak distribution and the Keycloak Quarkus server. Notice that the distribution is in zip format. We handle this by having added a reference to the root POM's maven-dependency-plugin in the build section, which contains an executionto unpack the zip file into our target directory.
If we now run mvn clean package, our target directory in the keycloak submodule should look something like this:

Target Directory Content

Docker setup

As previously announced, we will use Docker to containerize our solution. In doing so, we create a portable solution that can easily be started on any OS that has Java, Maven and Docker Desktop installed.

We will add two files to the root of our project. Firstly,
./Dockerfile with content:

FROM registry.access.redhat.com/ubi8-minimal:8.7 AS builder
RUN microdnf update -y && \
    microdnf install -y java-17-openjdk-headless && microdnf clean all && rm -rf /var/cache/yum/* && \
    echo "keycloak:x:0:root" >> /etc/group && \
    echo "keycloak:x:1000:0:keycloak user:/opt/keycloak:/sbin/nologin" >> /etc/passwd

COPY --chown=keycloak:keycloak keycloak/target/keycloak-20.0.3  /opt/keycloak

USER 1000

RUN /opt/keycloak/bin/kc.sh build --db=postgres

FROM registry.access.redhat.com/ubi8-minimal:8.7

RUN microdnf update -y && \
    microdnf reinstall -y tzdata && \
    microdnf install -y java-17-openjdk-headless && \
    microdnf clean all && rm -rf /var/cache/yum/* && \
    echo "keycloak:x:0:root" >> /etc/group && \
    echo "keycloak:x:10001:0:keycloak user:/opt/keycloak:/sbin/nologin" >> /etc/passwd && \
    ln -sf /usr/share/zoneinfo/Europe/Amsterdam /etc/localtime # set timezone

COPY --from=builder --chown=1000:0 /opt/keycloak /opt/keycloak

USER 1000
WORKDIR /opt/keycloak-config

EXPOSE 8080
EXPOSE 8443

ENTRYPOINT ["/opt/keycloak/bin/kc.sh", "start"]
Enter fullscreen mode Exit fullscreen mode

One may notice the use of a builder in our Dockerfile. Since the introduction and support of multistage builds in Docker 17.06 CE, the builder pattern makes use of two Docker images, one to create a base image for building assets and subsequently a second image to run the assets.

Since we added the Keycloak Distribution as a dependency, we are free to pick a base image of our choosing. The Universal Base Image Minimal ubi8-minimal:8.7 by Red Hat is a stripped down image that uses microdnf as a package manager. Furthermore, JDK 17 is installed, required user permissions set, and eventually we copy our packaged Keycloak distribution into the builder image.
In the runner image section, after initialising the base image with required libraries, we only copy the /opt/keycloak directory from the builder into the runner image and expose ports 8080 and 8443. Last but not least, the default entrypoint for our runner image is defined.

Secondly, we need to add the file ./docker-compose.yml with content:

version: '3.9'

services:
  postgres:
    image: postgres:14.4
    container_name: postgres
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_PASSWORD: postgres
      POSTGRES_USER: postgres
    ports:
      - '5432:5432'
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    build: .
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      - KC_LOG_LEVEL=info
      - KEYCLOAK_ADMIN=admin
      - KEYCLOAK_ADMIN_PASSWORD=admin
      - KC_HOSTNAME_STRICT_HTTPS=false
      - KC_DB=postgres
      - KC_DB_SCHEMA=public
      - KC_DB_URL_PORT=5432
      - KC_DB_URL_HOST=postgres
      - KC_DB_USERNAME=postgres
      - KC_DB_PASSWORD=postgres
      - KC_HEALTH_ENABLED=true
      - KC_METRICS_ENABLED=false
    container_name: keycloak
    entrypoint: /opt/keycloak/bin/kc.sh start-dev --http-enabled=true --cache=local
    ports:
      - "8080:8080"
    healthcheck:
      test: [ "CMD-SHELL", "wget -q -O /dev/null http://keycloak:8080/" ]
      interval: 10s
      timeout: 10s
      retries: 10
      start_period: 10s
Enter fullscreen mode Exit fullscreen mode

Fairly straight forward, this file allows us to compose our required containers in our local environment.
The first listed service is a simple Postgres DB. Once this service becomes healthy (health check defined) our second service Keycloak, which depends on the availability of our DB, will be built and launched.

Launching Keycloak

As required by our self-defined acceptance criteria, we can now launch our Keycloak instance by running the following commands from the root directory of our project:

mvn clean package && docker compose up
Enter fullscreen mode Exit fullscreen mode

In the logging you will see that postgres is launched first and after a brief waiting period, caused by the health checks, Keycloak will start up. Eventually, you will be able to access the admin console via http://localhost:8080. The credentials used in this tutorial are:

  • username: admin
  • password: admin

After logging in, you should see the Admin UI:

Keycloak AdminUI

Summary

In this post, we have seen how to build our own Keycloak container image using not the Keycloak base image but the Keycloak distribution as a project dependency + a minimal base image in our Dockerfile. The code of this tutorial step can be found here.

We have now laid down the groundwork. In the next post, we will get started with the actual configuration as code using Java.

Stay tuned for more!

Discussion (0)