Group 3 of the #114 sweep, first chart. keycloak has TWO Ingress objects - the public hostname and the admin hostname - so it gets TWO HTTPRoutes, with httpRoute.admin.parentRefs falling back to httpRoute.parentRefs exactly as ingress.admin.className falls back to ingress.className: "which data path" is a property of the Gateway in this API, not of the route, so an internal Gateway here is what an internal ingress class is in the other mode. The admin route stays gated on ingress.admin.enabled, because that value is not an Ingress switch - it also decides whether KC_HOSTNAME_ADMIN is set on the Deployment, i.e. whether the admin hostname exists at all. Switching to Gateway mode therefore must not require turning it off. FIXED ALONG THE WAY: the admin Ingress did not hang off ingress.enabled. Measured on origin/main, `helm template -f ci/keycloak/test-values.yaml --set ingress.enabled=false` emitted kc-keycloak-admin-ingress - so "ingress.enabled: false" did not mean "no Ingress", and ingress.clusterIssuer stayed required for an object nobody asked for. It also made Gateway mode impossible: the admin Ingress would have gone on claiming the admin host next to the admin HTTPRoute, one host over two data paths, which is the exact conflict the mutual exclusion exists to prevent. The default render is unchanged; what changed is a combination that could not be coherent. WHAT THIS MODE DROPS, MEASURED ON THE LIVE CLUSTER rather than inferred. On 2026-10-12 BOTH of this chart's Ingress objects in jk-cluster carried nginx.ingress.kubernetes.io/proxy-buffer-size: 16k nginx.ingress.kubernetes.io/proxy-buffers-number: 4 set by the OPERATOR through ingress.annotations and ingress.admin.annotations - the chart sets neither, which is why reading the chart alone does not reveal them. The Gateway API has no annotation channel and no counterpart for proxy buffer sizing; it is implementation policy, not routing. Per decision 1 of #114 the chart renders no policy object, so this is NAMED and not rebuilt, in values.yaml, in the schema and in the Gateway branch of NOTES.txt, with the consequence spelled out in Keycloak's own terms: large headers (JWTs, SAML assertions, long Set-Cookie chains on the login flow) make a too-small buffer answer 502 at the PROXY while Keycloak logs the request as successful. Named and deliberately not touched: four further Ingress objects in the same namespace (keycloak-realm-*-ingress, with app-root) belong to the Helm release jk-cluster-config-fleet-cd-keycloak-realm-ingress, i.e. to another repository. They point at this release's Service under four further hostnames. This chart renders exactly the two routes its own two Ingresses cover. Minor: additive, defaults unchanged. Empty helm template diff against origin/main with ci/keycloak/test-values.yaml. Part of #114
122 lines
6.0 KiB
YAML
122 lines
6.0 KiB
YAML
{{- /*
|
|
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.
|
|
|
|
BOTH OBJECTS HANG OFF ingress.enabled, AND THE ADMIN ONE DID NOT USED TO
|
|
(fixed with the Gateway-API support, Gitea #114). ingress.admin.enabled is NOT
|
|
an Ingress switch: it decides whether KC_HOSTNAME_ADMIN is set on the
|
|
Deployment, i.e. whether the admin hostname is in play at all - so the admin
|
|
Ingress used to render with ingress.enabled: false, which meant
|
|
"ingress.enabled: false" did not mean "no Ingress" and clusterIssuer was still
|
|
required for an object nobody asked for. Measured before the fix:
|
|
`helm template -f ci/keycloak/test-values.yaml --set ingress.enabled=false`
|
|
emitted kc-keycloak-admin-ingress and nothing else of the routing path. It also
|
|
made Gateway mode impossible: switching httpRoute on leaves ingress.enabled
|
|
false, and the admin Ingress would have gone on claiming the admin host next to
|
|
the admin HTTPRoute - one host, two data paths, the exact conflict the mutual
|
|
exclusion exists to prevent. The default render is unchanged (ingress.enabled
|
|
defaults to true); what changed is a combination that could not be coherent.
|
|
*/ -}}
|
|
{{- $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 and .Values.ingress.enabled $adminEnabled }}
|
|
---
|
|
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 }}
|