the/experts. Blog

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

Posted on • Updated on

Keycloak - Configuration as Code Pt.1

Most projects that use Keycloak make use of some kind of configuration automation. While you can literally do almost anything via the admin UI of Keycloak, it also supports other ways of configuration, such as:

  • JSON file imports
  • Shell script configuration via the Keycloak admin CLI
  • Configuration as code (i.e. the Keycloak admin REST client in Java)

When using Keycloak in bigger (enterprise) projects, you will want to use a more sophisticated way of configuring your instance than just “clicking things together” in the admin interface.
In the following few blog posts, and as part of the “Configuration as code with Keycloak” series, we will create a brand-new project, that will eventually result in a completely containerized Keycloak instance that is entirely configured by Java code.

In detail, our acceptance criteria for this demo project include the following:

  • The output of our build is one docker image that includes Keycloak, our custom Keycloak extensions code and the configuration code
  • The latest version of Keycloak is used (>=20.0.3 at time of writing)
  • All configuration of our Keycloak instance has to be achieved in form of code. If we clear the underlying database, the only thing we need to do is re-start our Keycloak instance and re-run the configuration script.
  • Keycloak can be made environment aware (i.e. DEV, ACC, PRD) via configuration properties

The Why

The first question we should ask ourselves is why go through the hassle of writing (potentially) a lot of Java code if there exists the possibility of using the admin UI of Keycloak to configure the things one needs and subsequently exporting them into a JSON file we can simply auto-import later on?
Simply put, there are the following advantages:

  • Our configuration becomes unit testable.
  • Version control on Java classes is easier to manage than on 15k+ lines of code JSON files.
  • The team is in control of the code it writes. The export function to a JSON file by Keycloak we do not, and we have no influence on the order of export.
  • Configuration code becomes readable and easier to work with.
  • We have fine-grained control over dependencies used in our Keycloak container image and as such have more flexibility when it comes to CVE patches.

In the first blog post of this series, the knowledge base was created. We learnt what Keycloak is capable of in general terms and what basic building blocks are available in Keycloak. We will now start to create our base project setup using Maven.

The Maven Project Setup

In this section we will create the base directory structure and initialize the maven project.

The directory structure

Create your root directory with and switch the terminal location into it by running

mkdir keycloak-configAsCode-demo && cd keycloak-configAsCode-demo
Enter fullscreen mode Exit fullscreen mode

We now create two maven artifacts called keycloak and java-configuration by running the following two commands in our terminal window:

mvn archetype:generate -DgroupId=nl.the_experts.keycloak -DartifactId=java-configuration -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
Enter fullscreen mode Exit fullscreen mode

and

mvn archetype:generate -DgroupId=nl.the_experts.keycloak -DartifactId=keycloak -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false
Enter fullscreen mode Exit fullscreen mode

Cleanup of generated files

Remove the generated files java-configuration/src/main/java/nl/the_experts/keycloak/App.java and keycloak/src/main/java/nl/the_experts/keycloak/App.java and the corresponding test files.

Create the parent pom.xml file

In the root directory of the project create a file named 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>

    <groupId>nl.the_experts.keycloak</groupId>
    <artifactId>keycloak-configascode-demo</artifactId>
    <version>0.0.1-local</version>
    <packaging>pom</packaging>

    <modules>
        <module>keycloak</module>
        <module>java-configuration</module>
    </modules>

    <properties>
        <!-- Java -->
        <java.version>17</java.version>

        <!-- Maven -->
        <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
        <maven.shade.plugin.version>3.3.0</maven.shade.plugin.version>
        <maven.jar.plugin.version>3.2.2</maven.jar.plugin.version>

        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ss'Z'</maven.build.timestamp.format>

        <!-- Project specific -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <!-- Keycloak -->
        <keycloak.version>20.0.3</keycloak.version>
        <quarkus.version>2.13.3.Final</quarkus.version>
        <quarkus.resteasy.version>2.13.5.Final</quarkus.resteasy.version>
        <quarkus.native.builder-image>mutable-jar</quarkus.native.builder-image>

        <!-- Plugins -->
        <build-helper-maven-plugin.version>3.3.0</build-helper-maven-plugin.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <!--Quarkus-->
            <dependency>
                <groupId>io.quarkus.arc</groupId>
                <artifactId>arc</artifactId>
                <version>${quarkus.version}</version>
            </dependency>
            <!--keycloak-->
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-quarkus-server</artifactId>
                <version>${keycloak.version}</version>
            </dependency>
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-quarkus-dist</artifactId>
                <version>${keycloak.version}</version>
                <type>zip</type>
            </dependency>
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-admin-client</artifactId>
                <version>${keycloak.version}</version>
            </dependency>
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-core</artifactId>
                <version>${keycloak.version}</version>
            </dependency>
            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-services</artifactId>
                <version>${keycloak.version}</version>
            </dependency>

            <!--REST-->
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-resteasy-jackson</artifactId>
                <version>${quarkus.resteasy.version}</version>
            </dependency>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-rest-client</artifactId>
                <version>${quarkus.version}</version>
            </dependency>
            <dependency>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-rest-client-jackson</artifactId>
                <version>${quarkus.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>${maven-surefire-plugin.version}</version>
                    <configuration>
                        <excludes>
                            <exclude>**/*IntegrationTest.java</exclude>
                        </excludes>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>unpack-keycloak-server-distribution</id>
                            <phase>package</phase>
                            <goals>
                                <goal>unpack</goal>
                            </goals>
                            <configuration>
                                <artifactItems>
                                    <artifactItem>
                                        <groupId>org.keycloak</groupId>
                                        <artifactId>keycloak-quarkus-dist</artifactId>
                                        <type>zip</type>
                                        <outputDirectory>target</outputDirectory>
                                    </artifactItem>
                                </artifactItems>
                                <excludes>**/lib/**</excludes>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>io.quarkus</groupId>
                    <artifactId>quarkus-maven-plugin</artifactId>
                    <version>${quarkus.version}</version>
                    <configuration>
                        <finalName>keycloak</finalName>
                        <buildDir>${project.build.directory}/keycloak-${keycloak.version}</buildDir>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>build</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>
Enter fullscreen mode Exit fullscreen mode

Let's briefly analyze the different sections we have now added to our root parent POM. In preparation for our base Keycloak setup the most basic blocks are already in there. We will use those in Pt.2 of this blog series.

  • In the modules element we register our two submodules we previously created.
  • In the properties element we maintain all the (version) variables that we will use throughout our project.
  • In the dependencyManagement element, all dependencies and their versions we want to use should be added.
  • Contained in the build section you will find the pluginManagement where we define the plugins that we will use later on throughout our project.

This is not a Maven tutorial though, so if you want to learn more about how these elements work, you can find all the information here. Let us continue with our setup so we can actually write some code for Keycloak.

Updating the child pom.xml files

All that remains is to update the child POMs and we are ready to go. Please replace the content of ./keycloak/pom.xml with the following snippet:

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

</project>
Enter fullscreen mode Exit fullscreen mode

and accordingly, ./java-configuration/pom.xml with this snippet:

<?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>java-configuration</artifactId>
</project>
Enter fullscreen mode Exit fullscreen mode

After having completed the above steps, your project tree should look something like this:

The project tree

Package our maven project

You should now be able to package the parent maven project by running the following command from the project root directory:

mvn clean package
Enter fullscreen mode Exit fullscreen mode

Your result should look something like this:

Maven package result

For now, our recently created project and its submodules are completely empty. We have prepared some build steps in our pluginManagement but we are not using that yet. In the upcoming blog Keycloak - Configuration as Code Pt.2, we will continue with our project by setting up a clean and basic Keycloak distribution that we can run within a container platform such as Docker.

Stay tuned for more.

The code from this tutorial step can be found here.

Discussion (0)