Files
helm-charts/ci/keycloak/fixtures.yaml
T
jklein f784f40fd5
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
feat(keycloak): new chart for Keycloak (1.0.0+up26.8.0)
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
2026-10-04 17:02:47 +00:00

97 lines
3.1 KiB
YAML

# Fixtures for the keycloak install-test: a throwaway PostgreSQL plus the two
# Secrets the 1Password operator would otherwise materialize from the
# OnePasswordItems the chart renders (release name: ci, stub CRD in ci/common/).
#
# WHY A REAL DATABASE AND NOT A STUB: Keycloak does nothing without one. It
# creates its entire schema through Liquibase on first start, and - the reason
# this fixture is the valuable half of the test - jdbc-ping discovers the second
# replica THROUGH this database. A stub would turn a cluster test into a
# templating test.
#
# The image is pinned to a major line like every other fixture here
# (postgres:16-alpine); ci/** is excluded from Renovate on purpose, because
# this is test scaffolding and churning it only risks breaking the
# install-test.
apiVersion: v1
kind: Secret
metadata:
name: ci-keycloak-db-secret
stringData:
# The field NAMES are what matters here, not the values: the operator
# materializes one Secret key per item field, and the Deployment references
# these two by name. A rename on either side leaves the pod in
# CreateContainerConfigError, which is a state no render can show.
#
# They must agree with the POSTGRES_USER / POSTGRES_PASSWORD below, because
# Keycloak really connects.
username: keycloak
password: ci-keycloak-db-password
---
apiVersion: v1
kind: Secret
metadata:
name: ci-keycloak-bootstrap-admin-secret
stringData:
# The temporary bootstrap administrator. Keycloak creates it while the master
# realm is created, so these values are exercised on the very first start -
# and the variables are named KC_BOOTSTRAP_ADMIN_* only since Keycloak 26.
#
# A visibly throwaway password: nothing here survives the run, the cluster is
# deleted with the job, and the only thing it protects is a realm that exists
# for a few minutes.
username: ci-temp-admin
password: ci-bootstrap-admin-password
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: public.ecr.aws/docker/library/postgres:16-alpine
env:
- name: POSTGRES_USER
value: keycloak
- name: POSTGRES_PASSWORD
value: ci-keycloak-db-password
- name: POSTGRES_DB
value: keycloak
ports:
- containerPort: 5432
readinessProbe:
exec:
command: ["pg_isready", "-U", "keycloak"]
periodSeconds: 5
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
# emptyDir: the database exists for the length of one job. Nothing here
# is meant to survive, and a PVC would only add a provisioner to the
# list of things that can fail a chart's test.
- name: data
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432