feat(auth): gate pihole UI with per-app oauth2-proxy sidecar (canary)

Close the un-gated-UI security finding for forward-auth apps (R-IDP-02) by
rendering the Option-B per-app oauth2-proxy reverse-proxy sidecar that crafty
proves working. catalog/render.py now emits, for any forward-auth app with an
exposed UI: the oauth2-proxy sidecar (upstream http://127.0.0.1:<uiport>,
:4180, --allowed-group per auth.group), an authproxy :4180 Service port, an
HTTPRoute backendRef -> :4180, and a gateway-ingress CiliumNetworkPolicy that
allows the Gateway to reach ONLY :4180 (fail-closed: the bare UI is never
served un-authenticated). oidc/native/none apps render byte-identical.

This is the CANARY: only pihole's golden carries the sidecar and is verified
fail-closed in-cluster first. jackett/qbittorrent/radarr/sonarr are deferred to
the rollout commit (temporarily listed in ci/render-golden.sh) so their UIs
keep serving exactly as today until their clients+secrets land.

- keycloak: confidential pihole client (realm-import + realm-clients mirror),
  provisioned live via kcadm (groups membership mapper, bare names).
- secret: SOPS/age pihole-oauth2-proxy-credentials (client-id/secret/cookie),
  wired into apps/wave-0 via the renderer's new sidecar-creds wave rule.
- L1 render goldens regenerated for the sidecar (forward-auth fixtures).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 4.8 committed 2026-08-31 08:36:03 -05:00
1 parent a04251bbc8
commit 72d4720d58
10 files changed
+584 -22

No files matched your search

@@ -43,6 +43,10 @@ spec:
secret:
name: keycloak-realm-clients
key: crafty-client-secret
PIHOLE_CLIENT_SECRET:
secret:
name: keycloak-realm-clients
key: pihole-client-secret
realm:
realm: homelab
enabled: true
@@ -360,6 +364,41 @@ spec:
"access.token.claim": "true"
"userinfo.token.claim": "true"
"claim.name": groups
# pihole — the DNS admin UI (catalog/pihole.yaml auth.mode forward-auth). PiHole's UI has NO
# native OIDC, so it is gated by a PER-APP oauth2-proxy REVERSE-PROXY sidecar (Option B, rendered
# by catalog/render.py) — this is that sidecar's confidential auth-code client, modeled on the
# crafty/oauth2-proxy clients. Redirect URI is the oauth2-proxy callback on PiHole's host. The
# `groups` claim (bare names) lets the sidecar enforce --allowed-group=platform-admins (R-IDP-06).
# Secret injected from the SOPS keycloak-realm-clients Secret via PIHOLE_CLIENT_SECRET (R-IDP-04);
# the SAME value is mirrored in apps/wave-0/pihole-oauth2-proxy-credentials.sops.yaml (cross-
# namespace mirror). Provisioned live via kcadm at authoring time (2026-08-31) and captured here
# as the durable GitOps source of truth (the operator imports the realm once; kcadm is authoritative).
- clientId: pihole
name: PiHole (oauth2-proxy)
protocol: openid-connect
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
secret: "$(env:PIHOLE_CLIENT_SECRET)"
redirectUris:
- https://pihole.home.keithjackson.dev/oauth2/callback
webOrigins:
- https://pihole.home.keithjackson.dev
defaultClientScopes:
- email
- profile
- roles
protocolMappers:
- name: groups
protocol: openid-connect
protocolMapper: oidc-group-membership-mapper
config:
"full.path": "false"
"id.token.claim": "true"
"access.token.claim": "true"
"userinfo.token.claim": "true"
"claim.name": groups
# --- MFA for platform-admins (R-IDP-04): a browser flow whose OTP step is CONDITIONAL on the
# platform-admin role. Non-admins log in without OTP; platform-admins must enroll/present TOTP.
# Set as the realm browserFlow below. VERIFY at bring-up (see runbook): a malformed flow fails