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:
1 parent
a04251bbc8
commit
72d4720d58
10 files changed
+584
-22
No files matched your search
@@ -12,6 +12,7 @@ stringData:
|
||||
semaphore-client-secret: ENC[AES256_GCM,data:BX5lXhFNX6vQh67G2+XM2vt9wymhL8Psx+NqQJneFup7gdZRjhk8paEU5dOfugZNwooieara7UFyAFm5+1BJFg==,iv:kmmz0ZiCZgsSowns3hjDTQzoD2lPCr9Oz1iS5WgbhEE=,tag:BbesQX6lxAhaGiqPqMQ9vA==,type:str]
|
||||
headlamp-client-secret: ENC[AES256_GCM,data:hlQ5m6FeqwOqX9HTT8jC6NZmhQH0kMgUveMZ2Cfen1zLLxWfYXXC1QMBWSqrchY2EpyIr7vNPbFjbqffwiSDHQ==,iv:WS1F3gkWn18STl4c38AlCe7TU6jvZpl/eULf2TnjIZ4=,tag:n2BnQYrm4gZbLeemDBykjA==,type:str]
|
||||
crafty-client-secret: ENC[AES256_GCM,data:j9HAqIVlr+y8xZ6RvOvtQrVBv/mTLK89KYu1oQMkeSMsjyZdwZlLwW+Q7oiO1hqpyEG7ht5z3x6dJegN4LaxQQ==,iv:mnG5boz0M2A6hDdLKBaneUPH75Ih1RhM2R2d77VG7Vs=,tag:TGQKb7+4lnmhS3DZm6Wz1A==,type:str]
|
||||
pihole-client-secret: ENC[AES256_GCM,data:lxheNbIev1Y6O0b3LJWvyROizOU2ts3WwZTa32x+efJwEek3dTBBc/3Nn0m5GKVNXv9lqkio9+qPTlZTmKZbLw==,iv:mfoIGDpGxc+f445Ev8dJY1YMe6WmOurMCDwdz80Cjbk=,tag:FPA2Ln97wG89NnLWdvnP+Q==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
@@ -27,8 +28,8 @@ sops:
|
||||
YXJGUUJiTnFndFhDZDU1bmxXTENCbmsKuhb3RQ3LZmuTPAXEck8b6iuZ5vxLb9fV
|
||||
+fQELmU4kOTbZrPSxy+kpttLG5Cy5d6X2ieZXBf6opzpLTUhlBOkHA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2026-08-28T20:36:54Z"
|
||||
mac: ENC[AES256_GCM,data:hXqmcUYihXpprhm0Sn9CxbxuJR12WZtMKRPi+320KjU3ZEOa2Bz0wqzMQFhM2fLa4GttBG49YbdDHhqk1z/hqtHgxJoeiIi9qQOrpKgRC5oI42x6CTS7uhyIeh0y8h1UlQWaBbJ6Z+ig4OQ/1W9PCFkxL9klt6tbnQlWGiIbSB4=,iv:rY+1gerz/mXgvXPH4HpRRd0Nus0kHV8xC6G4WZDEXyA=,tag:gofnyk3rx+RFmpRxoiF8GA==,type:str]
|
||||
lastmodified: "2026-08-31T13:33:15Z"
|
||||
mac: ENC[AES256_GCM,data:5wUiBt5+PxzAdGaKrf6wwx0TBjOC2doukzJSleRpYtN/Tf+ubMHNDa6C1sH/RFd83haPrws1ZoAedmyiBSwqwLhmslwil5ZHzbyaBs8fVrp/EcKgNmaUKfjv8AOj4q5MFWIb8eeKGCpt1GVGHlwg4ewfwRn+2mgPev1LKsNsaYc=,iv:X2APh51JZ16O17bz+cNLgXGdORaeYsdXvznbnVt8mwU=,tag:99ekaLyqhL4YPT34uo9xCA==,type:str]
|
||||
pgp: []
|
||||
encrypted_regex: ^(data|stringData)$
|
||||
version: 3.9.4
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user