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
41 lines
1.8 KiB
YAML
41 lines
1.8 KiB
YAML
{{- $settings := fromYaml (include "keycloak.settings" .) }}
|
|
{{- /*
|
|
THE MANAGEMENT SERVICE, AND WHAT IT IS AND IS NOT FOR.
|
|
|
|
Keycloak serves /health, /health/live, /health/ready, /health/started and -
|
|
with keycloak.metrics.enabled - /metrics on the management port, and it serves
|
|
them WITHOUT AUTHENTICATION. The probes do not need this Service (the kubelet
|
|
addresses the container port directly); a metrics scraper or an uptime check
|
|
inside the cluster does, which is the only reason it exists.
|
|
|
|
IT IS A SEPARATE OBJECT FROM THE SERVING SERVICE ON PURPOSE. Putting port 9000
|
|
next to port 8080 on one Service would mean the Service an Ingress already
|
|
points at also carries the unauthenticated health and metrics surface - and
|
|
then publishing it is one wrong port name in an Ingress backend away, with
|
|
nothing in the diff that reads as a mistake. Separated, naming it is a
|
|
deliberate act.
|
|
|
|
AND IT MUST NEVER GET AN INGRESS. There is no value in this chart that would
|
|
publish it, and none should be added. An unauthenticated readiness endpoint on
|
|
the internet tells an attacker when the database is down; /metrics tells them
|
|
the realm and client names. Upstream's own alternative for this - setting
|
|
legacy-observability-interface=true, which moves health back onto the serving
|
|
port where the Ingress already is - is explicitly "not recommended for security
|
|
reasons", and this chart does not offer it.
|
|
*/ -}}
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-management-service
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
spec:
|
|
selector:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
|
ports:
|
|
- protocol: TCP
|
|
port: {{ $settings.ports.management | int64 }}
|
|
targetPort: management
|
|
name: management
|
|
type: ClusterIP
|