Files
helm-charts/ci/keycloak/test-values.yaml
T
jklein 4c1cff1744 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 18:00:20 +00:00

128 lines
6.1 KiB
YAML

# CI values for the keycloak install-test.
#
# WHY THIS CHART IS TESTED RATHER THAN EXCLUDED, and what a green run is worth.
# Keycloak has no volume, needs one throwaway PostgreSQL and reaches Ready on
# its own, so an exclusion would be hard to justify. More importantly, this is
# the only place where the four riskiest assumptions of the chart are exercised
# at all - none of them is observable in a render.
#
# WHAT A GREEN RUN PROVES, point by point (the measurement points of Gitea #83):
#
# 1. THE TWO INIT CONTAINERS WORK AT ALL. The seed container copies the
# image's own augmentation output into the volume, the build container
# mounts that volume at /opt/keycloak/lib/quarkus and runs `kc.sh build`
# into it - both with readOnlyRootFilesystem: true. If the seeding
# assumption were wrong (QuarkusEntryPoint loads
# quarkus/quarkus-application.dat before it dispatches any command,
# `build` included), the build container could not start, and the pod
# would sit in Init with the reason only in its log.
# 2. readOnlyRootFilesystem: true HOLDS FOR THE SERVING CONTAINER, with
# emptyDirs only at /opt/keycloak/lib/quarkus, /opt/keycloak/data and
# /tmp - and NOT at /opt/keycloak/conf, which would hide the
# cache-ispn.xml that cache=ispn reads.
# 3. `start --optimized` ACCEPTS THE PRE-BUILT INSTALLATION and does not
# re-augment. A disagreement between the build options and the serving
# container would show up here as a crash loop rather than as a slow
# start, because the path it would want to rewrite is read-only.
# 4. jdbc-ping FINDS THE SECOND REPLICA from cache=ispn and the database
# configuration alone - which is why this test runs TWO replicas (see
# below) and why that is the single most valuable line in this file.
# Plus the mechanical half: the Secret key names match what the Deployment
# references, /health/started, /health/live and /health/ready all answer on
# the management port (so health-enabled really was baked in by the init
# container), the resource quantities are valid, and the schema accepts a
# realistic non-default values file.
#
# WHAT IT DOES NOT PROVE, and no install-test here could:
# - that hostname-admin serves the console cleanly on a second hostname. No
# ingress controller answers in CI and nothing drives a browser, so the
# chart's own measurement point 5 stays open. What IS checked is that both
# Ingress objects are accepted and that the Service port they name exists.
# - anything about an upgrade. The rolling-update restriction between minor
# versions is a property of two versions meeting, and this test installs
# one.
# - that the PostgreSQL here resembles production. It is one pod with an
# emptyDir, so nothing about durability, connection limits or latency
# transfers.
secrets:
# Materialized by ci/keycloak/fixtures.yaml as ci-keycloak-db-secret and
# ci-keycloak-bootstrap-admin-secret (the OnePasswordItem stub CRD comes from
# ci/common/).
databaseSecretRef: op://ci/keycloak-db
bootstrapAdminSecretRef: op://ci/keycloak-bootstrap-admin
keycloak:
# TWO, i.e. the chart's default, and the whole reason this fixture is not a
# one-replica shortcut. With one replica the install-test would be green
# without ever showing whether the second node is found - and "a path the
# test does not walk is a path we have not proven" is the lesson Gitea #80
# paid for. The install-test script waits for the Deployment's rollout, and a
# rollout is only complete when BOTH pods are Ready, so cluster formation is
# inside the assertion rather than next to it.
replicas: 2
db:
# The throwaway PostgreSQL from fixtures.yaml. Plain Service name: both are
# in the same namespace.
host: postgres
database: keycloak
# Small but not degenerate: 2 replicas x 5 = 10 connections against
# postgres:16-alpine's default max_connections of 100, which leaves the
# fixture comfortable while still exercising the pool options.
pool:
initialSize: 1
minSize: 1
maxSize: 5
# ON against the chart's default of off, because it is a BUILD option: with
# this true the init container has to build an installation that differs from
# the published image in TWO options rather than one, and /metrics has to
# appear on the management port. A green run therefore proves the build
# arguments really reach `kc.sh build` - with metrics off, a build that
# silently did nothing would look identical.
metrics:
enabled: true
# Set on purpose, although one release cannot collide with itself: it
# exercises the KC_CACHE_EMBEDDED_CLUSTER_NAME path, and a name Keycloak
# rejected would fail the start rather than pass unnoticed.
cache:
clusterName: ci-keycloak
resources:
# Below the chart's defaults to stay inside the runner's budget, and the
# memory number is chosen rather than rounded: the image sizes the heap at
# 70% of the LIMIT, so 600Mi x limitFactor 3 = 1800Mi gives a ~1260Mi heap.
# Upstream's floor is "at least 750 MB" for the limit; going much below
# this makes the JVM the reason a run fails.
requests:
cpu: 250m
memory: 600Mi
ephemeral-storage: 256Mi
build:
resources:
requests:
cpu: 250m
memory: 512Mi
ephemeral-storage: 256Mi
ingress:
# Left ON although no ingress controller answers in CI: the point is that
# both objects are accepted and that the Service port they name BY NAME
# ("http") actually exists - a typo there is otherwise invisible until
# somebody opens the URL.
enabled: true
domain: keycloak.ci.example.com
clusterIssuer: ci-selfsigned
admin:
# The second hostname, on as well, so the chart renders KC_HOSTNAME_ADMIN
# and Keycloak has to accept it as a full URL. That is the half of
# measurement point 5 a cluster-less test can reach: Keycloak validates
# both hostname values at start ("verified that full URL is used, incl.
# scheme and hostname"), so a chart that built a bare host here would fail
# the start instead of serving a broken console later.
enabled: true
domain: keycloak-admin.ci.example.com