feat(keycloak): new chart for Keycloak (1.0.0+up26.8.0)
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
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
This commit is contained in:
19 files changed
+2563
No files matched your search
@@ -0,0 +1,108 @@
|
||||
{{- /*
|
||||
TWO INGRESS OBJECTS, AND THE SECOND ONE IS NOT A SECURITY BOUNDARY.
|
||||
|
||||
The public one publishes the login endpoints, the token endpoints and the
|
||||
account console. The admin one publishes the SAME backend under a second
|
||||
hostname, so that the administration console - whose URLs Keycloak generates
|
||||
from hostname-admin - can live on an internal ingress class or in a different
|
||||
DNS zone.
|
||||
|
||||
WHAT IT DOES NOT DO, because the value's name invites the opposite assumption.
|
||||
Upstream, verbatim: "Using the hostname-admin option does not prevent accessing
|
||||
the Administration REST API endpoints via the frontend URL specified by the
|
||||
hostname option. If you want to restrict access to the Administration REST API,
|
||||
you need to do it on the reverse proxy level." So /admin/realms/... stays
|
||||
reachable through ingress.domain, and an IP allow-list on the admin host
|
||||
restricts nothing by itself.
|
||||
|
||||
This chart deliberately renders NO deny rule for /admin on the public Ingress.
|
||||
Two reasons, and the second is the one that decided it: the administration
|
||||
console loads assets and performs redirects across more paths than /admin, so a
|
||||
rule written from the path name breaks the console in ways that only show up in
|
||||
a browser; and a rule nobody has tested against a running console is a measure
|
||||
that looks like one, which is worse than an absent one. Whoever needs the
|
||||
restriction puts it on the proxy in front of the public host, where it can be
|
||||
tested.
|
||||
|
||||
GUIDELINE 12.1, CHECKED IN BOTH DIRECTIONS: ingress.domain is read a second
|
||||
time outside this template - as KC_HOSTNAME on the Deployment - so it stays
|
||||
required even with ingress.enabled: false, and its required() lives in the
|
||||
keycloak.frontendUrl helper rather than here. clusterIssuer is read only in
|
||||
this file, so switching both Ingresses off frees it.
|
||||
*/ -}}
|
||||
{{- $adminEnabled := eq (include "keycloak.adminHostnameEnabled" .) "true" }}
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided (ingress.clusterIssuer)" .Values.ingress.clusterIssuer }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
rules:
|
||||
- host: {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
port:
|
||||
name: http
|
||||
path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}
|
||||
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
||||
{{- end }}
|
||||
{{- if $adminEnabled }}
|
||||
{{- if .Values.ingress.enabled }}
|
||||
---
|
||||
{{- end }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-admin-ingress
|
||||
labels:
|
||||
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
|
||||
annotations:
|
||||
# Its own certificate, for its own host. A separate TLS secret
|
||||
# (tls-<release>-keycloak-admin-ingress) rather than a shared one, because
|
||||
# these are two different hostnames that may well sit on two different
|
||||
# ingress classes - the shared-secret form in the style guide is for
|
||||
# several objects on ONE host.
|
||||
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided (ingress.clusterIssuer)" .Values.ingress.clusterIssuer }}
|
||||
{{- with ((.Values.ingress.admin | default dict).annotations) }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
# Falls back to ingress.className when not set. An internal ingress class
|
||||
# here is the useful half of the admin hostname - and still not the access
|
||||
# control the comment at the top of this file is about.
|
||||
ingressClassName: {{ (.Values.ingress.admin | default dict).className | default .Values.ingress.className }}
|
||||
rules:
|
||||
- host: {{ required "An admin Domain/Host must be provided (ingress.admin.domain) while ingress.admin.enabled is true" ((.Values.ingress.admin | default dict).domain) }}
|
||||
http:
|
||||
paths:
|
||||
# The SAME backend and the same path as the public Ingress. The
|
||||
# administration console is not a separate application and not a
|
||||
# separate path - only the hostname differs, and Keycloak generates
|
||||
# its admin URLs for that hostname because of KC_HOSTNAME_ADMIN.
|
||||
- backend:
|
||||
service:
|
||||
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
||||
port:
|
||||
name: http
|
||||
path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ required "An admin Domain/Host must be provided (ingress.admin.domain) while ingress.admin.enabled is true" ((.Values.ingress.admin | default dict).domain) }}
|
||||
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-admin-ingress
|
||||
{{- end }}
|
||||
Reference in new issue
Block a user