From 1a8d9851f22ef8a75ad6d60d17586734a9f03af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=B6hlke?= Date: Thu, 19 Feb 2026 07:53:05 +0100 Subject: [PATCH 1/5] feat: add OIDC middleware support via Traefik plugin - Configure traefik-oidc-auth plugin when OIDC_ENABLED is set - Add OIDC-authenticated routes for provision, who-am-i, and oauth2 callbacks - Add Keycloak reverse-proxy route and unauthenticated theme endpoint - Redact auth headers from access logs Co-Authored-By: Claude Opus 4.6 --- dev/command.sh | 6 +- entrypoint.sh | 129 ++++++++++++++++++++++++++++++++++++++++++ templates/traefik.yml | 16 +++++- 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/dev/command.sh b/dev/command.sh index 33d0162..f8e9cd0 100644 --- a/dev/command.sh +++ b/dev/command.sh @@ -2,15 +2,15 @@ if [ "$APP_CONTEXT" = "dev" ]; then echo "Starting Traefik in development mode..." - exec traefik + exec traefik "$@" fi if [ "$APP_CONTEXT" = "prod" ]; then echo "Starting Traefik in production mode..." - exec traefik + exec traefik "$@" fi if [ "$APP_CONTEXT" = "tests" ]; then echo "Starting Traefik in test mode..." - exec traefik + exec traefik "$@" fi diff --git a/entrypoint.sh b/entrypoint.sh index 1083260..4ed4e3e 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -18,6 +18,13 @@ EXTERNAL_ADDRESS="${EXTERNAL_ADDRESS:-openslides.example.com}" ACME_ENDPOINT="${ACME_ENDPOINT:-}" ACME_EMAIL="${ACME_EMAIL:-}" +# OIDC configuration +OIDC_ENABLED="${OIDC_ENABLED:-}" +OIDC_SESSION_SECRET="${OIDC_SESSION_SECRET:-}" +OIDC_PROVIDER_URL="${OIDC_PROVIDER_URL:-}" +OIDC_CLIENT_ID="${OIDC_CLIENT_ID:-}" +OIDC_CLIENT_SECRET="${OIDC_CLIENT_SECRET:-}" + # Set default values for service endpoints ACTION_HOST="${ACTION_HOST:-backend}" ACTION_PORT="${ACTION_PORT:-9002}" @@ -50,6 +57,19 @@ CLIENT_PORT="${CLIENT_PORT:-9001}" # Generate base config from template envsubst < /templates/traefik.yml > "$TRAEFIK_CONFIG" +# Add experimental plugins section if OIDC is enabled +if [ -n "$OIDC_ENABLED" ]; then + echo "Configuring OIDC plugin in static configuration" + cat >> "$TRAEFIK_CONFIG" << 'EOF' + +experimental: + plugins: + traefik-oidc-auth: + moduleName: github.com/sevensolutions/traefik-oidc-auth + version: v0.19.3 +EOF +fi + # Add dashboard if enabled if [ -n "$ENABLE_DASHBOARD" ]; then echo "Enabling dashboard. 'debug: true' for now. NOT FOR PRODUCTION" @@ -144,6 +164,11 @@ for service_file in $SERVICES_DIR/*.service; do fi if eval [[ -n "\$${host_var}" ]]; then + # Skip auth service in OIDC mode - authentication handled by Keycloak + if [ -n "$OIDC_ENABLED" ] && [ "$service" = "auth" ]; then + echo "Skipping auth service in OIDC mode (auth handled by Keycloak)" + continue + fi eval "echo \"Adding config: $service (host: \$${host_var})\"" >&2 SERVICES="$SERVICES $service" else @@ -161,8 +186,61 @@ EOF # Concatenate all enabled .router files for service in $SERVICES; do envsubst < "$SERVICES_DIR/${service}.router" >> "$DYNAMIC_CONFIG" + # Add OIDC middleware to routes that need authentication + # In OIDC mode, Traefik injects the Authorization header with access token + if [ -n "$OIDC_ENABLED" ]; then + case "$service" in + client|autoupdate|action|presenter|icc|vote|search|media|projector) + echo " middlewares:" >> "$DYNAMIC_CONFIG" + echo " - oidc-auth" >> "$DYNAMIC_CONFIG" + ;; + esac + fi done +# In OIDC mode, add provisioning and who-am-i routes to backend +if [ -n "$OIDC_ENABLED" ]; then + echo "Adding OIDC auth routers (routes to backend)" + cat >> "$DYNAMIC_CONFIG" << EOF + keycloak: + rule: "PathPrefix(\`/auth\`)" + service: keycloak + entryPoints: + - main + priority: 10 + auth-oidc-provision: + rule: "PathPrefix(\`/system/auth/oidc-provision\`)" + service: action + entryPoints: + - main + middlewares: + - oidc-auth + priority: 15 + auth-who-am-i: + rule: "PathPrefix(\`/system/auth/who-am-i\`)" + service: action + entryPoints: + - main + middlewares: + - oidc-auth + priority: 15 + autoupdate-theme: + rule: "Path(\`/system/autoupdate/theme\`)" + service: autoupdate + entryPoints: + - main + priority: 50 + oauth2: + rule: "PathPrefix(\`/oauth2\`)" + service: client + entryPoints: + - main + middlewares: + - oidc-auth + priority: 10 +EOF +fi + # Add services section cat >> "$DYNAMIC_CONFIG" << 'EOF' @@ -174,6 +252,57 @@ for service in $SERVICES; do envsubst < "$SERVICES_DIR/${service}.service" >> "$DYNAMIC_CONFIG" done +# Add Keycloak service if OIDC is enabled +if [ -n "$OIDC_ENABLED" ]; then + cat >> "$DYNAMIC_CONFIG" << EOF + keycloak: + loadBalancer: + servers: + - url: "http://keycloak:8080" + passHostHeader: true +EOF +fi + +# Add OIDC middleware configuration if enabled +if [ -n "$OIDC_ENABLED" ]; then + echo "Enabling OIDC authentication middleware" + cat >> "$DYNAMIC_CONFIG" << EOF + + middlewares: + oidc-auth: + plugin: + traefik-oidc-auth: + LogLevel: debug + Secret: "${OIDC_SESSION_SECRET}" + Provider: + Url: "${OIDC_PROVIDER_URL}" + ClientId: "${OIDC_CLIENT_ID}" + ClientSecret: "${OIDC_CLIENT_SECRET}" + UsePkce: true + Scopes: + - openid + - profile + - email + - roles + LoginUri: /oauth2/login + CallbackUri: /oauth2/callback + LogoutUri: /oauth2/logout + PostLoginRedirectUri: /system/auth/oidc-provision + UnauthorizedBehavior: Auto + PostLogoutRedirectUri: / + SessionCookie: + SameSite: lax + HttpOnly: false + Headers: + - Name: Authentication + Value: 'bearer {{ "{{ .accessToken }}" }}' + - Name: X-Forwarded-User + Value: '{{ "{{ .claims.preferred_username }}" }}' + - Name: X-Auth-Request-Email + Value: '{{ "{{ .claims.email }}" }}' +EOF +fi + # Finally start CMD exec "$@" diff --git a/templates/traefik.yml b/templates/traefik.yml index c5cb72f..8d00387 100644 --- a/templates/traefik.yml +++ b/templates/traefik.yml @@ -1,5 +1,11 @@ # Traefik configuration for OpenSlides +# Experimental plugins configuration +# The traefik-oidc-auth plugin is loaded via command-line arguments +# when using docker-compose.oidc.yml overlay: +# --experimental.plugins.traefik-oidc-auth.modulename=github.com/sevensolutions/traefik-oidc-auth +# --experimental.plugins.traefik-oidc-auth.version=v0.19.3 + # Add provider to read routing config from file providers: file: @@ -13,7 +19,15 @@ ping: {} log: level: ${TRAEFIK_LOG_LEVEL} -accessLog: {} +accessLog: + fields: + headers: + defaultMode: keep + names: + Authorization: drop + X-Forwarded-User: keep + X-Auth-Request-Email: keep + authentication: drop # entryPoints are generated dynamically in entrypoint.sh script as their # definitions depend on TLS configuration From 6c5e0a9601bd1cdefc8f6061084b7f486f076340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=B6hlke?= Date: Thu, 19 Feb 2026 10:26:17 +0100 Subject: [PATCH 2/5] refactor: route theme endpoint to presenter service Update OIDC-exempt theme route from /system/autoupdate/theme to /system/presenter/theme now that theme serving lives in the backend. Co-Authored-By: Claude Opus 4.6 --- entrypoint.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 4ed4e3e..a23f08e 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -224,9 +224,9 @@ if [ -n "$OIDC_ENABLED" ]; then middlewares: - oidc-auth priority: 15 - autoupdate-theme: - rule: "Path(\`/system/autoupdate/theme\`)" - service: autoupdate + presenter-theme: + rule: "Path(\`/system/presenter/theme\`)" + service: presenter entryPoints: - main priority: 50 From afc9ef4c61a5acbc1f637ec60872a0a76bbd0c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=B6hlke?= Date: Thu, 19 Feb 2026 12:28:05 +0100 Subject: [PATCH 3/5] fix: use existing traefik-oidc-auth plugin version v0.18.0 v0.19.3 does not exist in the Traefik plugin registry, causing the plugin to fail to download and all OIDC-protected routes to return 404. Co-Authored-By: Claude Opus 4.6 --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index a23f08e..fabc20b 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -66,7 +66,7 @@ experimental: plugins: traefik-oidc-auth: moduleName: github.com/sevensolutions/traefik-oidc-auth - version: v0.19.3 + version: v0.18.0 EOF fi From 9d99b982c3a9e7f31bfc785d9b720d845b1751be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=B6hlke?= Date: Mon, 9 Mar 2026 11:08:22 +0000 Subject: [PATCH 4/5] fix: use internal provider URL for OIDC plugin discovery Add OIDC_INTERNAL_PROVIDER_URL for service-to-service OIDC discovery over plain HTTP (Docker network), while keeping OIDC_PROVIDER_URL as the external issuer URL validated in tokens via ValidIssuer. This fixes the self-signed TLS certificate error when the proxy tries to discover OIDC endpoints via its own HTTPS endpoint. --- entrypoint.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index fabc20b..0b844da 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -22,6 +22,7 @@ ACME_EMAIL="${ACME_EMAIL:-}" OIDC_ENABLED="${OIDC_ENABLED:-}" OIDC_SESSION_SECRET="${OIDC_SESSION_SECRET:-}" OIDC_PROVIDER_URL="${OIDC_PROVIDER_URL:-}" +OIDC_INTERNAL_PROVIDER_URL="${OIDC_INTERNAL_PROVIDER_URL:-${OIDC_PROVIDER_URL}}" OIDC_CLIENT_ID="${OIDC_CLIENT_ID:-}" OIDC_CLIENT_SECRET="${OIDC_CLIENT_SECRET:-}" @@ -275,10 +276,12 @@ if [ -n "$OIDC_ENABLED" ]; then LogLevel: debug Secret: "${OIDC_SESSION_SECRET}" Provider: - Url: "${OIDC_PROVIDER_URL}" + Url: "${OIDC_INTERNAL_PROVIDER_URL}" ClientId: "${OIDC_CLIENT_ID}" ClientSecret: "${OIDC_CLIENT_SECRET}" UsePkce: true + ValidateIssuer: true + ValidIssuer: "${OIDC_PROVIDER_URL}" Scopes: - openid - profile From 05c310c413b8e5dccd0c44018b69905c7becbb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20B=C3=B6hlke?= Date: Mon, 9 Mar 2026 13:31:40 +0000 Subject: [PATCH 5/5] fix: replace bash-ism [[ ]] with POSIX [ ] in entrypoint.sh --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index 0b844da..1020677 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -159,7 +159,7 @@ for service_file in $SERVICES_DIR/*.service; do service_upper=$(echo "$service" | tr '[:lower:]' '[:upper:]') host_var="${service_upper}_HOST" - if [[ ! -f "$SERVICES_DIR/$service.service" ]] || [[ ! -f "$SERVICES_DIR/$service.router" ]]; then + if [ ! -f "$SERVICES_DIR/$service.service" ] || [ ! -f "$SERVICES_DIR/$service.router" ]; then echo "Skipping, config incomplete: $service" continue fi