From 7f38b67ac05c959041d88b4da0b099eb43f9fd42 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 3 Mar 2026 10:36:49 +0300 Subject: [PATCH 1/2] [keycloak] Enable injecting themes This patch lets Cozystack admins specify initContainers that will run `cp -r /themes/ /opt/keycloak/themes/` on startup, effectively providing an interface for operators to inject custom themes into the keycloak deployment to customize the UI. ```release-note [keycloak] Enable injection of user-provided themes for Keycloak via initContainers. ``` Signed-off-by: Timofei Larkin --- packages/system/keycloak/templates/sts.yaml | 21 +++++++++++++++++++++ packages/system/keycloak/values.yaml | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/packages/system/keycloak/templates/sts.yaml b/packages/system/keycloak/templates/sts.yaml index e2bba431..90d2d95c 100644 --- a/packages/system/keycloak/templates/sts.yaml +++ b/packages/system/keycloak/templates/sts.yaml @@ -41,6 +41,17 @@ spec: restartPolicy: Always securityContext: fsGroup: 1000 + {{- if .Values.themes }} + initContainers: + {{- range .Values.themes }} + - name: theme-{{ .name }} + image: {{ .image }} + command: ["sh", "-c", "cp -r /themes/* /opt/keycloak/themes/"] + volumeMounts: + - name: themes + mountPath: /opt/keycloak/themes + {{- end }} + {{- end }} containers: - name: keycloak image: {{ .Values.image }} @@ -128,6 +139,11 @@ spec: value: https://{{ $ingressHost }} - name: JAVA_OPTS_APPEND value: "-Djgroups.dns.query=keycloak-headless.cozy-keycloak.svc.{{ $clusterDomain }}" + {{- if .Values.themes }} + volumeMounts: + - name: themes + mountPath: /opt/keycloak/themes + {{- end }} ports: - name: http containerPort: 8080 @@ -155,4 +171,9 @@ spec: periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 + {{- if .Values.themes }} + volumes: + - name: themes + emptyDir: {} + {{- end }} terminationGracePeriodSeconds: 60 diff --git a/packages/system/keycloak/values.yaml b/packages/system/keycloak/values.yaml index b2f53d01..6cba9aef 100644 --- a/packages/system/keycloak/values.yaml +++ b/packages/system/keycloak/values.yaml @@ -14,3 +14,7 @@ resources: requests: memory: 500Mi cpu: 100m + +themes: [] +# - name: my-theme +# image: my-registry/my-keycloak-theme:v1.0 From ed51d3e16e2831b7a6c8df5cafa7c9be8c4c25e8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 30 Mar 2026 17:01:10 +0300 Subject: [PATCH 2/2] [keycloak] Harden theme injection with validation and security Add securityContext to theme init containers matching the main container security posture. Add input validation for theme entries: required fields, DNS-1123 name sanitization, duplicate detection, and container name length limit. Add imagePullSecrets support for private registries and sizeLimit on the emptyDir volume. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/keycloak/templates/sts.yaml | 42 +++++++++++++++++++-- packages/system/keycloak/values.yaml | 6 +++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/system/keycloak/templates/sts.yaml b/packages/system/keycloak/templates/sts.yaml index 90d2d95c..c1827b93 100644 --- a/packages/system/keycloak/templates/sts.yaml +++ b/packages/system/keycloak/templates/sts.yaml @@ -1,3 +1,7 @@ +{{- define "keycloak.theme.sanitizedName" -}} +{{- regexReplaceAll "-+" (regexReplaceAll "[^a-z0-9-]" (. | lower) "-") "-" | trimPrefix "-" | trimSuffix "-" -}} +{{- end -}} + {{- $host := index .Values._cluster "root-host" }} {{- $ingressHost := .Values.ingress.host | default (printf "keycloak.%s" $host) }} {{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} @@ -39,14 +43,43 @@ spec: app: keycloak-ha spec: restartPolicy: Always + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} securityContext: fsGroup: 1000 {{- if .Values.themes }} + {{- $themeNames := list }} + {{- range .Values.themes }} + {{- if not .name }}{{ fail "theme entry missing required field: name" }}{{- end }} + {{- if not .image }}{{ fail "theme entry missing required field: image" }}{{- end }} + {{- $sanitized := include "keycloak.theme.sanitizedName" .name }} + {{- if not $sanitized }}{{ fail (printf "theme name %q produces empty container name after sanitization" .name) }}{{- end }} + {{- if gt (len (printf "theme-%s" $sanitized)) 63 }}{{ fail (printf "theme name %q produces container name exceeding 63 characters" .name) }}{{- end }} + {{- if has $sanitized $themeNames }}{{ fail (printf "duplicate theme name after sanitization: %s (from %s)" $sanitized .name) }}{{- end }} + {{- $themeNames = append $themeNames $sanitized }} + {{- end }} initContainers: {{- range .Values.themes }} - - name: theme-{{ .name }} - image: {{ .image }} - command: ["sh", "-c", "cp -r /themes/* /opt/keycloak/themes/"] + - name: theme-{{ include "keycloak.theme.sanitizedName" .name }} + image: "{{ .image }}" + imagePullPolicy: IfNotPresent + command: ["sh", "-c", "[ -d /themes ] && cp -r /themes/. /opt/keycloak/themes/ || { echo 'ERROR: /themes directory not found in image'; exit 1; }"] + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + memory: 64Mi + securityContext: + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false volumeMounts: - name: themes mountPath: /opt/keycloak/themes @@ -174,6 +207,7 @@ spec: {{- if .Values.themes }} volumes: - name: themes - emptyDir: {} + emptyDir: + sizeLimit: 256Mi {{- end }} terminationGracePeriodSeconds: 60 diff --git a/packages/system/keycloak/values.yaml b/packages/system/keycloak/values.yaml index 6cba9aef..4368ea2c 100644 --- a/packages/system/keycloak/values.yaml +++ b/packages/system/keycloak/values.yaml @@ -18,3 +18,9 @@ resources: themes: [] # - name: my-theme # image: my-registry/my-keycloak-theme:v1.0 +# Theme images must contain theme files under /themes/ directory. +# Each theme is copied into Keycloak's /opt/keycloak/themes/ via init container. +# If multiple themes contain files with the same path, later entries take precedence. + +imagePullSecrets: [] +# - name: my-registry-secret