Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 9s
Validate Helm Charts / Build keycloak (pull_request) Successful in 4s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 11s
Install-test / Install-test keycloak (pull_request) Successful in 2m23s
Validate Helm Charts / Install-test (pull_request) Successful in 2m23s
Two replicas with cache=ispn, clustered through Keycloak's default jdbc-ping stack: discovery runs over the PostgreSQL that is there anyway, so no headless Service and no JGroups wiring. Deployment rather than StatefulSet - Keycloak 26 keeps everything in the database, user sessions included. The chart's defining property is that it has TWO INIT CONTAINERS and needs both. db, health-enabled and metrics-enabled are Keycloak BUILD options (verified in the 26.8.0 option table), the published image is augmented with their defaults, and supplying different ones at runtime makes Keycloak rewrite /opt/keycloak/lib/quarkus at start - which readOnlyRootFilesystem: true forbids and which upstream describes as a start that "creates an image that is mutable". Not setting db is no way out: the probes alone force it, because health-enabled is a build option too. An empty emptyDir over that path fails as well, because QuarkusEntryPoint loads quarkus/quarkus-application.dat before it dispatches any command, `build` included. So one init container seeds the directory into a volume, the next runs `kc.sh build` into it, and the serving container runs `start --optimized` - every container keeping the full hardening baseline, with no documented deviation to carry. Build options therefore exist in exactly one place, and the asymmetry is enforced rather than trusted: the serving container passes no build option at all, and a build option's variable name in keycloak.env fails the render with a message naming it. Hostname handling is the security-relevant part. KC_HOSTNAME is derived from ingress.domain as a full URL (which upstream requires alongside hostname-admin), hostname-strict stays at its default true, and proxy-headers is set rather than optional. The admin hostname is NOT presented as an access control: upstream states that hostname-admin "does not prevent accessing the Administration REST API endpoints via the frontend URL specified by the hostname option", so the chart renders no /admin deny rule and values.yaml, NOTES.txt and the wiki all say in the same words that restricting that API is reverse-proxy work. The management port 9000 is a containerPort and a ClusterIP Service for in-cluster scraping, and is in no Ingress: it serves /health/* and optionally /metrics without authentication. Liveness uses /health/live and deliberately not /health or /health/ready - those include the database, and a liveness probe on them would restart the whole cluster for as long as the database is away. Also in this change: - First PodDisruptionBudget in this repository, which makes it a convention (README section 11): minAvailable as a value, and rendered only while the computed replica count is >= 2, because minAvailable: 1 with one replica blocks every node drain forever with a message that names the PDB rather than the replica count. - First quay.io image, so quay.io joins the trusted registries in ci/trivy/data/ksv0125.yaml - configured rather than suppressed, so AVD-KSV-0125 keeps being able to say something. - Renovate block with NO version constraint, which is the counter-case to garage and stalwart and is measured rather than assumed: docker versioning already excludes the -N rebuild tags, the floating minors and latest/nightly. Three dry-run fixtures are recorded at the block, and against the committed config 26.7.4 offers exactly 26.7.5 (patch) and 26.8.0 (minor). - No automerge exception, by decision (Gitea #83): the global automerge applies, and the minor-upgrade procedure plus `kc.sh update-compatibility` is documented in NOTES.txt instead. - Two upstream defaults deviated from, each with its reason at the value: db-pool-max-size 20 instead of 100 (two replicas at 100 ask a default PostgreSQL for twice its max_connections), and uid 1000 / gid 0 instead of this repo's 20000/1000, because the image declares keycloak:x:1000:0 and chowns its install tree to that pair. Part of #83
85 lines
4.8 KiB
YAML
85 lines
4.8 KiB
YAML
{{- /*
|
|
The 1Password items this chart materializes. Two of them, because they have
|
|
different lifetimes rather than different audiences: the database credential
|
|
lives as long as the release, while the bootstrap administrator is meant to be
|
|
switched off again after the first install.
|
|
|
|
WHAT THE FIELD NAMES ARE AND WHY THEY ARE NOT FREE-FORM: the operator
|
|
materializes one Secret key per FIELD of the item, and the Deployment
|
|
references those keys by name. An item without the expected field leaves the
|
|
container in CreateContainerConfigError - not in a crash loop, and not with a
|
|
message about the field. NOTES.txt lists all four fields for the installer.
|
|
|
|
THEY ARRIVE AS ENVIRONMENT VARIABLES AND THAT IS FORCED, NOT PREFERRED.
|
|
Keycloak 26 has no _FILE indirection for an option's value: there is no
|
|
KC_DB_PASSWORD_FILE and no generic KC_<OPTION>_FILE. Checked against the 26.8.0
|
|
option table - every KC_*_FILE variable there is a PATH option (certificate,
|
|
key store, trust store, log file, export file), i.e. "point at this file", not
|
|
"read this option's value from a file". So the file form valkey argues for is
|
|
simply unavailable here. DO NOT "FIX" THIS to a file mount: there is no option
|
|
that would read it. (Keycloak's Vault SPI, KC_VAULT=file, is a different and
|
|
larger mechanism with its own naming scheme, and adopting it would be its own
|
|
decision.)
|
|
|
|
AND THE LIMIT THAT CANNOT BE CLOSED: a change to the CONTENT of either item
|
|
restarts nothing. Helm never sees a secret the 1Password operator
|
|
materializes, so there is nothing to hash. What this chart does cover is the
|
|
op:// PATH, through checksum/secret-ref on the pod template. After rotating a
|
|
value INSIDE an item the manual step is
|
|
kubectl -n <ns> rollout restart deployment/<release>-keycloak-deployment
|
|
|
|
The 1Password operator's auto-restart annotation is NOT set here. Unlike the
|
|
StatefulSet charts in this repo it WOULD work - the operator enumerates
|
|
Deployments - so this is a decision rather than a constraint: its trigger is
|
|
the item's version COUNTER and not its content, so a re-save with identical
|
|
values rolls the whole cluster. For an identity provider, an unannounced
|
|
rolling restart because somebody opened a vault entry is worse than a
|
|
deliberate one. README guideline 9 has the precedence trap if it is ever set.
|
|
*/ -}}
|
|
{{- $settings := fromYaml (include "keycloak.settings" .) -}}
|
|
apiVersion: onepassword.com/v1
|
|
kind: OnePasswordItem
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
spec:
|
|
# Must carry the fields: username, password
|
|
#
|
|
# The role needs to own its schema: Keycloak creates and migrates the whole
|
|
# schema itself through Liquibase on first start, so a read-write grant on
|
|
# existing tables is not enough.
|
|
#
|
|
# Host, port and database name are NOT in this item - they are plain values
|
|
# (keycloak.db.host / .port / .database), because they are not secret and
|
|
# because moving a database should not mean editing a vault entry.
|
|
itemPath: {{ required "You must provide a database secret reference (secrets.databaseSecretRef) - the 1Password item holding the fields 'username' and 'password' of the PostgreSQL role Keycloak uses" .Values.secrets.databaseSecretRef }}
|
|
{{- if $settings.bootstrapAdmin.enabled }}
|
|
---
|
|
apiVersion: onepassword.com/v1
|
|
kind: OnePasswordItem
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-bootstrap-admin-secret
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
spec:
|
|
# Must carry the fields: username, password
|
|
#
|
|
# THIS ACCOUNT IS TEMPORARY, and that is a change from Keycloak before 26 -
|
|
# the old KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD pair created a regular
|
|
# administrator. Upstream on the current pair: "A user or service admin
|
|
# account created using one of the methods described below is temporary ...
|
|
# the account should exist only for the duration necessary to perform
|
|
# operations needed to gain permanent and more secure admin access. After
|
|
# that, the account needs to be removed manually."
|
|
#
|
|
# So creating a permanent administrator after the first install is a
|
|
# MANDATORY step, and switching keycloak.bootstrapAdmin.enabled to false
|
|
# afterwards removes both this item and the per-start log line about the
|
|
# initial administrator not being created. NOTES.txt has the sequence.
|
|
#
|
|
# Upstream's default for the username is "temp-admin"; this chart takes both
|
|
# fields from the vault so the pair has one source of truth.
|
|
itemPath: {{ required "You must provide a bootstrap admin secret reference (secrets.bootstrapAdminSecretRef) - the 1Password item holding the fields 'username' and 'password' of the TEMPORARY bootstrap administrator. Required while keycloak.bootstrapAdmin.enabled is true; set that to false once a permanent administrator exists" .Values.secrets.bootstrapAdminSecretRef }}
|
|
{{- end }}
|