feat(keycloak): one hostname per realm, in both routing modes
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build keycloak (pull_request) Successful in 8s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 24s
Install-test / Install-test keycloak (pull_request) Successful in 3m29s
Validate Helm Charts / Install-test (pull_request) Successful in 3m29s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build keycloak (pull_request) Successful in 8s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 24s
Install-test / Install-test keycloak (pull_request) Successful in 3m29s
Validate Helm Charts / Install-test (pull_request) Successful in 3m29s
keycloak.realmHosts gives a realm a hostname of its own: this release's Service published under a further name, with that name's bare / sent to /realms/<realm>/account/. It renders in BOTH modes - one Ingress per entry carrying nginx.ingress.kubernetes.io/app-root, or one HTTPRoute per entry whose Exact match on / carries a RequestRedirect filter - because a routing value in this repository is mode-independent by design - ingress.domain is read in Gateway mode too - so a feature rendering in one mode only would silently disappear when an installation switched. Default []: nothing renders, and the render is byte-identical to 1.2.0 for the fixture's values in both modes. Minor, additive, no value becomes required. ONE OBJECT PER ENTRY, not one with several hostnames. spec.hostnames applies to the whole object and a rule's matches are path-based, so inside a shared route every entry would match Exact: / and the realm could not be told apart. Names therefore carry the LIST INDEX, as homeassistant's per-host routes do and for the same reason - a realm name is not a dependable object-name fragment. The consequence is written down rather than left to be discovered: reordering the list renames and recreates the objects. THE WORK IS IN THE INGRESS HALF, NOT THE ROUTE. Two convention rules do not survive these objects, and both deviations are required rather than tolerated: tlsSecretName is a per-entry value with NO default, because these hostnames already have certificates and several entries typically share one, so a derived name would ask cert-manager for a second certificate for a name a wildcard already covers; and the cert-manager annotation is OPTIONAL per entry, because cert-manager keys a Certificate by the secret it writes and only one object per secret may request it - the same deliberate omission the qBittorrent object in freesailarr makes. Annotations are per entry too: of the four hostnames this was written for, three carry the proxy-buffer pair and one does not, which a chart-global block could not express. statusCode: 302 is written out at the field's own default. 301 and 302 are both Support: Core, so the portability trap of #119 - an implementation declining an Extended code sets Accepted: False on the WHOLE route - does not apply here; ingress-nginx emits 302 for app-root, and a value that is only accidentally right documents nothing. Five render aborts, each against a failure that reports nothing: an entry without a host (ingress-nginx reads an empty host as a catch-all for the whole controller), without a realm (/realms//account/, a 404 behind a healthy hostname and certificate), without a tlsSecretName in Ingress mode (the controller's default certificate, a browser-only failure), a repeated host or one the release already publishes (webhook rejection in Ingress mode, two merged routes decided by object age in Gateway mode), and a filled list with both modes off (no object at all, and nothing in the release that reveals the list was ignored). The entry-shape checks sit in a shared helper so they exist in both modes, and the Ingress template skips them while httpRoute.enabled is also true so that the specific message cannot hide the mutual-exclusion one. CI: the two entries live in ci/keycloak/test-values.yaml, which ci/httproute/keycloak.yaml is applied on top of, so one definition covers both modes - two Ingress objects in the install-test, two HTTPRoutes in the render test. They cover both Ingress forms, with and without a clusterIssuer, and the render test holds their inertness in Gateway mode. The httproute match test goes from 56 assertions over 21 charts to 60, keycloak's share from 4 to 8 plus two "not checked" lines for the backend-less redirect rules. The #118 prefix/redirect pairing correctly does not fire: it is scoped to rules that strip a prefix, and a realm route strips nothing. NOT IN THIS CHANGE: the migration. Nothing in jk-cluster-config is touched. With the default [] no running system changes, so the order a takeover has to run in is documented instead - remove the objects in the owning repository first, then install with the list filled, because the reverse or both at once is rejected by ingress-nginx's admission webhook and leaves a Fleet bundle in ErrApplied until forceSyncGeneration is raised. Whether anything there depends on those objects BY NAME is unchecked and named as unchecked. Fixes #122
This commit is contained in:
10 files changed
+629
-16
No files matched your search
@@ -18,6 +18,7 @@ see of it.
|
||||
{{- if not $adminHostnames -}}
|
||||
{{- $adminHostnames = list (.Values.ingress.admin | default dict).domain -}}
|
||||
{{- end -}}
|
||||
{{- $realmHosts := (fromYaml (include "keycloak.settings" .)).realmHosts | default list -}}
|
||||
{{- $deploy := printf "%s-%s-deployment" .Release.Name .Chart.Name -}}
|
||||
{{- $svc := printf "%s-%s-service" .Release.Name .Chart.Name -}}
|
||||
Keycloak {{ .Chart.AppVersion }} installed as release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
@@ -45,16 +46,40 @@ ENDPOINTS - {{ if .Values.ingress.enabled }}INGRESS MODE (ingress.enabled: true,
|
||||
/health, /health/live, /health/ready, /health/started{{ if $settings.metrics.enabled }} and /metrics{{ end }}
|
||||
WITHOUT authentication. It has a ClusterIP Service
|
||||
({{ .Release.Name }}-{{ .Chart.Name }}-management-service) for in-cluster scraping only.
|
||||
{{ if $realmHosts }}
|
||||
REALM HOSTNAMES - {{ len $realmHosts }} further name(s), each landing in ONE realm
|
||||
|
||||
The object names carry the LIST INDEX and not the realm, so this is the only
|
||||
place that says which is which. Reordering keycloak.realmHosts therefore
|
||||
renames these objects and recreates them.
|
||||
{{ range $i, $entry := $realmHosts }}
|
||||
https://{{ $entry.host }}
|
||||
realm {{ $entry.realm }} (bare / -> /realms/{{ $entry.realm }}/account/)
|
||||
object {{ $.Release.Name }}-{{ $.Chart.Name }}-realm-{{ if $httpRoute.enabled }}httproute{{ else }}ingress{{ end }}{{ if $i }}-{{ $i }}{{ end }}
|
||||
{{- if not $httpRoute.enabled }}
|
||||
cert {{ $entry.tlsSecretName }}{{ if $entry.clusterIssuer }}, requested by THIS object ({{ $entry.clusterIssuer }}){{ else }}, NOT requested by this object - it must already exist or be issued elsewhere{{ end }}
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
{{- /*
|
||||
No "and nothing publishes them" branch here, deliberately: a filled
|
||||
keycloak.realmHosts with both modes off ABORTS the render in
|
||||
keycloak.ingress.yaml, so this block can only ever be reached with a mode on.
|
||||
A branch for an impossible state reads like a state somebody has seen.
|
||||
*/ -}}
|
||||
{{- end }}
|
||||
{{ if $httpRoute.enabled }}
|
||||
GATEWAY MODE - WHAT THIS CHART DOES NOT RENDER, AND WHAT YOU MUST SET YOURSELF
|
||||
|
||||
This release renders {{ if $adminHostname }}TWO HTTPRoutes{{ else }}ONE HTTPRoute{{ end }} and NOTHING else of the routing path -
|
||||
This release renders {{ add (ternary 2 1 $adminHostname) (len $realmHosts) }} HTTPRoute(s) and NOTHING else of the routing path -
|
||||
no Gateway, no class, no Certificate, no policy object.
|
||||
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-httproute {{ join ", " $hostnames }}
|
||||
{{- if $adminHostname }}
|
||||
{{ .Release.Name }}-{{ .Chart.Name }}-admin-httproute {{ join ", " $adminHostnames }}
|
||||
{{- end }}
|
||||
{{- range $i, $entry := $realmHosts }}
|
||||
{{ $.Release.Name }}-{{ $.Chart.Name }}-realm-httproute{{ if $i }}-{{ $i }}{{ end }} {{ $entry.host }} (realm {{ $entry.realm }})
|
||||
{{- end }}
|
||||
|
||||
Three things therefore belong to the cluster, and if any is missing the
|
||||
hostnames above simply do not answer:
|
||||
@@ -69,6 +94,11 @@ GATEWAY MODE - WHAT THIS CHART DOES NOT RENDER, AND WHAT YOU MUST SET YOURSELF
|
||||
{{- range ($httpRouteAdmin.parentRefs | default $httpRoute.parentRefs) }}
|
||||
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }} (admin)
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $i, $entry := $realmHosts }}
|
||||
{{- range ($entry.parentRefs | default $httpRoute.parentRefs) }}
|
||||
{{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }} (realm {{ $entry.realm }})
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
must exist AND their listeners must allow routes from this namespace
|
||||
({{ .Release.Namespace }}) in allowedRoutes. A rejected attachment is visible only
|
||||
@@ -84,6 +114,12 @@ GATEWAY MODE - WHAT THIS CHART DOES NOT RENDER, AND WHAT YOU MUST SET YOURSELF
|
||||
chart renders no Certificate here, so there is no
|
||||
tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress secret and nothing for cert-manager to do
|
||||
for this release.
|
||||
{{- if $realmHosts }}
|
||||
The realm hostnames above are part of that: their tlsSecretName values
|
||||
are not read in this mode either. Where several of them share a DNS zone,
|
||||
ONE wildcard listener for that zone replaces one certificate per name -
|
||||
which is the one thing this mode makes cheaper here rather than dearer.
|
||||
{{- end }}
|
||||
|
||||
AND THE ONE YOU WILL NOT NOTICE UNTIL A LOGIN FAILS: THE PROXY BUFFERS.
|
||||
|
||||
@@ -109,6 +145,28 @@ GATEWAY MODE - WHAT THIS CHART DOES NOT RENDER, AND WHAT YOU MUST SET YOURSELF
|
||||
the Gateway API has no annotation channel for controller behaviour. An IP
|
||||
allow-list for the admin host is the other thing that has to move - and read
|
||||
the first trap below before relying on it in either mode.
|
||||
{{ if $realmHosts }}
|
||||
AND THE SAME LOSS ONCE MORE, ON THE REALM HOSTNAMES - where it is likelier to
|
||||
be met rather than merely possible.
|
||||
|
||||
Each entry of keycloak.realmHosts carries its own annotations block in Ingress
|
||||
mode, and in this mode every one of them is gone: {{ len $realmHosts }} hostname(s) here, none
|
||||
of which can configure a proxy buffer through this chart any more. What the
|
||||
entries CAN still express is the routing - the redirect into the realm is a
|
||||
rule of the route and survives the mode change intact.
|
||||
|
||||
Why these hostnames are the awkward ones: they exist to put somebody in an
|
||||
ACCOUNT CONSOLE. That is a logged-in session from the first request after the
|
||||
redirect, so the large Authorization and Set-Cookie headers are the normal
|
||||
traffic on these names rather than an edge case the way they are on a login
|
||||
host that mostly serves anonymous requests. A buffer sized for the default
|
||||
therefore fails here FIRST, and it fails as a 502 from the Gateway on a
|
||||
hostname whose Ingress never had a chart value to point at - so there is
|
||||
nothing to grep for and nothing to un-set.
|
||||
|
||||
Set the buffer equivalent for these hostnames too, in the Gateway
|
||||
implementation's own object, and set it before the names resolve there.
|
||||
{{- end }}
|
||||
|
||||
THE ADMIN HOSTNAME'S "INTERNAL CLASS" MOVES TO THE PARENT. What
|
||||
ingress.admin.className does in the other mode, httpRoute.admin.parentRefs
|
||||
|
||||
Reference in new issue
Block a user