Commit Graph
4 Commits
Author SHA1 Message Date
jakman85andClaude Opus 4.8 68690e5019 fix(net): allow SSO apps egress to keycloak backend (Cilium 1.20 CVE-2025-30162)
App SSO broke cluster-wide after the Cilium 1.16.5->1.20.1 upgrade: the CVE-2025-30162 fix (in
>=1.16.8/1.17.2) makes the shared Gateway's L7 proxy re-evaluate a pod's egress policy against
Keycloak's REAL backend cluster identity (enforce_policy_on_l7lb) when it hairpins to keycloak.home.
'toEntities: world' matches the Gateway VIP at L3/L4 but not the resolved keycloak identity at L7, so
OIDC discovery/token 403'd ('Access denied') for every app with a NetworkPolicy (gitea/nextcloud/
semaphore/headlamp/crafty...); Grafana survived only because monitoring has no NetworkPolicy.

Fix: explicit toEndpoints egress allow to the keycloak backend for SSO apps.
- catalog/render.py: emit it for auth.mode in {oidc,forward-auth}; re-rendered 8 app goldens.
- platform/headlamp + platform/crafty: hand-written policies get the same allow.
Split-horizon design unchanged. Verified live: headlamp-ns -> keycloak discovery 403 -> 200.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-30 23:34:03 -05:00
jakman85andClaude Opus 4.8 071123ae43 fix(headlamp): rename OIDC secret keys to OIDC_* so chart args expand
Headlamp OIDC login failed with `unsupported protocol scheme ""`. The
kubernetes-sigs/headlamp chart `envFrom`s the headlamp-oidc Secret verbatim
and renders args like `-oidc-idp-issuer-url=$(OIDC_ISSUER_URL)`, so the Secret
keys must be the exact env var names. The keys were camelCase
(clientID/issuerURL/scopes/callbackURL/clientSecret), following the mistaken
`.example`, so `OIDC_ISSUER_URL` was never defined. Kubernetes left the literal
`$(OIDC_ISSUER_URL)` in the arg and Headlamp's OIDC discovery hit
`Get "$(OIDC_ISSUER_URL)/.well-known/openid-configuration": unsupported
protocol scheme ""`.

Rename the SOPS Secret keys to OIDC_CLIENT_ID / OIDC_CLIENT_SECRET /
OIDC_ISSUER_URL / OIDC_SCOPES / OIDC_CALLBACK_URL (values unchanged, still
SOPS/age-encrypted). Fix the `.example` and README so the correct, load-bearing
key names are documented and the camelCase footgun can't recur.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-29 15:09:15 -05:00
jakman85andClaude Opus 4.8 ebe0371b1b platform: move Headlamp out of the gating platform tier + fix -session-ttl crashloop
Headlamp's HelmRelease sat in the wait:true `platform` Flux tier. Its pod
crashlooped (`flag provided but not defined: -session-ttl` — chart 0.40.1
renders a container arg the appVersion v0.40.1 backend lacks; upstream
kubernetes-sigs/headlamp#4883), and because it gated `platform`, the whole
chain stalled: platform Ready=Unknown -> platform-config, oauth2-proxy,
apps-wave-0/1/2 and apps-vms all Ready=False "dependency not ready".

Part A (unblock + durable lesson): a leaf UI must never gate the chain. Move
all Headlamp resources into a new non-gating Flux Kustomization
clusters/base/headlamp.yaml (dependsOn platform-config, wait:false, SOPS
decryption patched per-env in clusters/dev) — mirroring oauth2-proxy exactly.
The new Kustomization owns the whole platform/headlamp component; its HTTPRoute
moves from platform-config into platform/headlamp/kustomization.yaml. Removed
headlamp from platform/kustomization.yaml and its httproute from
platform-config/kustomization.yaml. Now a crashlooping Headlamp can never stall
platform/platform-config/apps.

Part B (make it start): pin the chart to 0.41.0 (`>=0.41.0 <0.42.0`). The
backend added `f.Int("session-ttl", …)` in v0.41.0 and chart 0.41.0 pins that
same image (appVersion == chart version), so chart+image realign; every arg the
chart renders for our values is supported. OIDC config + per-user RBAC
(clusterRoleBinding.create:false) unchanged.

Gate: `make -C ci gate` PASS.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-28 12:01:01 -05:00
jakman85andClaude Opus 4.8 5fb097d019 platform: deploy Headlamp dashboard with Keycloak OIDC + per-user RBAC
Add Headlamp (kubernetes-sigs/headlamp Helm chart) at
headlamp.home.keithjackson.dev with native Keycloak OIDC login and PER-USER
RBAC: Headlamp forwards each user's id_token to the k3s apiserver, so the
existing oidc:* group bindings (platform-admins->cluster-admin, viewers->view)
apply per user. The ServiceAccount is deliberately NOT bound to cluster-admin
(clusterRoleBinding.create=false), since user requests ride the forwarded token.

Key integration nuance: the apiserver is configured oidc-client-id=kubernetes,
so it only accepts tokens whose aud contains `kubernetes`. The new `headlamp`
Keycloak client carries an oidc-audience-mapper (included.client.audience=
kubernetes) so tokens carry aud=[headlamp, kubernetes] -- accepted by both
Headlamp's own verifier and the apiserver, leaving the apiserver config
untouched. A groups mapper emits the `groups` claim the apiserver maps to RBAC.

- platform/headlamp: namespace, HelmRepository/HelmRelease, SOPS OIDC secret
  (externalSecret envFrom), default-deny NetworkPolicy + Cilium ingress
  (gateway)/egress (apiserver+Keycloak) policies, README. Wired into
  platform/kustomization.yaml (platform tier).
- platform/headlamp/httproute.yaml wired into platform-config (Gateway API CR,
  dependsOn platform), modeled on grafana-httproute.
- keycloak-realm-import: add `headlamp` confidential client + groups/audience
  mappers + HEADLAMP_CLIENT_SECRET placeholder. Client also created LIVE via
  kcadm (realm import no-ops on the existing realm).
- keycloak-realm-clients.sops: add headlamp-client-secret (mirrored into
  headlamp-oidc-credentials clientSecret). Both encrypted to the repo age key.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-28 11:44:33 -05:00