From 438fcbe9feac176d1e07a25fedf0da491e102aa4 Mon Sep 17 00:00:00 2001 From: rock Date: Tue, 27 Oct 2026 14:17:21 -0700 Subject: [PATCH] feat(comfyui): put ComfyUI behind ZITADEL single sign-on comfyui.riotpiao.com is served with no authentication at all: anyone who can resolve the host reaches the UI and its queue. ComfyUI has no OIDC support, so the front door is oauth2-proxy, and nginx asks it for a decision on every request. Nothing inside the cluster calls this host, so the whole host is gated rather than carved into open and closed paths. If a client ever needs direct access, add a skip rule for its exact paths. The ZITADEL app is created by the provisioning script, and the client secret, cookie secret and client id live in a SOPS secret written by ksops. --- k8s/apps/comfyui/ingress.yaml | 5 + k8s/apps/comfyui/kustomization.yaml | 1 + k8s/apps/comfyui/oauth2-proxy.yaml | 101 ++++++++++++++++++ .../secrets/comfyui-oauth2-proxy.enc.yaml | 30 ++++++ k8s/argocd/secrets/secret-generator.yaml | 1 + scripts/iam/zitadel-provision.py | 5 + 6 files changed, 143 insertions(+) create mode 100644 k8s/apps/comfyui/oauth2-proxy.yaml create mode 100644 k8s/argocd/secrets/comfyui-oauth2-proxy.enc.yaml diff --git a/k8s/apps/comfyui/ingress.yaml b/k8s/apps/comfyui/ingress.yaml index 21ad2d4..73b7939 100644 --- a/k8s/apps/comfyui/ingress.yaml +++ b/k8s/apps/comfyui/ingress.yaml @@ -13,6 +13,11 @@ metadata: # WebSocket support for ComfyUI's live preview nginx.ingress.kubernetes.io/proxy-http-version: "1.1" nginx.ingress.kubernetes.io/upstream-hash-by: "$remote_addr" + # Single sign-on. ComfyUI has no auth of its own, so every request is handed to + # oauth2-proxy for an allow or deny decision. + nginx.ingress.kubernetes.io/auth-url: "http://comfyui-oauth2-proxy.comfyui.svc.cluster.local:4180/oauth2/auth" + nginx.ingress.kubernetes.io/auth-signin: "https://comfyui.riotpiao.com/oauth2/start?rd=$scheme://$host$request_uri" + nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user,x-auth-request-email" nginx.ingress.kubernetes.io/configuration-snippet: | proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; diff --git a/k8s/apps/comfyui/kustomization.yaml b/k8s/apps/comfyui/kustomization.yaml index 1d4f62e..8ebab14 100644 --- a/k8s/apps/comfyui/kustomization.yaml +++ b/k8s/apps/comfyui/kustomization.yaml @@ -4,4 +4,5 @@ kind: Kustomization resources: - deployment.yaml - service.yaml + - oauth2-proxy.yaml - ingress.yaml diff --git a/k8s/apps/comfyui/oauth2-proxy.yaml b/k8s/apps/comfyui/oauth2-proxy.yaml new file mode 100644 index 0000000..02c8e79 --- /dev/null +++ b/k8s/apps/comfyui/oauth2-proxy.yaml @@ -0,0 +1,101 @@ +# ComfyUI has no authentication of its own: comfyui.riotpiao.com is open to anyone +# who can resolve the host. It also has no OIDC support, so ZITADEL cannot be a +# login method inside it. oauth2-proxy is the front door instead: nginx asks it for +# a decision on every request and it answers from a ZITADEL session. +# +# No API client calls this host today, so the whole host is gated. If a client ever +# needs direct access, add a skip rule for the exact paths it uses rather than +# opening the whole host. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: comfyui-oauth2-proxy + namespace: comfyui + labels: + app: comfyui-oauth2-proxy +spec: + replicas: 1 + selector: + matchLabels: + app: comfyui-oauth2-proxy + template: + metadata: + labels: + app: comfyui-oauth2-proxy + spec: + securityContext: + runAsNonRoot: true + runAsUser: 65534 + seccompProfile: {type: RuntimeDefault} + containers: + - name: oauth2-proxy + image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1 + args: + - --provider=oidc + - --oidc-issuer-url=https://zitadel.riotpiao.com + - --email-domain=* + - --upstream=static://202 + - --reverse-proxy=true + - --set-xauthrequest=true + - --cookie-secure=true + - --cookie-expire=12h + - --cookie-refresh=1h + - --skip-provider-button=true + - --redirect-url=https://comfyui.riotpiao.com/oauth2/callback + - --http-address=0.0.0.0:4180 + env: + - name: OAUTH2_PROXY_CLIENT_ID + valueFrom: {secretKeyRef: {name: comfyui-oauth2-proxy, key: client-id}} + - name: OAUTH2_PROXY_CLIENT_SECRET + valueFrom: {secretKeyRef: {name: comfyui-oauth2-proxy, key: client-secret}} + - name: OAUTH2_PROXY_COOKIE_SECRET + valueFrom: {secretKeyRef: {name: comfyui-oauth2-proxy, key: cookie-secret}} + ports: + - {name: http, containerPort: 4180} + readinessProbe: + httpGet: {path: /ping, port: http} + initialDelaySeconds: 5 + livenessProbe: + httpGet: {path: /ping, port: http} + initialDelaySeconds: 15 + securityContext: + allowPrivilegeEscalation: false + capabilities: {drop: ["ALL"]} + resources: + requests: {cpu: 10m, memory: 32Mi} + limits: {cpu: 200m, memory: 128Mi} +--- +apiVersion: v1 +kind: Service +metadata: + name: comfyui-oauth2-proxy + namespace: comfyui + labels: + app: comfyui-oauth2-proxy +spec: + type: ClusterIP + selector: + app: comfyui-oauth2-proxy + ports: + - {name: http, port: 4180, targetPort: http} +--- +# The proxy needs its own path. The auth_request annotation on the main ingress would +# otherwise also guard /oauth2/start, and the login entry point would demand a login. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: comfyui-oauth2 + namespace: comfyui +spec: + ingressClassName: nginx + rules: + - host: comfyui.riotpiao.com + http: + paths: + - path: /oauth2 + pathType: Prefix + backend: + service: + name: comfyui-oauth2-proxy + port: + number: 4180 diff --git a/k8s/argocd/secrets/comfyui-oauth2-proxy.enc.yaml b/k8s/argocd/secrets/comfyui-oauth2-proxy.enc.yaml new file mode 100644 index 0000000..a4fc165 --- /dev/null +++ b/k8s/argocd/secrets/comfyui-oauth2-proxy.enc.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Secret +metadata: + name: comfyui-oauth2-proxy + namespace: comfyui +type: Opaque +data: + client-id: ENC[AES256_GCM,data:jFLgcrh1tiAU0pijGxy+/mFsFHzI5zSi,iv:7GYL4xtPnzyZWtEA0egPHhzvpXGevkIH3Cvr+oD9hKI=,tag:UeDTIKlfcYnOOOJiPNgN0g==,type:str] + client-secret: ENC[AES256_GCM,data:9QFu5a2EkEMjc/B0moPIkMEChrN0Qh5BQtAzFcvW4liKxzVAPbTKgQOJeND+NQjn5ZoE89kqqRyqNJF3bT3H1pWoDFngk9J2X7BN/9r6Qyubias0Jukydw==,iv:xf2kT0fbnJat3L3xxUYm3ht0YvHptU/RIdt9Eh0D6xU=,tag:8iknaQ2kKP1OZ/1jRCa+1g==,type:str] + cookie-secret: ENC[AES256_GCM,data:Qq+l4go/CFtfsZrgYUcTj9bB4sreu2GEqJnu15H6fxIlD7IgnmrodPoIVNpWcdm6LSYabGjm2jraN6RP,iv:EOoDHgvIydzHCC5POohjPq3Vj2YTpzhIhvdG2FzKKzc=,tag:iDXqzFU1Z79bzpG4I4xfww==,type:str] +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxOUtCUDduMVU5U1J4WXZX + Q1VzaFRVdXlHaEZpeVJNOWprdDhUOEd6Z1hzCi9QZEpXN3NDSENzUE9RTGdpb3Va + eVNHVTFLbXpMU0x5UWlFLzZnVlllWE0KLS0tIDZldVNqR1loNHlXZWovNWZ4WjFD + dVErenJDM3haSStxZEVWZUtBbDBIZ1UK9x6Gy4drjfUz2goAaI+oeUDWdNHQFAJl + JGI+X81r/6Krg5N8QDa/mE96E2M2G0yAO5FS1YbpCKOITUEyheacnQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-10-27T21:17:08Z" + mac: ENC[AES256_GCM,data:tTGmWFidEWTWHgKPV7b5ABOJwwlP1yQDK5Jd60c9ko6IsmP2GBBPCth7kJgPXuPcw+NHbJ7yt7bcVto7GkPdtY9daJmjPq0Gt16ZO4Xi4hCk22JVKqi/KJcKD/KB0r/VO9w093/Who5CcYgqifextfca4CuxS5m5dcOn4kRJCfE=,iv:0fDvvQr9wp8IrD16u88Ml4HsPEeMhd8R3ljQJoIpw3E=,tag:AanM7C6vbaBBmus1u1GRoA==,type:str] + pgp: [] + encrypted_regex: ^(data|stringData)$ + version: 3.9.3 diff --git a/k8s/argocd/secrets/secret-generator.yaml b/k8s/argocd/secrets/secret-generator.yaml index ad7e9c8..346c8f1 100644 --- a/k8s/argocd/secrets/secret-generator.yaml +++ b/k8s/argocd/secrets/secret-generator.yaml @@ -15,6 +15,7 @@ files: - forgejo-registry-pull.enc.yaml - forgejo-runner-token.enc.yaml - forgejo-secrets.enc.yaml + - comfyui-oauth2-proxy.enc.yaml - grafana-oidc-secrets.enc.yaml - grafana-secrets.enc.yaml - homelab-ca-secrets.enc.yaml diff --git a/scripts/iam/zitadel-provision.py b/scripts/iam/zitadel-provision.py index b6dcf68..6ca8b84 100755 --- a/scripts/iam/zitadel-provision.py +++ b/scripts/iam/zitadel-provision.py @@ -122,6 +122,11 @@ OIDC_APPS = { "https://forgejo.riotpiao.com/user/oauth2/zitadel/callback", "http://localhost:3000/user/oauth2/authentik/callback", "https://forgejo.riotpiao.com/user/oauth2/authentik/callback"]}, + # ComfyUI has no native OIDC. oauth2-proxy is its front door and this app is + # the proxy's client, so the callback path belongs to the proxy. + "comfyui": {**_WEB, "redirect_uris": [ + "http://localhost:4180/oauth2/callback", + "https://comfyui.riotpiao.com/oauth2/callback"]}, "grafana": {**_WEB, "redirect_uris": [ "http://localhost:3000/login/generic_oauth", "https://grafana.riotpiao.com/login/generic_oauth"]}, -- 2.54.0