Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
May 28, 2021 06:04 am GMT

Keycloak on Distroless

Keycloak is a wonderful piece of software, managed with success by RedHat, to be used as an Identity and Access Management software. RedHat distribute it as a zip package to be run on a machine with a JVM installed or as a container. Nowadays, container is a simpler solution, especially if you are using an orchestrator like Kubernetes.

The Keycloak image is available on the DockerHub or Quay. It provides an important level of configuration through environment variables, which is useful if you are not familiar with WildFly configuration. But, this solution has an important downside, especially for a tool dedicated to security tags are not maintained at OS level over time and has many vulnerabilities.

You can see below, a lot of vulnerabilities in the latest Keycloak image, especially at the OS level. In some case, you can't choose to rely on so many vulnerabilities and need to fix that, or at least reduce them.

$ trivy image jboss/keycloak:13.0.12021-05-26T19:23:14.416+0200    INFO    Detected OS: redhat2021-05-26T19:23:14.416+0200    INFO    Detecting RHEL/CentOS vulnerabilities...2021-05-26T19:23:14.432+0200    INFO    Number of PL dependency files: 621jboss/keycloak:13.0.1 (redhat 8.4)==================================Total: 118 (UNKNOWN: 0, LOW: 49, MEDIUM: 67, HIGH: 2, CRITICAL: 0)...

NOTE: Number of CVEs in an image evolves over time, so reports in this article can be way different if you run it by yourself.

On one side, you can choose to upgrade every packages in the image manually, hoping a fix is available in the official CentOS registry. Another solution is to change the base image to something with less vulnerability like Google Distroless. Those images only contain the runtime for your application and nothing less no shell, no package manager, nothing just your runtime. For Keycloak, we will use the Distroless Java image to sanitize our workload.

Nothing in distroless

Crafting the best Dockerfile possible

The original Keycloak image use a lot of bash scripts to configure the whole system. This is a good idea, but here, we don't have any shell in our Distroless base image, so we will have to extract the application, and the way to launch it from scratch.

Moving Keycloak into Distroless

If we analyse the jboss/keycloak:13.0.1 image with Dive, we can see all Keycloak related files are stored into /opt/jboss/.

dive

We will copy them into our distroless then, with the following Dockerfile:

FROM jboss/keycloak:13.0.1 as baseFROM gcr.io/distroless/java:11-nonrootCOPY --chown=nonroot:nonroot --from=base /opt/jboss /opt/jboss

The execution is pretty simple:

$ docker build -t keycloak-distroless .[+] Building 0.6s (8/8) FINISHED => [internal] load build definition from Dockerfile                       0.0s => => transferring dockerfile: 37B                                        0.0s => [internal] load .dockerignore                                          0.0s => => transferring context: 2B                                            0.0s => [internal] load metadata for gcr.io/distroless/java:11-nonroot         0.5s => [internal] load metadata for docker.io/jboss/keycloak:13.0.1           0.0s => [base 1/1] FROM docker.io/jboss/keycloak:13.0.1                        0.0s => [stage-1 1/2] FROM gcr.io/distroless/java:11-nonroot@sha256:07d017944  0.0s => CACHED [stage-1 2/2] COPY --chown=nonroot:nonroot --from=base /opt/jb  0.0s => exporting to image                                                     0.0s => => exporting layers                                                    0.0s => => writing image sha256:06e849f0ab369043be9c071a446484e2a699a114dd988  0.0s => => naming to docker.io/library/keycloak-distroless                     0.0s

Sadly, if we are launching it like this, we will see the following error:

$ docker run --rm -it -p 8080:8080 keycloak-distrolessError: -jar requires jar file specificationUsage: java [options] <mainclass> [args...]           (to execute a class)   or  java [options] -jar <jarfile> [args...]           (to execute a jar file)   or  java [options] -m <module>[/<mainclass>] [args...]       java [options] --module <module>[/<mainclass>] [args...]           (to execute the main class in a module)   or  java [options] <sourcefile> [args]           (to execute a single source-file program) Arguments following the main class, source file, -jar <jarfile>, -m or --module <module>/<mainclass> are passed as the arguments to main class....

This is because the default ENTRYPOINT of this distroless image want to launch a (fat) JAR, but keycloak is more complex than this, so we will have to find the right ENTRYPOINT for our use case.

Generating the ENTRYPOINT

For this one, we will use the original image to see how Keycloak is launched in its natural state. To do that, we will edit the standalone.sh file to make it more verbose and copy the java command generated from it. We will follow the official documentation to launch keycloak, but we will log into the container to do our magic trick:

# Starting the container with the minimal configuration and log into it thanks to the custom entrypoint$ docker run -it --rm -e DB_VENDOR=h2 --entrypoint=bash jboss/keycloak:13.0.1# From here, we are IN the Keycloak image!# The following command update the standalone.sh file to be a lot verbosebash-4.4$ awk -i inplace 'NR==2 {print "set -x"} 1' /opt/jboss/keycloak/bin/standalone.sh# Finally, we will launch keycloak from here and stop it when we found the line starting with "++ java"bash-4.4$ /opt/jboss/tools/docker-entrypoint.sh=========================================================================  Using Embedded H2 database=========================================================================+ DEBUG_MODE=false+ DEBUG_PORT=8787+ GC_LOG=+ SERVER_OPTS=+ '[' 3 -gt 0 ']'+ case "$1" in+ SERVER_OPTS=' '\''-Djboss.bind.address=172.17.0.2'\'''+ shift+ '[' 2 -gt 0 ']'+ case "$1" in+ SERVER_OPTS=' '\''-Djboss.bind.address=172.17.0.2'\'' '\''-Djboss.bind.address.private=172.17.0.2'\'''+ shift+ '[' 1 -gt 0 ']'+ case "$1" in+ SERVER_OPTS=' '\''-Djboss.bind.address=172.17.0.2'\'' '\''-Djboss.bind.address.private=172.17.0.2'\'' '\''-c=standalone-ha.xml'\'''+ shift+ '[' 0 -gt 0 ']'++ dirname /opt/jboss/keycloak/bin/standalone.sh+ DIRNAME=/opt/jboss/keycloak/bin++ basename /opt/jboss/keycloak/bin/standalone.sh+ PROGNAME=standalone.sh+ GREP=grep+ . /opt/jboss/keycloak/bin/common.sh++ '[' x = x ']'++ COMMON_CONF=/opt/jboss/keycloak/bin/common.conf++ '[' -r /opt/jboss/keycloak/bin/common.conf ']'+ MAX_FD=maximum+ MALLOC_ARENA_MAX=1+ export MALLOC_ARENA_MAX+ cygwin=false+ darwin=false+ linux=false+ solaris=false+ freebsd=false+ other=false+ case "`uname`" in++ uname+ linux=true+ false++ cd /opt/jboss/keycloak/bin/..++ pwd+ RESOLVED_JBOSS_HOME=/opt/jboss/keycloak+ '[' x/opt/jboss/keycloak = x ']'++ cd /opt/jboss/keycloak++ pwd+ SANITIZED_JBOSS_HOME=/opt/jboss/keycloak+ '[' /opt/jboss/keycloak '!=' /opt/jboss/keycloak ']'+ export JBOSS_HOME+ '[' x = x ']'+ RUN_CONF=/opt/jboss/keycloak/bin/standalone.conf+ '[' -r /opt/jboss/keycloak/bin/standalone.conf ']'+ . /opt/jboss/keycloak/bin/standalone.conf++ '[' x = x ']'++ JBOSS_MODULES_SYSTEM_PKGS=org.jboss.byteman++ '[' x = x ']'++ JAVA_OPTS='-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true'++ JAVA_OPTS='-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true'++ JAVA_OPTS='-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true '+ '[' false = true ']'+ '[' x = x ']'+ '[' x '!=' x ']'+ JAVA=java+ true+ CONSOLIDATED_OPTS='-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true   '\''-Djboss.bind.address=172.17.0.2'\'' '\''-Djboss.bind.address.private=172.17.0.2'\'' '\''-c=standalone-ha.xml'\'''+ for var in $CONSOLIDATED_OPTS++ echo -Xms64m++ tr -d \'+ p=-Xms64m+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo -Xmx512m++ tr -d \'+ p=-Xmx512m+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo -XX:MetaspaceSize=96M++ tr -d \'+ p=-XX:MetaspaceSize=96M+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo -XX:MaxMetaspaceSize=256m++ tr -d \'+ p=-XX:MaxMetaspaceSize=256m+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo -Djava.net.preferIPv4Stack=true++ tr -d \'+ p=-Djava.net.preferIPv4Stack=true+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo -Djboss.modules.system.pkgs=org.jboss.byteman++ tr -d \'+ p=-Djboss.modules.system.pkgs=org.jboss.byteman+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo -Djava.awt.headless=true++ tr -d \'+ p=-Djava.awt.headless=true+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo ''\''-Djboss.bind.address=172.17.0.2'\'''++ tr -d \'+ p=-Djboss.bind.address=172.17.0.2+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo ''\''-Djboss.bind.address.private=172.17.0.2'\'''++ tr -d \'+ p=-Djboss.bind.address.private=172.17.0.2+ case $p in+ for var in $CONSOLIDATED_OPTS++ echo ''\''-c=standalone-ha.xml'\'''++ tr -d \'+ p=-c=standalone-ha.xml+ case $p in+ false+ false+ false+ false+ '[' x = x ']'+ JBOSS_BASE_DIR=/opt/jboss/keycloak/standalone+ '[' x = x ']'+ JBOSS_LOG_DIR=/opt/jboss/keycloak/standalone/log+ '[' x = x ']'+ JBOSS_CONFIG_DIR=/opt/jboss/keycloak/standalone/configuration+ '[' x = x ']'+ JBOSS_MODULEPATH=/opt/jboss/keycloak/modules+ false+ '[' '' '!=' true ']'++ echo -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true++ grep '\-d64'+ JVM_D64_OPTION=++ echo -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true++ grep '\-d32'+ JVM_D32_OPTION=++ echo -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true++ grep '\-server'+ SERVER_SET=++ echo -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true++ grep '\-client'+ CLIENT_SET=+ '[' x '!=' x ']'+ '[' x '!=' x ']'+ false+ '[' x = x -a x = x ']'+ false+ PREPEND_JAVA_OPTS=' -server'+ setModularJdk+ java --add-modules=java.se -version+ MODULAR_JDK=true+ '[' '' = true ']'+ setDefaultModularJvmOptions -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true+ setModularJdk+ java --add-modules=java.se -version+ MODULAR_JDK=true+ '[' true = true ']'++ echo -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true++ grep '\-\-add\-modules'+ DEFAULT_MODULAR_JVM_OPTIONS=+ '[' x = x ']'+ DEFAULT_MODULAR_JVM_OPTIONS=' --add-exports=java.base/sun.nio.ch=ALL-UNNAMED'+ DEFAULT_MODULAR_JVM_OPTIONS=' --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED'+ DEFAULT_MODULAR_JVM_OPTIONS=' --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED'+ JAVA_OPTS='-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true   --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED'+ JAVA_OPTS=' -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true   --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED'++ echo -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED++ grep 'java\.security\.manager'+ SECURITY_MANAGER_SET=+ '[' x '!=' x ']'+ MODULE_OPTS=+ '[' '' = true ']'++ echo ''++ grep '\-javaagent:'+ AGENT_SET=+ '[' x '!=' x ']'+ echo ==================================================================================================================================================+ echo ''+ echo '  JBoss Bootstrap Environment'  JBoss Bootstrap Environment+ echo ''+ echo '  JBOSS_HOME: /opt/jboss/keycloak'  JBOSS_HOME: /opt/jboss/keycloak+ echo ''+ echo '  JAVA: java'  JAVA: java+ echo ''+ echo '  JAVA_OPTS:  -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true   --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED'  JAVA_OPTS:  -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true   --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED+ echo ''+ echo ==================================================================================================================================================+ echo ''+ true+ '[' x1 = x ']'+ eval '"java"' '-D"[Standalone]"' -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED '"-Dorg.jboss.boot.log.file=/opt/jboss/keycloak/standalone/log/server.log"' '"-Dlogging.configuration=file:/opt/jboss/keycloak/standalone/configuration/logging.properties"' -jar '"/opt/jboss/keycloak/jboss-modules.jar"' -mp '"/opt/jboss/keycloak/modules"' org.jboss.as.standalone '-Djboss.home.dir="/opt/jboss/keycloak"' '-Djboss.server.base.dir="/opt/jboss/keycloak/standalone"' ' '\''-Djboss.bind.address=172.17.0.2'\'' '\''-Djboss.bind.address.private=172.17.0.2'\'' '\''-c=standalone-ha.xml'\''' '&'+ JBOSS_PID=122+ trap 'kill -HUP  122' HUP++ java '-D[Standalone]' -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED -Dorg.jboss.boot.log.file=/opt/jboss/keycloak/standalone/log/server.log -Dlogging.configuration=file:/opt/jboss/keycloak/standalone/configuration/logging.properties -jar /opt/jboss/keycloak/jboss-modules.jar -mp /opt/jboss/keycloak/modules org.jboss.as.standalone -Djboss.home.dir=/opt/jboss/keycloak -Djboss.server.base.dir=/opt/jboss/keycloak/standalone -Djboss.bind.address=172.17.0.2 -Djboss.bind.address.private=172.17.0.2 -c=standalone-ha.xml+ trap 'kill -TERM 122' INT+ trap 'kill -QUIT 122' QUIT+ trap 'kill -PIPE 122' PIPE+ trap 'kill -TERM 122' TERM+ '[' x '!=' x ']'+ WAIT_STATUS=128+ '[' 128 -ge 128 ']'+ wait 12218:08:24,393 INFO  [org.jboss.modules] (main) JBoss Modules version 1.11.0.Final18:08:25,034 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.12.Final18:08:25,050 INFO  [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final18:08:25,219 INFO  [org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: Keycloak 13.0.1 (WildFly Core 15.0.1.Final) starting18:08:25,412 INFO  [org.jboss.vfs] (MSC service thread 1-4) VFS000002: Failed to clean existing content for temp file provider of type temp. Enable DEBUG level log to find what caused this18:08:26,228 INFO  [org.wildfly.security] (ServerService Thread Pool -- 22) ELY00001: WildFly Elytron version 1.15.3.Final^Cbash-4.4$ exit$ 

In the huge starting log, we can see the following command, starting with ++ java:

java '-D[Standalone]' -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED --add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED -Dorg.jboss.boot.log.file=/opt/jboss/keycloak/standalone/log/server.log -Dlogging.configuration=file:/opt/jboss/keycloak/standalone/configuration/logging.properties -jar /opt/jboss/keycloak/jboss-modules.jar -mp /opt/jboss/keycloak/modules org.jboss.as.standalone -Djboss.home.dir=/opt/jboss/keycloak -Djboss.server.base.dir=/opt/jboss/keycloak/standalone -Djboss.bind.address=172.17.0.2 -Djboss.bind.address.private=172.17.0.2 -c=standalone-ha.xml

This is the java command we will put inside our Dockerfile, as an ENTRYPOINT to make Keycloak start.

FROM jboss/keycloak:13.0.1 as baseFROM gcr.io/distroless/java:11-nonrootCOPY --chown=nonroot:nonroot --from=base /opt/jboss /opt/jbossENTRYPOINT [ "java", "-D[Standalone]", "-server", "-Xms64m", "-Xmx512m", "-XX:MetaspaceSize=96M", "-XX:MaxMetaspaceSize=256m", "-Djava.net.preferIPv4Stack=true", "-Djboss.modules.system.pkgs=org.jboss.byteman", "-Djava.awt.headless=true", "--add-exports=java.base/sun.nio.ch=ALL-UNNAMED", "--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED", "--add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED", "-Dorg.jboss.boot.log.file=/opt/jboss/keycloak/standalone/log/server.log", "-Dlogging.configuration=file:/opt/jboss/keycloak/standalone/configuration/logging.properties", "-jar", "/opt/jboss/keycloak/jboss-modules.jar", "-mp", "/opt/jboss/keycloak/modules", "org.jboss.as.standalone", "-Djboss.home.dir=/opt/jboss/keycloak", "-Djboss.server.base.dir=/opt/jboss/keycloak/standalone", "-Djboss.bind.address=0.0.0.0", "-Djboss.bind.address.private=1720.0.0.0", "-c=standalone.xml" ]

NOTE: You can tune this command to increase or decrease the memory setup, the private/public bind address of your keycloak instance and many other parameters. Here, we changed the configuration file used (-c=standalone.xml instead of -c=standalone-ha.xml for simplicity reasons) and the bound ip adresses (to 0.0.0.0)

If we build and run this, we will be able to access the Keycloak UI:

$ docker build -t keycloak-distroless .[+] Building 0.6s (8/8) FINISHED => [internal] load build definition from Dockerfile                       0.0s => => transferring dockerfile: 37B                                        0.0s => [internal] load .dockerignore                                          0.0s => => transferring context: 2B                                            0.0s => [internal] load metadata for gcr.io/distroless/java:11-nonroot         0.5s => [internal] load metadata for docker.io/jboss/keycloak:13.0.1           0.0s => [base 1/1] FROM docker.io/jboss/keycloak:13.0.1                        0.0s => [stage-1 1/2] FROM gcr.io/distroless/java:11-nonroot@sha256:07d017944  0.0s => CACHED [stage-1 2/2] COPY --chown=nonroot:nonroot --from=base /opt/jb  0.0s => exporting to image                                                     0.0s => => exporting layers                                                    0.0s => => writing image sha256:100908720c19018f2408bb53a5d78ef3d9eb51391b165  0.0s => => naming to docker.io/library/keycloak-distroless                     0.0s$ docker run --rm -it -p 8080:8080 keycloak-distroless18:15:22,645 INFO  [org.jboss.modules] (main) JBoss Modules version 1.11.0.Final18:15:23,283 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.12.Final18:15:23,292 INFO  [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final18:15:23,452 INFO  [org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: Keycloak 13.0.1 (WildFly Core 15.0.1.Final) starting18:15:23,694 INFO  [org.jboss.vfs] (MSC service thread 1-5) VFS000002: Failed to clean existing content for temp file provider of type temp. Enable DEBUG level log to find what caused this18:15:24,457 INFO  [org.wildfly.security] (ServerService Thread Pool -- 22) ELY00001: WildFly Elytron version 1.15.3.Final......18:15:44,642 INFO  [org.wildfly.extension.undertow] (ServerService Thread Pool -- 66) WFLYUT0021: Registered web context: '/auth' for server 'default-server'18:15:44,778 INFO  [org.jboss.as.server] (ServerService Thread Pool -- 46) WFLYSRV0010: Deployed "keycloak-server.war" (runtime-name : "keycloak-server.war")18:15:44,886 INFO  [org.jboss.as.server] (Controller Boot Thread) WFLYSRV0212: Resuming server18:15:44,892 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 13.0.1 (WildFly Core 15.0.1.Final) started in 22800ms - Started 692 of 977 services (686 services are lazy, passive or on-demand)18:15:44,896 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management18:15:44,896 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

If we try to access http://localhost:8080/, we can see the following page .

keycloak-ui-from-distroless

This is a good start, but this is just the minimal setup with H2 database, we often want something more robust for production!

Generating the perfect configuration

The jboss/keycloak image use a lot of environment variables to configure keycloak (and the underlying standalone.xml) for you but in our case, we can't use that because:

  • We don't have a shell to run those scripts.
  • We don't want to run those scripts at every startup / scale-up.

So, we will have to steal the generated standalone.xml file from the original container, post start-up, and include it in our container. For this example, I will use PostgreSQL as our main database.

To do this, I will use two shells side-by-side, one to launch Keycloak, and the other one to fetch the configuration.

# In the first shell# Creation of a docker networkfirst-shell$ docker network create keycloak-network4da77163731b584bef2c6d0b00386b9d62e31fa216204c6c6795f66e109ba1a6# Launching PostgreSQL linked to the network previously createdfirst-shell$ docker run --rm -d --name postgres --net keycloak-network \-e POSTGRES_DB=keycloak \-e POSTGRES_USER=keycloak \-e POSTGRES_PASSWORD=password postgres229816da42707e772542f1b089c616a2333a6fbe1aea2be7efe658d6f2c934a1first-shell$ docker run -it --rm --name keycloak \-e DB_ADDR=postgres \-e DB_USER=keycloak \-e DB_PASSWORD=password \-e KEYCLOAK_USER=foo \-e KEYCLOAK_PASSWORD=bar \--net keycloak-network jboss/keycloak:13.0.1=========================================================================  Using PostgreSQL database=========================================================================18:32:25,172 INFO  [org.jboss.modules] (CLI command executor) JBoss Modules version 1.11.0.Final18:32:25,279 INFO  [org.jboss.msc] (CLI command executor) JBoss MSC version 1.4.12.Final18:32:25,302 INFO  [org.jboss.threads] (CLI command executor) JBoss Threads version 2.4.0.Final18:32:25,453 INFO  [org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: Keycloak 13.0.1 (WildFly Core 15.0.1.Final) starting...18:32:59,128 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management18:32:59,129 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990 

In another shell, while the previous is still running, we will execute the following command to get the standalone.xml file used to configure Keycloak:

second-shell$ docker cp keycloak:/opt/jboss/keycloak/standalone/configuration/standalone.xml .second-shell$ lsstandalone.xml# We can now stop the keycloak containersecond-shell$ docker stop keycloakkeycloaksecond-shell$

Now, we will start the Distroless Keycloak and mount the standalone.xml inside the container.

$ docker run --rm -it -e DB_USER=keycloak -e DB_PASSWORD=password --net keycloak-network -v $(pwd)/standalone.xml:/opt/jboss/keycloak/standalone/configuration/standalone.xml -p 8080:8080 keycloak-distroless19:42:20,707 INFO  [org.jboss.modules] (main) JBoss Modules version 1.11.0.Final19:42:21,317 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.12.Final19:42:21,329 INFO  [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final19:42:21,470 INFO  [org.jboss.as] (MSC service thread 1-2) WFLYSRV0049: Keycloak 13.0.1 (WildFly Core 15.0.1.Final) starting19:42:21,651 INFO  [org.jboss.vfs] (MSC service thread 1-1) VFS000002: Failed to clean existing content for temp file provider of type temp. Enable DEBUG level log to find what caused this19:42:22,577 INFO  [org.wildfly.security] (ServerService Thread Pool -- 20) ELY00001: WildFly Elytron version 1.15.3.Final...19:43:58,356 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 13.0.1 (WildFly Core 15.0.1.Final) started in 17828ms - Started 595 of 873 services (584 services are lazy, passive or on-demand)19:43:58,362 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management19:43:58,363 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

And Voila!

keycloak-auth
keycloak-login
keycloak-ui

What about security?

The original and main purpose of this manipulation is to reduce the number of CVEs present in our image. We will be able to compare it using trivy again on our newly image.

$ trivy image keycloak-distroless2021-05-26T21:11:15.959+0200    INFO    Detected OS: debian2021-05-26T21:11:15.959+0200    INFO    Detecting Debian vulnerabilities...2021-05-26T21:11:15.963+0200    INFO    Number of PL dependency files: 6212021-05-26T21:11:15.963+0200    INFO    Detecting jar vulnerabilities...keycloak-distroless (debian 10.9)=================================Total: 27 (UNKNOWN: 0, LOW: 23, MEDIUM: 3, HIGH: 1, CRITICAL: 0)

We can see, our image contain fewer vulnerabilities, at LOW, MEDIUM or HIGH level. Again, this depends on when you are doing this analysis. With the solution provided in this article, you'll be able to rebuild your keycloak on a new, up-to-date, Distroless base image without updating keycloak. With the original keycloak image, the keycloak version is tied to the OS version (and security flaws).

NOTE: The jboss/keycloak:13.0.1 was released few hours before the creation of this article while the distroless/java-debian10:non-root was released 1 month ago. This is the worst comparison scenario possible for the Distroless base image.

dive-distroless

Another benefit of this alternative is to create a smaller image for keycloak. The previous dive reports stated 698 MB for the official image when our custom image weight only 519 MB, so around 179 MB reduction , and I'm sure we can remove almost 100MB by removing all useless binaries in the image (useless drivers, command line tools, documentation).

Conclusion

With this article, you should be able to build, from the official jboss/keycloak image a custom one based on the Distroless/java and even fix CVEs by doing it again when a new version of Distroless/java image is released.

I hope you liked it, you can find all the sample files from this article in this GitLab repository: davinkevin/keycloak-distroless.


Original Link: https://dev.to/stack-labs/keycloak-on-distroless-12ng

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To