From 5da116b34deee4bc239d6f224232a5a21d3976b7 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Sat, 12 Sep 2026 17:12:05 -0500 Subject: [PATCH] =?UTF-8?q?fix(oauth2-proxy):=20platform-admins=20may=20re?= =?UTF-8?q?ach=20the=20media=20UIs=20=E2=80=94=20the=20immediate=20403?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four forward-auth media UIs (jackett, qbittorrent, radarr, sonarr) gate on `--allowed-group=media-users` alone. On prod the `homelab` realm has exactly one user — the operator's `jakman85` — and it is a member of `platform-admins` ONLY. `media-users` has zero members, so those four hosts were reachable by nobody: oauth2-proxy completed OIDC discovery, redirected to Keycloak, got a token whose `groups` claim is `["platform-admins"]`, found no allowed group in it, and answered 403. No login form appeared because the browser already held a Keycloak SSO session from a platform-admins login — hence "403 immediately". pihole and crafty were unaffected: they already gate on platform-admins. `--allowed-group` is repeatable and OR-ed, so listing both groups means "media consumers OR platform operators". This grants no real privilege — platform-admins already maps to cluster-admin via k8s-oidc-rbac.yaml (oidc:platform-admins), so a member can already exec into these pods — and it keeps media-users meaningful for non-admin household accounts. Ruled out while diagnosing: client-secret drift (all 7 sidecar secrets hash-match keycloak-realm-clients), stale apex URLs (every live sidecar carries keycloak.prod.home.keithjackson.dev), missing group mappers (all 7 clients emit claim=groups, full.path=false), and the central oauth2-proxy pod restarts — those were Keycloak returning 503 "no healthy upstream" to OIDC discovery at startup, a consequence of the Keycloak roll, not a cause. Goldens re-rendered with the canonical command: python3 catalog/render.py --all catalog -o apps --wave-layout --no-image-automation R-IDP-06 Co-Authored-By: Claude Opus 5 --- apps/wave-0/jackett.yaml | 5 +++-- apps/wave-0/qbittorrent.yaml | 5 +++-- apps/wave-1/radarr.yaml | 5 +++-- apps/wave-1/sonarr.yaml | 5 +++-- catalog/jackett.yaml | 9 +++++++++ catalog/qbittorrent.yaml | 5 +++++ catalog/radarr.yaml | 5 +++++ catalog/sonarr.yaml | 5 +++++ 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/apps/wave-0/jackett.yaml b/apps/wave-0/jackett.yaml index 7af66d6..0f21bec 100644 --- a/apps/wave-0/jackett.yaml +++ b/apps/wave-0/jackett.yaml @@ -74,7 +74,7 @@ spec: homelab.keithjackson.dev/auth-mode: forward-auth annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: containers: - name: jackett @@ -97,6 +97,7 @@ spec: - --scope=openid email profile - --oidc-groups-claim=groups - --allowed-group=media-users + - --allowed-group=platform-admins - --skip-provider-button=true - --redirect-url=https://jackett.${BASE_DOMAIN}/oauth2/callback - --cookie-domain=.${BASE_DOMAIN} @@ -266,7 +267,7 @@ metadata: app.kubernetes.io/name: jackett annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: parentRefs: - name: shared diff --git a/apps/wave-0/qbittorrent.yaml b/apps/wave-0/qbittorrent.yaml index fb17766..f956793 100644 --- a/apps/wave-0/qbittorrent.yaml +++ b/apps/wave-0/qbittorrent.yaml @@ -74,7 +74,7 @@ spec: homelab.keithjackson.dev/auth-mode: forward-auth annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: containers: - name: qbittorrent @@ -97,6 +97,7 @@ spec: - --scope=openid email profile - --oidc-groups-claim=groups - --allowed-group=media-users + - --allowed-group=platform-admins - --skip-provider-button=true - --redirect-url=https://qbittorrent.${BASE_DOMAIN}/oauth2/callback - --cookie-domain=.${BASE_DOMAIN} @@ -266,7 +267,7 @@ metadata: app.kubernetes.io/name: qbittorrent annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: parentRefs: - name: shared diff --git a/apps/wave-1/radarr.yaml b/apps/wave-1/radarr.yaml index cb406c2..dd83afd 100644 --- a/apps/wave-1/radarr.yaml +++ b/apps/wave-1/radarr.yaml @@ -118,7 +118,7 @@ spec: homelab.keithjackson.dev/auth-mode: forward-auth annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: containers: - name: radarr @@ -150,6 +150,7 @@ spec: - --scope=openid email profile - --oidc-groups-claim=groups - --allowed-group=media-users + - --allowed-group=platform-admins - --skip-provider-button=true - --redirect-url=https://radarr.${BASE_DOMAIN}/oauth2/callback - --cookie-domain=.${BASE_DOMAIN} @@ -346,7 +347,7 @@ metadata: app.kubernetes.io/name: radarr annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: parentRefs: - name: shared diff --git a/apps/wave-1/sonarr.yaml b/apps/wave-1/sonarr.yaml index 6cfd516..e5fa7ab 100644 --- a/apps/wave-1/sonarr.yaml +++ b/apps/wave-1/sonarr.yaml @@ -118,7 +118,7 @@ spec: homelab.keithjackson.dev/auth-mode: forward-auth annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: containers: - name: sonarr @@ -150,6 +150,7 @@ spec: - --scope=openid email profile - --oidc-groups-claim=groups - --allowed-group=media-users + - --allowed-group=platform-admins - --skip-provider-button=true - --redirect-url=https://sonarr.${BASE_DOMAIN}/oauth2/callback - --cookie-domain=.${BASE_DOMAIN} @@ -346,7 +347,7 @@ metadata: app.kubernetes.io/name: sonarr annotations: homelab.keithjackson.dev/auth-mode: forward-auth - homelab.keithjackson.dev/auth-groups: media-users + homelab.keithjackson.dev/auth-groups: media-users,platform-admins spec: parentRefs: - name: shared diff --git a/catalog/jackett.yaml b/catalog/jackett.yaml index 6c4d742..cc0d250 100644 --- a/catalog/jackett.yaml +++ b/catalog/jackett.yaml @@ -42,6 +42,15 @@ auth: mode: forward-auth # oauth2-proxy -> Keycloak; bare UI, no usable native OIDC (docs/15; R-IDP-02) groups: - media-users # authenticated AND in an authorized group (R-IDP-06) + # platform-admins is a SUPERSET, not a widening. --allowed-group is repeatable and OR-ed, so + # listing both means "media consumers OR platform operators". Without it the media UIs are + # reachable by NOBODY the moment media-users is empty: on prod the realm's only user is the + # operator, who is in platform-admins only, so jackett/qbittorrent/radarr/sonarr answered an + # immediate 403 at the oauth2-proxy callback (no login prompt, because the Keycloak SSO session + # already existed) while pihole/crafty — which already gate on platform-admins — worked. + # This grants no real privilege: platform-admins already maps to cluster-admin via + # k8s-oidc-rbac.yaml (oidc:platform-admins), so a member can already exec into these pods. + - platform-admins # operators of these services must not be locked out of them update: channel: stable # R-UPD-01 range: ">=0.22.0" # stay on the Jackett 0.22.x stable line diff --git a/catalog/qbittorrent.yaml b/catalog/qbittorrent.yaml index 2efc778..f0fe0bc 100644 --- a/catalog/qbittorrent.yaml +++ b/catalog/qbittorrent.yaml @@ -57,6 +57,11 @@ auth: mode: forward-auth # oauth2-proxy -> Keycloak; bare UI, no usable native OIDC (docs/15; R-IDP-02) groups: - media-users # authenticated AND in an authorized group (R-IDP-06) + # See catalog/jackett.yaml for the full rationale: --allowed-group is repeatable and OR-ed, so + # platform-admins here is a superset, not a widening (that group already carries cluster-admin + # via k8s-oidc-rbac.yaml). Without it, an empty media-users locks every operator out with an + # immediate 403 at the oauth2-proxy callback — observed on prod 2026-09-12. + - platform-admins # operators of these services must not be locked out of them update: channel: stable # R-UPD-01 range: ">=5.0.0 <6.0.0" # stay on the qBittorrent 5.x stable line diff --git a/catalog/radarr.yaml b/catalog/radarr.yaml index bf40ff5..ce90d7b 100644 --- a/catalog/radarr.yaml +++ b/catalog/radarr.yaml @@ -107,6 +107,11 @@ auth: mode: forward-auth # bare UI, no usable OIDC: oauth2-proxy → Keycloak at the Gateway (docs/15; R-IDP-02) groups: - media-users # authenticated AND in the media group (R-IDP-06) + # See catalog/jackett.yaml for the full rationale: --allowed-group is repeatable and OR-ed, so + # platform-admins here is a superset, not a widening (that group already carries cluster-admin + # via k8s-oidc-rbac.yaml). Without it, an empty media-users locks every operator out with an + # immediate 403 at the oauth2-proxy callback — observed on prod 2026-09-12. + - platform-admins # operators of these services must not be locked out of them update: channel: stable # R-UPD-01 range: ">=5.0.0 <6.0.0" # stay on the Radarr v5 stable line diff --git a/catalog/sonarr.yaml b/catalog/sonarr.yaml index a164426..a91f2fb 100644 --- a/catalog/sonarr.yaml +++ b/catalog/sonarr.yaml @@ -107,6 +107,11 @@ auth: mode: forward-auth # bare UI, no usable OIDC: oauth2-proxy → Keycloak at the Gateway (docs/15; R-IDP-02) groups: - media-users # authenticated AND in the media group (R-IDP-06) + # See catalog/jackett.yaml for the full rationale: --allowed-group is repeatable and OR-ed, so + # platform-admins here is a superset, not a widening (that group already carries cluster-admin + # via k8s-oidc-rbac.yaml). Without it, an empty media-users locks every operator out with an + # immediate 403 at the oauth2-proxy callback — observed on prod 2026-09-12. + - platform-admins # operators of these services must not be locked out of them update: channel: stable # R-UPD-01 range: ">=4.0.0 <5.0.0" # stay on the Sonarr v4 stable line