feat(auth): gate jackett/qbittorrent/radarr/sonarr UIs with oauth2-proxy sidecar (rollout)

Roll the verified Option-B per-app oauth2-proxy sidecar out to the remaining
four forward-auth apps after the pihole canary was confirmed fail-closed
(unauthenticated -> 302 to Keycloak, pod 2/2, clean sidecar OIDC discovery).

Each app's Gateway route now terminates at its oauth2-proxy sidecar (:4180,
--allowed-group=media-users, --upstream=http://127.0.0.1:<uiport>); the
gateway-ingress CiliumNetworkPolicy allows the Gateway to reach ONLY :4180, so
the bare UI is never served un-authenticated (fail-closed; R-IDP-02/06). The
apps' own app/API ports stay in-Service for EAST-WEST callers (arr ->
qbittorrent:8080 / jackett:9117) via the existing reciprocal-ingress allows.

- keycloak: confidential jackett/qbittorrent/radarr/sonarr clients (realm-import
  + realm-clients mirror), provisioned live via kcadm (groups membership mapper).
- secrets: four SOPS/age <app>-oauth2-proxy-credentials, wired into apps/wave-0
  and apps/wave-1 by the renderer's sidecar-creds wave rule.
- ci: remove the temporary canary deferral (all five goldens now reproduce).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 4.8 committed 2026-08-31 08:45:46 -05:00
1 parent 72d4720d58
commit b2752aec48
13 files changed
+536 -18

No files matched your search

@@ -0,0 +1,30 @@
apiVersion: v1
kind: Secret
metadata:
name: jackett-oauth2-proxy
namespace: jackett
type: Opaque
stringData:
client-id: ENC[AES256_GCM,data:n5h6NG0emA==,iv:d6lvZfgRq6+IKNpsSoRa5a38R2f2+tWBO+j9QKDThMY=,tag:P+cvQXlWR4aZs7a9/lVuMQ==,type:str]
client-secret: ENC[AES256_GCM,data:WL5G7PuVTdHQoe7Zp2eiE9ppxSGE0+W2RMfISuwqD6JA9auG97ZYnPUjG8HNx7sosOvsmqeE9kONK23IqIeaQw==,iv:kSP10UKeDv/8BsgX36+uKwEY+LBovcicce1RkD14M4g=,tag:fEUsVuajWPxAmAo8Nnrrrg==,type:str]
cookie-secret: ENC[AES256_GCM,data:WChW9YLhzG9Nwp8dCWTFtirufXxGk3Ep9HXpfqcNz69PXQW3RaMC4EJpw2U=,iv:f6obIerkog8Q2gfj1tVL259LOl5jj59VE29KnJwmCXQ=,tag:s0FwNtU6vffKgDcGUNUhCg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age175l8mdpdfu7hq0aml3exs3gykarwjmuvr23sq56s3wjejnyk44wsp4mfzg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLWVptVHc4aFl0MXRWTHVP
cC9qcVoyZUV2TGZnTEpHY3RLOU8vQnNNNm00ClZRejE2bzFaWXNSUmQ5MEZtV3VK
djB2TXJqQjVUOE93QjN6U0lMVDBEbm8KLS0tIFFTZnB0c2pCZWZMZ084WEIxT2tW
WUxxemJHdWRZbnBpL280dzlpMmhwSW8Kra0bkx3iJYQi+3rnpU/89fJbJHzQjKi2
ZWNwYhLpULw1xNo3xbCm+PUp5f30kpyMKQld2qaLOZAqWI5vNl9Byw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-31T13:41:37Z"
mac: ENC[AES256_GCM,data:zU9nWCrSomAXAZ/Ve8U3yLHnoxhu8nkQ7523neJTGv810ONzAAZ7Gr8nFHR7bTqz2QwuGYD3P1ypdqYnKLM8ri2Zf1LDo/Su9ptnvQhUbfXKlRorcW6uAWsViCwvTM05wZNkdzZfza7tGwJQVTD6hc0cP+/7Y7JnpT5FEstK254=,iv:oggTRjcWqAJizA/a22+mtslkEWka8hUG0wudM8obO4g=,tag:i0TtNmqGe+b5t4iq8LpsqA==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.4
+69 -2
View File
@@ -84,6 +84,70 @@ spec:
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /data/config mountPath: /data/config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
- --reverse-proxy=true
- --provider=oidc
- --oidc-issuer-url=https://keycloak.home.keithjackson.dev/realms/homelab
- --upstream=http://127.0.0.1:9117
- --email-domain=*
- --scope=openid email profile
- --oidc-groups-claim=groups
- --allowed-group=media-users
- --skip-provider-button=true
- --redirect-url=https://jackett.home.keithjackson.dev/oauth2/callback
- --cookie-domain=.home.keithjackson.dev
- --whitelist-domain=.home.keithjackson.dev
- --cookie-secure=true
- --pass-access-token=true
- --set-xauthrequest=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: jackett-oauth2-proxy
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: jackett-oauth2-proxy
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: jackett-oauth2-proxy
key: cookie-secret
ports:
- name: http
containerPort: 4180
protocol: TCP
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
volumes: volumes:
- name: config - name: config
persistentVolumeClaim: persistentVolumeClaim:
@@ -104,6 +168,9 @@ spec:
- name: port-9117 - name: port-9117
port: 9117 port: 9117
targetPort: 9117 targetPort: 9117
- name: authproxy
port: 4180
targetPort: 4180
--- ---
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): jackett-allow-radarr-9117 # CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): jackett-allow-radarr-9117
apiVersion: cilium.io/v2 apiVersion: cilium.io/v2
@@ -166,7 +233,7 @@ spec:
- ingress - ingress
toPorts: toPorts:
- ports: - ports:
- port: '9117' - port: '4180'
protocol: TCP protocol: TCP
--- ---
# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): jackett-allow-internet-egress # CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): jackett-allow-internet-egress
@@ -214,7 +281,7 @@ spec:
value: / value: /
backendRefs: backendRefs:
- name: jackett - name: jackett
port: 9117 port: 4180
--- ---
# ServiceMonitor (R-OBS-01) # ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1 apiVersion: monitoring.coreos.com/v1
+2
View File
@@ -8,6 +8,7 @@ kind: Kustomization
resources: resources:
- gitea-bootstrap-secret.sops.yaml - gitea-bootstrap-secret.sops.yaml
- gitea.yaml - gitea.yaml
- jackett-oauth2-proxy-credentials.sops.yaml
- jackett.yaml - jackett.yaml
- nextcloud-db-app.sops.yaml - nextcloud-db-app.sops.yaml
- nextcloud-db-backup-credentials.sops.yaml - nextcloud-db-backup-credentials.sops.yaml
@@ -15,6 +16,7 @@ resources:
- pihole-oauth2-proxy-credentials.sops.yaml - pihole-oauth2-proxy-credentials.sops.yaml
- pihole.yaml - pihole.yaml
- plex.yaml - plex.yaml
- qbittorrent-oauth2-proxy-credentials.sops.yaml
- qbittorrent.yaml - qbittorrent.yaml
- semaphore-db-app.sops.yaml - semaphore-db-app.sops.yaml
- semaphore-db-backup-credentials.sops.yaml - semaphore-db-backup-credentials.sops.yaml
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Secret
metadata:
name: qbittorrent-oauth2-proxy
namespace: qbittorrent
type: Opaque
stringData:
client-id: ENC[AES256_GCM,data:ytfVRDabEfRM8NI=,iv:ERtFy5iss10id/cGLMZDmIAazkYtFG/spkxRYkoT2pw=,tag:FzOepKeYT05BBrz6RlnACQ==,type:str]
client-secret: ENC[AES256_GCM,data:klZzTdyAqQgG+1s074/0Bh5ybuHehiEo/RYceRGAGwuUrgCMb0AmYHCrE9gvtnPkSOBqWvFnPnorWkERc+V73g==,iv:nOUO/p+WnI5657I3Lm6zQAsumkscjF7J0fK0EVFHGo0=,tag:dF3AHYP4sxDChroRer9kMQ==,type:str]
cookie-secret: ENC[AES256_GCM,data:jVpsZjYHRrPvblfsb2f9OuneqoZ0cwcTLKrPb8XJ5oves1PmHtN5njrkk4o=,iv:bNjUK/ar4nniR7AjJ9fsGVkKd8nxwphkptmhFaYh9Ek=,tag:2axf+AshSdIvvqwyIfhq6g==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age175l8mdpdfu7hq0aml3exs3gykarwjmuvr23sq56s3wjejnyk44wsp4mfzg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAreWdPdUxiYXBxYWxZNlhy
WHFTZmo1RW1McXJJTjlWeTBCd2Y4WVU2TVRNCnExbEQrQXZCS2RRaGxJQlQ5eHpY
Z1UrZWlubUJpRC9YeEhJZW8zNVdLUVUKLS0tIGtmblFDRzk3ZC8wWVo2ZHJHWmEx
OW9DRUpaSlFrbHNDTGYvQUdyamh2bWsK5VvV4cl1iNXYECnSk3fi1ZuAPKGN+n8B
g51lT4gi03KA67ARzwVGVBfP5E1Cdmx4rJhi305tG+Ug99VyLBCUfQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-31T13:42:02Z"
mac: ENC[AES256_GCM,data:7GdlUKnZ/ST7EOhfPB6I2e2wyVHWmZ2G554yBcZQzqoZZWZ1hn+lkosNJxWD7E0hRId6P320EKj+Sf0g2ur3Mna5zii1GKFJHGtDEoccKvJQRx2JMkZlEC/x6xwc4BeJNtvidECVRvKC2WdrByRMr9rrI8NfXpmWH4e7iz1Xac0=,iv:UqsNOPRwzT+Dt043ldxIubkAAkPr0/nvjxlgVPiXIbw=,tag:7Wex5Rng3Nwtr8wxkFJD9Q==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.4
+69 -2
View File
@@ -84,6 +84,70 @@ spec:
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /data/config mountPath: /data/config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
- --reverse-proxy=true
- --provider=oidc
- --oidc-issuer-url=https://keycloak.home.keithjackson.dev/realms/homelab
- --upstream=http://127.0.0.1:8080
- --email-domain=*
- --scope=openid email profile
- --oidc-groups-claim=groups
- --allowed-group=media-users
- --skip-provider-button=true
- --redirect-url=https://qbittorrent.home.keithjackson.dev/oauth2/callback
- --cookie-domain=.home.keithjackson.dev
- --whitelist-domain=.home.keithjackson.dev
- --cookie-secure=true
- --pass-access-token=true
- --set-xauthrequest=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: qbittorrent-oauth2-proxy
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: qbittorrent-oauth2-proxy
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: qbittorrent-oauth2-proxy
key: cookie-secret
ports:
- name: http
containerPort: 4180
protocol: TCP
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
volumes: volumes:
- name: config - name: config
persistentVolumeClaim: persistentVolumeClaim:
@@ -104,6 +168,9 @@ spec:
- name: port-8080 - name: port-8080
port: 8080 port: 8080
targetPort: 8080 targetPort: 8080
- name: authproxy
port: 4180
targetPort: 4180
--- ---
# CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): qbittorrent-allow-radarr-8080 # CiliumNetworkPolicy reciprocal ingress allow (R-NET-02): qbittorrent-allow-radarr-8080
apiVersion: cilium.io/v2 apiVersion: cilium.io/v2
@@ -166,7 +233,7 @@ spec:
- ingress - ingress
toPorts: toPorts:
- ports: - ports:
- port: '8080' - port: '4180'
protocol: TCP protocol: TCP
--- ---
# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): qbittorrent-allow-internet-egress # CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): qbittorrent-allow-internet-egress
@@ -214,7 +281,7 @@ spec:
value: / value: /
backendRefs: backendRefs:
- name: qbittorrent - name: qbittorrent
port: 8080 port: 4180
--- ---
# ServiceMonitor (R-OBS-01) # ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1 apiVersion: monitoring.coreos.com/v1
+2
View File
@@ -10,7 +10,9 @@ resources:
- gitea-actions.yaml - gitea-actions.yaml
- nextcloud-bootstrap-secret.sops.yaml - nextcloud-bootstrap-secret.sops.yaml
- nextcloud.yaml - nextcloud.yaml
- radarr-oauth2-proxy-credentials.sops.yaml
- radarr.yaml - radarr.yaml
- semaphore-bootstrap-secret.sops.yaml - semaphore-bootstrap-secret.sops.yaml
- semaphore.yaml - semaphore.yaml
- sonarr-oauth2-proxy-credentials.sops.yaml
- sonarr.yaml - sonarr.yaml
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Secret
metadata:
name: radarr-oauth2-proxy
namespace: radarr
type: Opaque
stringData:
client-id: ENC[AES256_GCM,data:vFLxqzxt,iv:6k0ZjKP8rvg56ZFPWEGdyj+FLgK+LBOE0pLBWfC0bjA=,tag:LiZb5BasJAEWM1c3I8vFTA==,type:str]
client-secret: ENC[AES256_GCM,data:JzfHF8JF4YUxBxiC738N4UsMlcCOvxn4/AE2B4meZNGAZb2uhG4n51uEY8OQsbsM0Ni4qM2Wi17KuLDwAicfUA==,iv:A3AnUhYxw/IWfvfcZf6MgC5u37dMoNToIWxRG9AxEJQ=,tag:1OY2pWGTbCxPYYdaiBa/oA==,type:str]
cookie-secret: ENC[AES256_GCM,data:LPt5nghLfoNvr7yBes8rLeEK3M6UVIO1h9POHMOAj8iZX3l2RJJa4w8GcSg=,iv:Q5+7zH8Tb1SWe92B0pxekfMgOXlBG8nixWn/ho4oJw4=,tag:D+41dBFPVTGj86xv5/GEng==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age175l8mdpdfu7hq0aml3exs3gykarwjmuvr23sq56s3wjejnyk44wsp4mfzg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBHVG5MWVRyOVl1RWFpOExC
SjZKcEd1ZlFnZVZ5NmtyUlQzRGI1b1o3dG04CkwrTUNjd3crM1VzaUdPczliSDY1
a3dONnBFWk8yOW5kL2xES1RZbUN6L3cKLS0tIG9hWG9Cbm01RlMrcC9zMWkxeFgr
ZnRGRUVTTS81SFRyenNsZUZEcG1TUm8K5XUGP/1NVsoaFcBZUER3/zMPTtCuVisT
LrbSmTkIyVfzjN/vRpRH92b1VzENJvhGhRghhQaKY0rbG34i6yh3Vw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-31T13:42:25Z"
mac: ENC[AES256_GCM,data:TxdPz2zE6+ypgQG2b0Rp3VFiPL7LLls4NNkC7YQ/bVCLvyvaXdGsEWkiHJE+Po7hcESmZRt/NMS25mEXPMfvlSCltwFUTcrxoZDS0NI27kIzbtceTbxmznt/dMS/iZm21lCgA6Aag9ByLjWusPLkRKWhep+G+0G54qtfQzv3s80=,iv:44f0KikrpKpnSaLLyzTvnCfrwEXylRgVIlKMtSpMyNc=,tag:jDx43n0usDtbwzo8KzDBqw==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.4
+69 -2
View File
@@ -84,6 +84,70 @@ spec:
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /data/config mountPath: /data/config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
- --reverse-proxy=true
- --provider=oidc
- --oidc-issuer-url=https://keycloak.home.keithjackson.dev/realms/homelab
- --upstream=http://127.0.0.1:7878
- --email-domain=*
- --scope=openid email profile
- --oidc-groups-claim=groups
- --allowed-group=media-users
- --skip-provider-button=true
- --redirect-url=https://radarr.home.keithjackson.dev/oauth2/callback
- --cookie-domain=.home.keithjackson.dev
- --whitelist-domain=.home.keithjackson.dev
- --cookie-secure=true
- --pass-access-token=true
- --set-xauthrequest=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: radarr-oauth2-proxy
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: radarr-oauth2-proxy
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: radarr-oauth2-proxy
key: cookie-secret
ports:
- name: http
containerPort: 4180
protocol: TCP
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
volumes: volumes:
- name: config - name: config
persistentVolumeClaim: persistentVolumeClaim:
@@ -104,6 +168,9 @@ spec:
- name: port-7878 - name: port-7878
port: 7878 port: 7878
targetPort: 7878 targetPort: 7878
- name: authproxy
port: 4180
targetPort: 4180
--- ---
# CiliumNetworkPolicy egress allow (R-NET-02): radarr-allow-qbittorrent-8080 # CiliumNetworkPolicy egress allow (R-NET-02): radarr-allow-qbittorrent-8080
apiVersion: cilium.io/v2 apiVersion: cilium.io/v2
@@ -188,7 +255,7 @@ spec:
- ingress - ingress
toPorts: toPorts:
- ports: - ports:
- port: '7878' - port: '4180'
protocol: TCP protocol: TCP
--- ---
# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): radarr-allow-internet-egress # CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): radarr-allow-internet-egress
@@ -236,7 +303,7 @@ spec:
value: / value: /
backendRefs: backendRefs:
- name: radarr - name: radarr
port: 7878 port: 4180
--- ---
# ServiceMonitor (R-OBS-01) # ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1 apiVersion: monitoring.coreos.com/v1
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Secret
metadata:
name: sonarr-oauth2-proxy
namespace: sonarr
type: Opaque
stringData:
client-id: ENC[AES256_GCM,data:JMYoJymH,iv:CM9HF71QZCeETzrOtkeTUs+BLTp/gGSExulXUP8yS/M=,tag:l73RaFQolO7+wZBo8nYCvw==,type:str]
client-secret: ENC[AES256_GCM,data:Fq271KdbtKWKWOe1Sun6+SLv/kSBsvAeFUhZIq6J2DNPGM4E8dgc2YANITcpezcJR07PAbSlUcS0L/qF6K+YhQ==,iv:/mVD1uOkweyO26RMV3qRQmLdyxXJR4I7p+OjYiiuSMU=,tag:9ZOKye5Sp2NHU2RXH1Y+wQ==,type:str]
cookie-secret: ENC[AES256_GCM,data:jMZZEi8fdh20BbPzCoVfwdS1g0/ma5n/QISU5A/gQCu/2Rkylt7mp798CUg=,iv:6IPqDNQyJHze2DUOQ93IwruWSge5nXjkVcQIOMfJSwY=,tag:cOKRcDKywV3PBOBrhoddRQ==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age175l8mdpdfu7hq0aml3exs3gykarwjmuvr23sq56s3wjejnyk44wsp4mfzg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoNU5NbnJnV0dKRnpIN2dw
QmhBRWI4aEpWajRHVkxZcGxCRk1hMDBjSlRVCm91b0U2N08vNi8rSEFaVlRJazFL
bllPcm9EMUgrQ3ZlUDlrdEZpZE8zNmMKLS0tIDU0ajdycXZrazlQb3RVRGQyY25h
R3RDajltcmFvQWdLYWQ2Mjh0VkplaWsKJprnLNJYzMd0yHbxjsD8P66Wu+sefmaV
nduEgVQY0t4EsYN6iKVDLMUAp+o3gXU8QuMuBnFZznrmBghfG+ZruQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-31T13:42:49Z"
mac: ENC[AES256_GCM,data:ckVR9OyFvle0fLhBjHtJ6hH2St2CRJeu6abTCnqtxAfMDVXdbIkgHEesmN2kAgA7s3LkKJC2m7EFIpt3pO94VQNnZvC1KKD7qUSIfJN1DLVv78m8JbEQfVTJk/WgWy1ObxLriw5hDuMiEuOvKND+s6eqS5j6er4Q72lBsBpoEeY=,iv:uguThOQbR9Ks1qs1U0HO7EhUj1BT60fvM+3xVEC9hUU=,tag:m4P5nhlspCZyJRuOqffMog==,type:str]
pgp: []
encrypted_regex: ^(data|stringData)$
version: 3.9.4
+69 -2
View File
@@ -84,6 +84,70 @@ spec:
volumeMounts: volumeMounts:
- name: config - name: config
mountPath: /data/config mountPath: /data/config
- name: oauth2-proxy
image: quay.io/oauth2-proxy/oauth2-proxy:v7.8.1
imagePullPolicy: IfNotPresent
args:
- --http-address=0.0.0.0:4180
- --reverse-proxy=true
- --provider=oidc
- --oidc-issuer-url=https://keycloak.home.keithjackson.dev/realms/homelab
- --upstream=http://127.0.0.1:8989
- --email-domain=*
- --scope=openid email profile
- --oidc-groups-claim=groups
- --allowed-group=media-users
- --skip-provider-button=true
- --redirect-url=https://sonarr.home.keithjackson.dev/oauth2/callback
- --cookie-domain=.home.keithjackson.dev
- --whitelist-domain=.home.keithjackson.dev
- --cookie-secure=true
- --pass-access-token=true
- --set-xauthrequest=true
env:
- name: OAUTH2_PROXY_CLIENT_ID
valueFrom:
secretKeyRef:
name: sonarr-oauth2-proxy
key: client-id
- name: OAUTH2_PROXY_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: sonarr-oauth2-proxy
key: client-secret
- name: OAUTH2_PROXY_COOKIE_SECRET
valueFrom:
secretKeyRef:
name: sonarr-oauth2-proxy
key: cookie-secret
ports:
- name: http
containerPort: 4180
protocol: TCP
resources:
requests:
cpu: 25m
memory: 64Mi
limits:
memory: 128Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
livenessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
readinessProbe:
httpGet:
path: /ping
port: http
initialDelaySeconds: 5
periodSeconds: 15
volumes: volumes:
- name: config - name: config
persistentVolumeClaim: persistentVolumeClaim:
@@ -104,6 +168,9 @@ spec:
- name: port-8989 - name: port-8989
port: 8989 port: 8989
targetPort: 8989 targetPort: 8989
- name: authproxy
port: 4180
targetPort: 4180
--- ---
# CiliumNetworkPolicy egress allow (R-NET-02): sonarr-allow-qbittorrent-8080 # CiliumNetworkPolicy egress allow (R-NET-02): sonarr-allow-qbittorrent-8080
apiVersion: cilium.io/v2 apiVersion: cilium.io/v2
@@ -188,7 +255,7 @@ spec:
- ingress - ingress
toPorts: toPorts:
- ports: - ports:
- port: '8989' - port: '4180'
protocol: TCP protocol: TCP
--- ---
# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): sonarr-allow-internet-egress # CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): sonarr-allow-internet-egress
@@ -236,7 +303,7 @@ spec:
value: / value: /
backendRefs: backendRefs:
- name: sonarr - name: sonarr
port: 8989 port: 4180
--- ---
# ServiceMonitor (R-OBS-01) # ServiceMonitor (R-OBS-01)
apiVersion: monitoring.coreos.com/v1 apiVersion: monitoring.coreos.com/v1
+1 -8
View File
@@ -40,14 +40,7 @@ PYTHON="${PYTHON:-python3}"
# by-product of `--all catalog` but must NOT have a golden; the gate ignores # by-product of `--all catalog` but must NOT have a golden; the gate ignores
# their rendered output. Onboarding one = add its golden and drop it here. # their rendered output. Onboarding one = add its golden and drop it here.
# #
# CANARY DEFERRAL (forward-auth oauth2-proxy sidecar rollout, 2026-08-31): the renderer now : "${RENDER_GOLDEN_NOT_ONBOARDED:=keycloak vm-demo}"
# emits a per-app oauth2-proxy sidecar for every forward-auth app (pihole/jackett/qbittorrent/
# radarr/sonarr). pihole is the CANARY — its golden carries the sidecar and is verified fail-closed
# in-cluster FIRST. The other four are deferred to the ROLLOUT commit so partial progress is safe
# (their committed goldens stay sidecar-less and keep serving exactly as today). They are listed
# here TEMPORARILY so the canonical render's fresh (sidecar) output does not flag drift against the
# deferred sidecar-less goldens. The ROLLOUT commit re-renders their goldens and REMOVES them here.
: "${RENDER_GOLDEN_NOT_ONBOARDED:=keycloak vm-demo jackett qbittorrent radarr sonarr}"
log "Stage: render==golden reproducibility (canonical render vs committed apps/)" log "Stage: render==golden reproducibility (canonical render vs committed apps/)"
@@ -13,6 +13,10 @@ stringData:
headlamp-client-secret: ENC[AES256_GCM,data:hlQ5m6FeqwOqX9HTT8jC6NZmhQH0kMgUveMZ2Cfen1zLLxWfYXXC1QMBWSqrchY2EpyIr7vNPbFjbqffwiSDHQ==,iv:WS1F3gkWn18STl4c38AlCe7TU6jvZpl/eULf2TnjIZ4=,tag:n2BnQYrm4gZbLeemDBykjA==,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] 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] pihole-client-secret: ENC[AES256_GCM,data:lxheNbIev1Y6O0b3LJWvyROizOU2ts3WwZTa32x+efJwEek3dTBBc/3Nn0m5GKVNXv9lqkio9+qPTlZTmKZbLw==,iv:mfoIGDpGxc+f445Ev8dJY1YMe6WmOurMCDwdz80Cjbk=,tag:FPA2Ln97wG89NnLWdvnP+Q==,type:str]
jackett-client-secret: ENC[AES256_GCM,data:YIXxL5x0VstT4iTW2G/rjdF9jh14log6GPojbs1RyZYnPBs5zDgwU9kCHD8EUGtcbXbz4hZall/imt9zU080JA==,iv:Y3DwFxrWv8gTW3kK9Gq6Un+WbG2VJs28yr6/hUIyenc=,tag:D7Q2wKkx8rTmpGbvKEoM0g==,type:str]
qbittorrent-client-secret: ENC[AES256_GCM,data:jXNg4mF9Li0ilsV53iYeVJCGavVGFjvLk9jKBrRgcH6id1QCzsjs0bbMqJO5nALWfKhD8+CEmYJRrfABYTFKZA==,iv:XbLkX7/tfB/DLeMriHA3Oo2M6hgF1Z6RbHBXmdwUJoA=,tag:gikgdU0ajNKZxxYEUGc1aw==,type:str]
radarr-client-secret: ENC[AES256_GCM,data:acsBXdewKB/gJNyoo1CsPLCvZe6fuJWbrKLim1/57OPnDHNoA+OvkQqt12Y4VMAXjzl7BWD2afFTIkIcaNGgaQ==,iv:VSvMaDZeuFyIAWpUoa++khvx07hY6g7X8PxORtjFTVI=,tag:jY5QSw6cMvUhCfX32RNgXA==,type:str]
sonarr-client-secret: ENC[AES256_GCM,data:6cCVadKAKh5YbZIPocXG8pNY/AZbo3zCUP/19a0aHK0fIx6QzvBBl7v2vTsY/tq5tiVx8Uzr6oQNo/xt6pIgIA==,iv:EBXBZgpgdt7GiIS0w/Uj/vNE9EBgSgrbaMOKP346GKA=,tag:I2skAB+fdfQTsMgcywOrAg==,type:str]
sops: sops:
kms: [] kms: []
gcp_kms: [] gcp_kms: []
@@ -28,8 +32,8 @@ sops:
YXJGUUJiTnFndFhDZDU1bmxXTENCbmsKuhb3RQ3LZmuTPAXEck8b6iuZ5vxLb9fV YXJGUUJiTnFndFhDZDU1bmxXTENCbmsKuhb3RQ3LZmuTPAXEck8b6iuZ5vxLb9fV
+fQELmU4kOTbZrPSxy+kpttLG5Cy5d6X2ieZXBf6opzpLTUhlBOkHA== +fQELmU4kOTbZrPSxy+kpttLG5Cy5d6X2ieZXBf6opzpLTUhlBOkHA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-08-31T13:33:15Z" lastmodified: "2026-08-31T13:42:49Z"
mac: ENC[AES256_GCM,data:5wUiBt5+PxzAdGaKrf6wwx0TBjOC2doukzJSleRpYtN/Tf+ubMHNDa6C1sH/RFd83haPrws1ZoAedmyiBSwqwLhmslwil5ZHzbyaBs8fVrp/EcKgNmaUKfjv8AOj4q5MFWIb8eeKGCpt1GVGHlwg4ewfwRn+2mgPev1LKsNsaYc=,iv:X2APh51JZ16O17bz+cNLgXGdORaeYsdXvznbnVt8mwU=,tag:99ekaLyqhL4YPT34uo9xCA==,type:str] mac: ENC[AES256_GCM,data:/sOLb/XYQ2WB+mPgnTfYXD3y7xwDi926v0e2upBqgcCugtfKHg+Q1C9+XUiG2qE/iIBJyYVtOu9pmiMMNyT9hJ9vm7WFOC6gqukFMo42PjAI4I4PExfjVMKt76kKJ4OZZrKF2IKLjccZFuKDQhfADIR3zl6UNf/x5wz7XpXPEvI=,iv:iV4eOcRxsgTm1uNVMkMwsspj9glKCSZQlBciRY+MDJY=,tag:c72Pks1VJH+gmfP5FOQPSw==,type:str]
pgp: [] pgp: []
encrypted_regex: ^(data|stringData)$ encrypted_regex: ^(data|stringData)$
version: 3.9.4 version: 3.9.4
@@ -47,6 +47,22 @@ spec:
secret: secret:
name: keycloak-realm-clients name: keycloak-realm-clients
key: pihole-client-secret key: pihole-client-secret
JACKETT_CLIENT_SECRET:
secret:
name: keycloak-realm-clients
key: jackett-client-secret
QBITTORRENT_CLIENT_SECRET:
secret:
name: keycloak-realm-clients
key: qbittorrent-client-secret
RADARR_CLIENT_SECRET:
secret:
name: keycloak-realm-clients
key: radarr-client-secret
SONARR_CLIENT_SECRET:
secret:
name: keycloak-realm-clients
key: sonarr-client-secret
realm: realm:
realm: homelab realm: homelab
enabled: true enabled: true
@@ -399,6 +415,119 @@ spec:
"access.token.claim": "true" "access.token.claim": "true"
"userinfo.token.claim": "true" "userinfo.token.claim": "true"
"claim.name": groups "claim.name": groups
# jackett/qbittorrent/radarr/sonarr — media stack UIs (catalog/*.yaml auth.mode forward-auth,
# auth.groups media-users). Each has a bare UI with no usable native OIDC, gated by a per-app
# oauth2-proxy REVERSE-PROXY sidecar (Option B, rendered by catalog/render.py) — these are those
# sidecars' confidential auth-code clients, modeled on the crafty/pihole clients. Redirect URI is
# the oauth2-proxy callback on each app's host; the `groups` mapper (bare names) lets the sidecar
# enforce --allowed-group=media-users (R-IDP-06). Secrets injected from the SOPS keycloak-realm-
# clients Secret via the *_CLIENT_SECRET placeholders (R-IDP-04); the SAME values are mirrored in
# apps/wave-*/<app>-oauth2-proxy-credentials.sops.yaml. Provisioned live via kcadm 2026-08-31 and
# captured here as the durable GitOps source of truth (operator imports once; kcadm authoritative).
- clientId: jackett
name: Jackett (oauth2-proxy)
protocol: openid-connect
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
secret: "$(env:JACKETT_CLIENT_SECRET)"
redirectUris:
- https://jackett.home.keithjackson.dev/oauth2/callback
webOrigins:
- https://jackett.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
- clientId: qbittorrent
name: qBittorrent (oauth2-proxy)
protocol: openid-connect
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
secret: "$(env:QBITTORRENT_CLIENT_SECRET)"
redirectUris:
- https://qbittorrent.home.keithjackson.dev/oauth2/callback
webOrigins:
- https://qbittorrent.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
- clientId: radarr
name: Radarr (oauth2-proxy)
protocol: openid-connect
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
secret: "$(env:RADARR_CLIENT_SECRET)"
redirectUris:
- https://radarr.home.keithjackson.dev/oauth2/callback
webOrigins:
- https://radarr.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
- clientId: sonarr
name: Sonarr (oauth2-proxy)
protocol: openid-connect
enabled: true
publicClient: false
standardFlowEnabled: true
directAccessGrantsEnabled: false
secret: "$(env:SONARR_CLIENT_SECRET)"
redirectUris:
- https://sonarr.home.keithjackson.dev/oauth2/callback
webOrigins:
- https://sonarr.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 # --- 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. # 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 # Set as the realm browserFlow below. VERIFY at bring-up (see runbook): a malformed flow fails