feat(k8s): terminate TLS in the cluster for a public domain (refs #177)

`public.domain` is the whole switch. Empty — the default, and what compose, CI
and a laptop cluster use — renders nothing new and leaves every manifest as it
was. Set it and templates/edge.yaml adds a Caddy deployment that gets its own
certificates from Let's Encrypt and proxies the five browser-facing hostnames to
the ClusterIP services, so a public deployment doesn't use their NodePorts at
all.

Caddy rather than an ingress controller because the four portals already run
caddy:2-alpine (ADR-0034, whose ceiling note called exactly this out): no new
dependency, no cert-manager, no CRDs, no Ingress objects for five hostnames that
never change. The Fedora host keeps only a layer-4 forward of 80/443, because
the public IP is there and nothing in the cluster can claim it.

KC_HOSTNAME and the portals' config.json now both come from `big.keycloakUrl`,
so the issuer a token carries and the authority the BFF discovers are one string
by construction (ADR-0010) rather than by two templates agreeing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
not
2026-09-18 16:30:38 +02:00
co-authored by Claude Opus 5
parent 88fda30008
commit 56cba9c340
5 changed files with 179 additions and 3 deletions
@@ -135,6 +135,20 @@ cluster-internal hosts ({{ .Release.Namespace }}) and the node address
{{- end }}
{{- end -}}
{{/*
The origin a browser reaches Keycloak on, and so the issuer its tokens carry and
the authority the portals are configured with (ADR-0010). With a public edge that
is the `auth` hostname on `public.domain` — which must stay in step with the `auth`
key in `public.routes`; without one it is the node address plus Keycloak's NodePort.
*/}}
{{- define "big.keycloakUrl" -}}
{{- if .Values.public.domain -}}
https://auth.{{ .Values.public.domain }}
{{- else -}}
http://{{ .Values.host }}:{{ index .Values.nodePorts "keycloak" }}
{{- end -}}
{{- end -}}
{{- define "big.labels" -}}
app.kubernetes.io/name: {{ .name }}
app.kubernetes.io/instance: {{ .root.Release.Name }}