Skip to content

Commit

Permalink
Updated to V24 and added custom SPI to allow setting a fixed issuer i…
Browse files Browse the repository at this point in the history
…n generated tokens
  • Loading branch information
richturner committed Apr 24, 2024
1 parent 85ba20e commit b721a0b
Show file tree
Hide file tree
Showing 49 changed files with 222 additions and 21 deletions.
20 changes: 12 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Keycloak image built for postgresql support with theme handling customisation
# to always fallback to standard openremote theme.
# ------------------------------------------------------------------------------------
ARG VERSION=23.0
ARG VERSION=24.0
FROM registry.access.redhat.com/ubi9 AS ubi-micro-build
MAINTAINER [email protected]

Expand All @@ -24,13 +24,14 @@ ENV KC_FEATURES=token-exchange
ENV KC_DB=postgres
ENV KC_HTTP_RELATIVE_PATH=/auth

# Install openremote theme
ADD --chown=keycloak:keycloak build/image/openremote-theme.jar /opt/keycloak/providers
# Install custom providers
ADD --chown=keycloak:keycloak build/image/openremote-theme-provider.jar /opt/keycloak/providers
ADD --chown=keycloak:keycloak build/image/openremote-issuer-provider.jar /opt/keycloak/providers

WORKDIR /opt/keycloak

# Build custom image and copy into this new image
RUN /opt/keycloak/bin/kc.sh build
RUN /opt/keycloak/bin/kc.sh build --spi-initializer-provider=issuer

FROM quay.io/keycloak/keycloak:${VERSION}

Expand Down Expand Up @@ -58,16 +59,19 @@ ENV KC_DB_USERNAME=postgres
ENV KC_DB_PASSWORD=postgres
ENV KC_HOSTNAME=localhost
ENV KC_HTTP_ENABLED=true
ENV KC_PROXY_HEADERS=forwarded
# Pre V24 proxy setting
ENV KC_PROXY=edge
# V24+ proxy setting
ENV KC_PROXY_HEADERS=xforwarded
ENV KC_LOG_LEVEL=info
ENV KEYCLOAK_ADMIN=admin
ENV KEYCLOAK_ADMIN_PASSWORD=secret
ENV KC_LOG_LEVEL=info
ENV KEYCLOAK_DEFAULT_THEME=openremote
ENV KEYCLOAK_ACCOUNT_THEME=openremote
ENV KEYCLOAK_WELCOME_THEME=keycloak

HEALTHCHECK --interval=3s --timeout=3s --start-period=30s --retries=120 CMD curl --fail --silent http://localhost:8080/auth || exit 1
HEALTHCHECK --interval=3s --timeout=3s --start-period=30s --retries=120 CMD curl --head -fsS http://localhost:8080/auth/health/ready || exit 1

EXPOSE 8080

ENTRYPOINT /opt/keycloak/bin/kc.sh ${KEYCLOAK_START_COMMAND:-start} --optimized --spi-theme-login-default=${KEYCLOAK_LOGIN_THEME:-openremote} --spi-theme-account-theme=${KEYCLOAK_ACCOUNT_THEME:-openremote} --spi-theme-welcome-theme=${KEYCLOAK_WELCOME_THEME:-keycloak} --spi-theme-admin-theme=${KEYCLOAK_ADMIN_THEME:-keycloak} ${KEYCLOAK_START_OPTS:-}
ENTRYPOINT /opt/keycloak/bin/kc.sh ${KEYCLOAK_START_COMMAND:-start} --optimized --spi-initializer-issuer-base-uri=${KEYCLOAK_ISSUER_BASE_URI:-} --spi-theme-login-default=${KEYCLOAK_LOGIN_THEME:-openremote} --spi-theme-account-theme=${KEYCLOAK_ACCOUNT_THEME:-openremote} --spi-theme-welcome-theme=${KEYCLOAK_WELCOME_THEME:-keycloak} --spi-theme-admin-theme=${KEYCLOAK_ADMIN_THEME:-keycloak} ${KEYCLOAK_START_OPTS:-}
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

[![Docker Image](https://github.com/openremote/keycloak/actions/workflows/keycloak.yml/badge.svg)](https://github.com/openremote/keycloak/actions/workflows/keycloak.yml)

Keycloak docker image built for `postgres` with openremote theme embedded and set as default and also sets the request path to `/auth` (like older versions of Keycloak to simplify usage behind a reverse proxy).
Keycloak docker image built for `postgres` with:

* Default env variable values to assume running behind a reverse proxy sending `X-Forwarded-*` headers (env variables can be changed see keycloak documentation)
* Enables metrics and health endpoints by default
* Adds custom functionality to allow token 'issuer' to be fixed by setting `KEYCLOAK_ISSUER_BASE_URI` (e.g. `KEYCLOAK_ISSUER_BASE_URI: https://192.168.1.2/auth`)
this is to allow a private deployment to be accessed over a reverse tunnel, when using this you also need to set the following but precaution should be taken to validate the `Host` header in the reverse proxy:
* `KC_HOSTNAME: `
* `KC_HOSTNAME_STRICT: false`
* OpenRemote theme embedded and set as default
* Request path to `/auth` (like older versions of Keycloak to simplify usage behind a reverse proxy)

## Working on the OpenRemote theme
The openremote theme template files are located in `src/main/resources/theme/openremote`; to work on the OpenRemote theme use:
Expand Down
12 changes: 0 additions & 12 deletions build.gradle

This file was deleted.

39 changes: 39 additions & 0 deletions fixed-issuer/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
plugins {
id 'java'
}

configurations {
jarLibs
}

repositories {
mavenCentral()
}

dependencies {
implementation 'org.keycloak:keycloak-services:23.0.7'
implementation 'org.keycloak:keycloak-server-spi:23.0.7'
implementation 'net.bytebuddy:byte-buddy:1.14.7'
implementation 'net.bytebuddy:byte-buddy-agent:1.14.7'
jarLibs 'net.bytebuddy:byte-buddy-agent:1.14.7'
implementation 'org.slf4j:slf4j-api:2.0.13'
compileOnly 'org.projectlombok:lombok:1.18.32'
annotationProcessor 'org.projectlombok:lombok:1.18.32'
compileOnly 'com.google.auto.service:auto-service:1.1.1'
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
}

jar {
archivesBaseName = "openremote-issuer-provider"
from {
configurations.jarLibs.collect {
zipTree(it)
}
}
}

task installDist(type: Copy) {
into "${rootDir}/build/image"

from jar.outputs
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.openremote.keycloak;

import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;

public interface InitializerProviderFactory extends ProviderFactory, Provider {
@Override
default Provider create(KeycloakSession session) {
return null;
}

@Override
default void init(Config.Scope config) {
}

@Override
default void close() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.openremote.keycloak;

import com.google.auto.service.AutoService;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;

@AutoService(Spi.class)
public class InitializerSpi implements Spi {

@Override
public boolean isInternal() {
return true;
}

@Override
public String getName() {
return "initializer";
}

@Override
public Class<? extends Provider> getProviderClass() {
return Provider.class;
}

@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return InitializerProviderFactory.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.openremote.keycloak;

import com.google.auto.service.AutoService;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.services.Urls;
import org.keycloak.services.validation.Validation;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.returns;

/**
* Copied from https://github.com/dasniko/keycloak-extensions-demo/blob/main/initializer/src/main/java/dasniko/keycloak/initializer/issuer/IssuerInitializerProvider.java
* in order to be able to set a fixed issuer value which is useful when keycloak needs to be accessed by a non public hostname and this isn't supported out of the box,
* see https://github.com/keycloak/keycloak/issues/11584 for discussion.
*/
@Slf4j
@AutoService(InitializerProviderFactory.class)
public class IssuerInitializerProvider implements InitializerProviderFactory {

public static final String PROVIDER_ID = "issuer";

private static final String CONFIG_ATTR_BASE_URI = "base-uri";

private static String issuerBaseUri;

@Override
public String getId() {
return PROVIDER_ID;
}

@Override
public Provider create(KeycloakSession session) {
return null;
}

@Override
public void init(Config.Scope config) {
issuerBaseUri = config.get(CONFIG_ATTR_BASE_URI);
if (!Validation.isBlank(issuerBaseUri)) {
log.info("Issuer BaseURI fixed value: {}", issuerBaseUri);
} else {
log.info("No issuer BaseURI provided");
}
}

@Override
public void postInit(KeycloakSessionFactory factory) {
if (!Validation.isBlank(issuerBaseUri)) {
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(Urls.class)
.method(named("realmIssuer").and(isDeclaredBy(Urls.class).and(returns(String.class))))
.intercept(MethodDelegation.to(this.getClass()))
.make()
.load(Urls.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
}
}

@SuppressWarnings("unused")
public static String realmIssuer(URI baseUri, String realmName) {
try {
baseUri = new URI(issuerBaseUri);
} catch (URISyntaxException | NullPointerException ignored) {
}
return Urls.realmBase(baseUri).path("{realm}").build(realmName).toString();
}

@Override
public List<ProviderConfigProperty> getConfigMetadata() {
return ProviderConfigurationBuilder.create()
.property()
.name(CONFIG_ATTR_BASE_URI)
.type(ProviderConfigProperty.STRING_TYPE)
.helpText("The baseUri to use for the issuer of this server. Keep empty, if the regular hostname settings should be used.")
.add()
.build();
}
}
4 changes: 4 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include 'fixed-issuer'
include 'theme'
include 'build'

13 changes: 13 additions & 0 deletions theme/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id 'java'
}

jar {
archivesBaseName = "openremote-theme-provider"
}

task installDist(type: Copy) {
into "${rootDir}/build/image"

from jar.outputs
}

0 comments on commit b721a0b

Please sign in to comment.