Files
register-referentie/infra/helm/check-issuer.py
T
notandClaude Opus 5 88fda30008 test(k8s): pin the issuer, the portal authority and the public edge (refs #177)
Keycloak pins one issuer and each portal is configured with one authority; when
they drift the symptom lands three services away — a login that bounces back
logged out, or a 401 from the BFF (ADR-0010) — so assert they are the same
string. The same check states what publishing the stack has to mean: with
`public.domain` set the five hostnames are served and both halves become
`https://auth.<domain>`, and with it empty nothing of the edge renders, which is
what compose, CI and a laptop cluster depend on.

Red: the chart has no `public.domain`, so setting it changes nothing and no
hostname is published.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:30:38 +02:00

81 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Fail when the pinned issuer and the portals' OIDC authority stop agreeing.
Keycloak pins one issuer (`KC_HOSTNAME`) and each portal is configured with one
authority (`config.json`). A browser token carries the first; the BFF validates
against what it discovers from the second (ADR-0010). When the two drift the
symptom is three services away — a login that bounces back logged out, or a 401
from the BFF — so the chart builds both from one helper and this asserts it.
It also pins the two halves of the public edge (ADR-0035): that setting
`public.domain` actually publishes the hostnames, and that leaving it empty
renders no edge at all, which is what compose, CI and a laptop cluster rely on.
Run it with `make k8s-lint`. No cluster needed.
"""
import json
import re
import subprocess
import sys
from pathlib import Path
CHART = Path(__file__).resolve().parent / "big-reference"
DOMAIN = "example.test"
def render(*sets: str) -> str:
argv = ["helm", "template", "big", str(CHART), "-n", "big"]
for s in sets:
argv += ["--set", s]
proc = subprocess.run(argv, capture_output=True, text=True)
if proc.returncode != 0:
sys.exit(f"helm template failed:\n{proc.stderr}")
return proc.stdout
def issuer(out: str) -> str:
"""The value of KC_HOSTNAME in the rendered manifests."""
m = re.search(r"name: KC_HOSTNAME\n\s+value: \"(\S+)\"", out)
return m[1] if m else ""
def authorities(out: str) -> set[str]:
"""Every portal's OIDC authority, with the realm path stripped."""
found = set()
for line in re.findall(r'\{ "authority": .* \}', out):
url = json.loads(line)["authority"]
found.add(url.rsplit("/realms/", 1)[0])
return found
def main() -> int:
problems = []
public = render(f"public.domain={DOMAIN}")
if issuer(public) != f"https://auth.{DOMAIN}":
problems.append(f" with public.domain set, KC_HOSTNAME is {issuer(public)!r}, not https://auth.{DOMAIN}")
if authorities(public) != {f"https://auth.{DOMAIN}"}:
problems.append(f" with public.domain set, the portals point at {sorted(authorities(public))}")
for host in (f"register.{DOMAIN}", f"mijn.{DOMAIN}", f"behandel.{DOMAIN}", f"beheer.{DOMAIN}", f"auth.{DOMAIN}"):
if host not in public:
problems.append(f" {host} is not published by the edge")
private = render()
if authorities(private) != {issuer(private)}:
problems.append(f" by default the portals point at {sorted(authorities(private))}, the issuer is {issuer(private)!r}")
if "caddy-edge" in private:
problems.append(" the edge renders with no public.domain — compose, CI and a laptop cluster expect nothing")
if problems:
print("the chart's OIDC origin is inconsistent:\n" + "\n".join(problems))
print("\nBoth halves come from the `big.keycloakUrl` helper — change it, not one caller.")
return 1
print(f"issuer + portal authority agree, with and without a public domain")
return 0
if __name__ == "__main__":
raise SystemExit(main())