Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions charts/authup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ Kubernetes: `>=1.25.0-0`
| server.startupProbe.successThreshold | int | `1` | |
| server.startupProbe.timeoutSeconds | int | `5` | |
| server.terminationGracePeriodSeconds | int | `30` | Pod termination grace period (server-core tears down within ~10s after signal) |
| server.theme.enabled | bool | `false` | Mount an operator theme for the served consoles (the auth console and the account console). Requires an authup image that supports THEME_DIRECTORY_PATH; older images ignore it |
| server.theme.existingConfigMap | string | `""` | Existing ConfigMap holding the theme (tpl-rendered name). Use for binary assets, which cannot be expressed in files |
| server.theme.existingConfigMapItems | list | `[]` | Key -> path projection for existingConfigMap, so its keys can land in subdirectories (e.g. [{key: theme-css, path: assets/theme.css}]). Empty mounts every key flat at the theme root |
| server.theme.files | object | `{}` | Map of path -> file content, relative to the theme root (tpl-rendered). Keys may carry a "/" ("assets/theme.css") and are projected into subdirectories. Only assets/ is served over HTTP. Text only — use existingConfigMap with binaryData for images |
| server.theme.fragmentsEnabled | bool | `false` | Read fragments/head.html and splice it into the console <head>. Raw, unsanitized markup on the identity provider origin, so it is opt-in |
| server.tolerations | list | `[]` | Tolerations |
| server.topologySpreadConstraints | list | `[]` | Topology spread constraints (a missing labelSelector is filled with the pod's selector labels) |
| server.trustProxy | string | `"1"` | TRUST_PROXY setting. The chart defaults to one trusted hop (the ingress), not authup's spoofable trust-everything default |
Expand Down
28 changes: 28 additions & 0 deletions charts/authup/ci/theme-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Operator theme mounted from an inline files map, covering the path-keyed
# ConfigMap projection (a "/" key must land in a subdirectory) and the env
# wiring. THEME_DIRECTORY_PATH is ignored by an authup image that predates
# console theming, so the rollout assertion holds either way.
server:
theme:
enabled: true
fragmentsEnabled: true
files:
theme.json: |
{
"version": 1,
"title": "Sign in to ACME",
"stylesheet": "assets/theme.css",
"tokens": {
"--authup-periwinkle": "#c0392b"
},
"tokensDark": {
"--authup-auth-accent": "#e06c5a"
}
}
assets/theme.css: |
.a-auth-shell-card { border-radius: 2px; }
fragments/head.html: |
<meta name="operator" content="acme">

adminConsole:
enabled: false
131 changes: 130 additions & 1 deletion charts/authup/templates/_server-env.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Strict-boolean variables are always quoted: authup crashes the boot on
unparsable boolean strings by design.
*/}}
{{- define "authup.server.configEnv" -}}
{{- include "authup.server.validateTheme" . -}}
DB_TYPE: {{ include "authup.database.type" . | quote }}
DB_HOST: {{ include "authup.database.host" . | quote }}
DB_PORT: {{ include "authup.database.port" . | quote }}
Expand Down Expand Up @@ -36,7 +37,7 @@ CLIENT_SYSTEM_ENABLED: "true"
CLIENT_SYSTEM_SECRET_RESET: "true"
{{- end }}
{{- end }}
{{- $reserved := list "DB_TYPE" "DB_HOST" "DB_PORT" "DB_USERNAME" "DB_DATABASE" "DB_PASSWORD" "PUBLIC_URL" "TRUSTED_ORIGINS" "TRUST_PROXY" "REGISTRATION_ENABLED" "PASSWORD_RECOVERY_ENABLED" "EMAIL_VERIFICATION_ENABLED" "MFA_ENABLED" "MFA_REQUIRED" "USER_ADMIN_PASSWORD" "USER_ADMIN_PASSWORD_RESET" "CLIENT_SYSTEM_ENABLED" "CLIENT_SYSTEM_SECRET" "CLIENT_SYSTEM_SECRET_RESET" "REDIS" "SMTP" "SECRETS_ENCRYPTION_KEY" }}
{{- $reserved := list "DB_TYPE" "DB_HOST" "DB_PORT" "DB_USERNAME" "DB_DATABASE" "DB_PASSWORD" "PUBLIC_URL" "TRUSTED_ORIGINS" "TRUST_PROXY" "REGISTRATION_ENABLED" "PASSWORD_RECOVERY_ENABLED" "EMAIL_VERIFICATION_ENABLED" "MFA_ENABLED" "MFA_REQUIRED" "THEME_DIRECTORY_PATH" "THEME_FRAGMENTS_ENABLED" "USER_ADMIN_PASSWORD" "USER_ADMIN_PASSWORD_RESET" "CLIENT_SYSTEM_ENABLED" "CLIENT_SYSTEM_SECRET" "CLIENT_SYSTEM_SECRET_RESET" "REDIS" "SMTP" "SECRETS_ENCRYPTION_KEY" }}
{{- range $key, $value := .Values.server.config }}
{{- if has $key $reserved }}
{{- fail (printf "authup: server.config.%s collides with a first-class chart value — set it through the dedicated value instead." $key) }}
Expand Down Expand Up @@ -138,6 +139,134 @@ provisioning files, config file).
{{- end }}
{{- end -}}

{{/*
Theme volume / volumeMount, deliberately NOT part of the shared server
helpers: the migration Job is a pre-upgrade HOOK, and hooks precede regular
resources, so on the upgrade that first enables theming it would reference a
ConfigMap that does not exist yet and hang. A migration run has no use for
the theme either way.
*/}}
{{/*
Theme environment, kept OUT of authup.server.configEnv for the same reason
as the volume: the migration Job inlines configEnv, and pointing
THEME_DIRECTORY_PATH at a directory that Job does not mount would describe
a pod that does not exist. Nothing reads it there today (the migration
command boots only config + logger, never the http module), but the env
should not contradict the pod it is in.

THEME_* stays in configEnv's reserved-key list regardless, so a
`server.config` entry cannot emit a duplicate key into the same ConfigMap.
*/}}
{{- define "authup.server.themeEnv" -}}
{{- if include "authup.server.themeMounted" . }}
THEME_DIRECTORY_PATH: {{ include "authup.server.themeMountPath" . | quote }}
THEME_FRAGMENTS_ENABLED: {{ .Values.server.theme.fragmentsEnabled | toString | quote }}
{{- end }}
{{- end -}}

{{- define "authup.server.themeVolumeMounts" -}}
{{- if include "authup.server.themeMounted" . }}
- name: theme
mountPath: {{ include "authup.server.themeMountPath" . }}
readOnly: true
{{- end }}
{{- end -}}

{{- define "authup.server.themeVolumes" -}}
{{- if include "authup.server.themeMounted" . }}
- name: theme
configMap:
name: {{ include "authup.server.themeConfigMapName" . }}
{{- /* Whole-volume projection on purpose: a subPath mount is frozen
until the pod restarts, which would destroy authup's live theme
reload. */}}
{{- if .Values.server.theme.existingConfigMap }}
{{- with .Values.server.theme.existingConfigMapItems }}
items: {{- include "authup.tplvalues.render" (dict "value" . "context" $) | nindent 6 }}
{{- end }}
{{- else }}
items:
{{- range $path, $content := .Values.server.theme.files }}
- key: {{ include "authup.server.themeConfigMapKey" $path }}
path: {{ $path }}
{{- end }}
{{- end }}
{{- end }}
{{- end -}}

{{/*
Absolute path the theme volume is mounted at, and the value of
THEME_DIRECTORY_PATH. A constant: the chart owns both ends.
*/}}
{{- define "authup.server.themeMountPath" -}}
/etc/authup/theme
{{- end -}}

{{/*
Flatten a theme path into a valid ConfigMap key ("/" is not allowed in one).
The volume's items list projects it back, so the encoding never reaches the
operator.
*/}}
{{- define "authup.server.themeConfigMapKey" -}}
{{- . | replace "/" "__" -}}
{{- end -}}

{{- define "authup.server.themeConfigMapName" -}}
{{- if .Values.server.theme.existingConfigMap -}}
{{- include "authup.tplvalues.render" (dict "value" .Values.server.theme.existingConfigMap "context" $) -}}
{{- else -}}
{{- printf "%s-theme" (include "authup.server.fullname" .) -}}
{{- end -}}
{{- end -}}

{{/*
True when a theme should be mounted at all.
*/}}
{{- define "authup.server.themeMounted" -}}
{{- if and .Values.server.theme.enabled (or .Values.server.theme.files .Values.server.theme.existingConfigMap) -}}
true
{{- end -}}
{{- end -}}
Comment thread
tada5hi marked this conversation as resolved.

{{/*
Render-time validation. The chart fails loud rather than shipping a
silently-inert theme: the dominant failure mode of theming is a page that
looks exactly like an un-themed page.
*/}}
{{- define "authup.server.validateTheme" -}}
{{- if .Values.server.theme.enabled }}
{{- if not (or .Values.server.theme.files .Values.server.theme.existingConfigMap) }}
{{- fail "authup: server.theme.enabled requires server.theme.files or server.theme.existingConfigMap — an empty theme directory would render an un-themed page with no error." }}
{{- end }}
{{- if and .Values.server.theme.files .Values.server.theme.existingConfigMap }}
{{- fail "authup: set either server.theme.files or server.theme.existingConfigMap, not both — the existing ConfigMap would win and the inline files would be silently ignored." }}
{{- end }}
{{- range $path, $content := .Values.server.theme.files }}
{{- if hasPrefix "/" $path }}
{{- fail (printf "authup: server.theme.files key %q must be relative to the theme root." $path) }}
{{- end }}
{{- if contains ".." $path }}
{{- fail (printf "authup: server.theme.files key %q must not traverse out of the theme root." $path) }}
{{- end }}
{{- if contains "__" $path }}
{{- fail (printf "authup: server.theme.files key %q must not contain \"__\" — it is reserved for encoding the path separator into a ConfigMap key." $path) }}
{{- end }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
{{- /* A ConfigMap data key must match ^[A-Za-z0-9._-]+$, so a path
carrying a space, a colon or any other character outside this set
would flatten into an INVALID key and fail at apply time with a
Kubernetes validation error instead of here. The set is the one the
server's own asset handler accepts, so the chart now rejects at
render time exactly what the server would 404 at request time. */}}
{{- if not (regexMatch "^[a-zA-Z0-9][a-zA-Z0-9._/-]*$" $path) }}
{{- fail (printf "authup: server.theme.files key %q must start with a letter or digit and contain only letters, digits, \".\", \"_\", \"-\" and \"/\"." $path) }}
{{- end }}
{{- end }}
{{- end }}
{{- if and .Values.server.theme.existingConfigMapItems (not .Values.server.theme.existingConfigMap) }}
{{- fail "authup: server.theme.existingConfigMapItems requires server.theme.existingConfigMap." }}
{{- end }}
{{- end -}}

{{- define "authup.server.provisioningConfigMapName" -}}
{{- if .Values.server.provisioning.existingConfigMap -}}
{{- include "authup.tplvalues.render" (dict "value" .Values.server.provisioning.existingConfigMap "context" $) -}}
Expand Down
6 changes: 6 additions & 0 deletions charts/authup/templates/server/configmap-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ metadata:
labels: {{- include "authup.labels" (dict "context" $ "component" "server") | nindent 4 }}
annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }}
data: {{- include "authup.server.configEnv" . | nindent 2 }}
{{- /* Only the Deployment consumes this ConfigMap (envFrom); the
migration Job inlines configEnv instead, so it never sees the
theme env. See authup.server.themeEnv. */}}
{{- if include "authup.server.themeMounted" . }}
{{- include "authup.server.themeEnv" . | nindent 2 }}
{{- end }}
{{- end }}
18 changes: 18 additions & 0 deletions charts/authup/templates/server/configmap-theme.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{- if and .Values.server.enabled .Values.server.theme.enabled .Values.server.theme.files (not .Values.server.theme.existingConfigMap) }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ printf "%s-theme" (include "authup.server.fullname" .) }}
namespace: {{ include "authup.namespace" . | quote }}
labels: {{- include "authup.labels" (dict "context" $ "component" "server") | nindent 4 }}
annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }}
data:
{{- /* A ConfigMap key cannot contain "/", but the theme layout has
subdirectories. Keys are flattened here and projected back to their
real path by the volume's items list, so the operator writes
"assets/theme.css" and never sees the encoding. */}}
{{- range $path, $content := .Values.server.theme.files }}
{{ include "authup.server.themeConfigMapKey" $path }}: |-
{{- include "authup.tplvalues.render" (dict "value" $content "context" $) | nindent 4 }}
{{- end }}
{{- end }}
15 changes: 14 additions & 1 deletion charts/authup/templates/server/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ spec:
{{- end }}
annotations:
{{- if not .Values.server.disableRestartOnChanges }}
checksum/env: {{ include "authup.server.configEnv" . | sha256sum }}
{{- /* Both halves of the env ConfigMap: themeEnv lives outside
configEnv (the migration Job must not inherit it), so hashing
configEnv alone would leave a fragmentsEnabled flip invisible
to the running pods. */}}
checksum/env: {{ printf "%s%s" (include "authup.server.configEnv" .) (include "authup.server.themeEnv" .) | sha256sum }}
{{- if include "authup.auth.createSecret" . }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
{{- end }}
Expand All @@ -40,6 +44,9 @@ spec:
{{- if and .Values.server.provisioning.enabled .Values.server.provisioning.files }}
checksum/provisioning: {{ include (print $.Template.BasePath "/server/configmap-provisioning.yaml") . | sha256sum }}
{{- end }}
{{- if and .Values.server.theme.enabled .Values.server.theme.files }}
checksum/theme: {{ include (print $.Template.BasePath "/server/configmap-theme.yaml") . | sha256sum }}
{{- end }}
{{- if .Values.server.configuration }}
checksum/configuration: {{ include (print $.Template.BasePath "/server/configmap-configuration.yaml") . | sha256sum }}
{{- end }}
Expand Down Expand Up @@ -158,13 +165,19 @@ spec:
lifecycle: {{- include "authup.tplvalues.render" (dict "value" .Values.server.lifecycleHooks "context" $) | nindent 12 }}
{{- end }}
volumeMounts: {{- include "authup.server.volumeMounts" . | nindent 12 }}
{{- if include "authup.server.themeMounted" . }}
{{- include "authup.server.themeVolumeMounts" . | nindent 12 }}
{{- end }}
{{- if .Values.server.extraVolumeMounts }}
{{- include "authup.tplvalues.render" (dict "value" .Values.server.extraVolumeMounts "context" $) | nindent 12 }}
{{- end }}
{{- if .Values.server.sidecars }}
{{- include "authup.tplvalues.render" (dict "value" .Values.server.sidecars "context" $) | nindent 8 }}
{{- end }}
volumes: {{- include "authup.server.volumes" . | nindent 8 }}
{{- if include "authup.server.themeMounted" . }}
{{- include "authup.server.themeVolumes" . | nindent 8 }}
{{- end }}
{{- if .Values.server.extraVolumes }}
{{- include "authup.tplvalues.render" (dict "value" .Values.server.extraVolumes "context" $) | nindent 8 }}
{{- end }}
Expand Down
50 changes: 50 additions & 0 deletions charts/authup/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3306,6 +3306,55 @@
"title": "terminationGracePeriodSeconds",
"type": "integer"
},
"theme": {
"additionalProperties": false,
"properties": {
"enabled": {
"default": false,
"description": "Mount an operator theme for the served consoles (the auth console and\nthe account console). Requires an authup image that supports\nTHEME_DIRECTORY_PATH; older images ignore it",
"required": [],
"title": "enabled",
"type": "boolean"
},
"existingConfigMap": {
"default": "",
"description": "Existing ConfigMap holding the theme (tpl-rendered name). Use for\nbinary assets, which cannot be expressed in files",
"required": [],
"title": "existingConfigMap",
"type": "string"
},
"existingConfigMapItems": {
"description": "Key -\u003e path projection for existingConfigMap, so its keys can land in\nsubdirectories (e.g. [{key: theme-css, path: assets/theme.css}]).\nEmpty mounts every key flat at the theme root",
"items": {
"required": []
},
"required": [],
"title": "existingConfigMapItems",
"type": "array"
},
"files": {
"additionalProperties": true,
"description": "Map of path -\u003e file content, relative to the theme root (tpl-rendered).\nKeys may carry a \"/\" (\"assets/theme.css\") and are projected into\nsubdirectories. Only assets/ is served over HTTP. Text only — use\nexistingConfigMap with binaryData for images",
"required": [],
"title": "files"
},
"fragmentsEnabled": {
"default": false,
"description": "Read fragments/head.html and splice it into the console \u003chead\u003e. Raw,\nunsanitized markup on the identity provider origin, so it is opt-in",
"required": [],
"title": "fragmentsEnabled",
"type": "boolean"
}
},
"required": [
"enabled",
"existingConfigMap",
"existingConfigMapItems",
"fragmentsEnabled"
],
"title": "theme",
"type": "object"
},
"tolerations": {
"description": "Tolerations",
"items": {
Expand Down Expand Up @@ -3376,6 +3425,7 @@
"configuration",
"existingConfigmap",
"provisioning",
"theme",
"migration",
"command",
"args",
Expand Down
23 changes: 23 additions & 0 deletions charts/authup/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,29 @@ server:
# -- Existing Secret with provisioning files (tpl-rendered; takes
# precedence — use for provisioning content that carries credentials)
existingSecret: ""
theme:
# -- Mount an operator theme for the served consoles (the auth console and
# the account console). Requires an authup image that supports
# THEME_DIRECTORY_PATH; older images ignore it
enabled: false
# @schema
# additionalProperties: true
# @schema
# -- Map of path -> file content, relative to the theme root (tpl-rendered).
# Keys may carry a "/" ("assets/theme.css") and are projected into
# subdirectories. Only assets/ is served over HTTP. Text only — use
# existingConfigMap with binaryData for images
files: {}
# -- Existing ConfigMap holding the theme (tpl-rendered name). Use for
# binary assets, which cannot be expressed in files
existingConfigMap: ""
# -- Key -> path projection for existingConfigMap, so its keys can land in
# subdirectories (e.g. [{key: theme-css, path: assets/theme.css}]).
# Empty mounts every key flat at the theme root
existingConfigMapItems: []
# -- Read fragments/head.html and splice it into the console <head>. Raw,
# unsanitized markup on the identity provider origin, so it is opt-in
fragmentsEnabled: false
migration:
# -- Run `server/core migration run` as a pre-upgrade hook Job. Recommended
# for multi-replica deployments (serializes DDL before pods roll). Fresh
Expand Down
Loading