From 990e3128253fe110748fcd9ae4af24f8039f7e42 Mon Sep 17 00:00:00 2001 From: rock Date: Sun, 25 Oct 2026 21:08:23 -0700 Subject: [PATCH] feat(iam): wire all 9 consumer manifests to Zitadel OIDC - Replace Authentik issuer/auth/token/userinfo URLs with Zitadel endpoints in grafana, minio, paperless-ai, paperless-ai-new, homarr, argocd, blackbox - Write Zitadel client IDs/secrets into plain manifests and SOPS secrets (grafana, minio, paperless, homarr) - Adopt homarr-oidc into SOPS (homarr-auth-oidc.enc.yaml) - Add homarr to SECRET_MAP and remove from MANUAL_ONLY in zitadel-provision.py - Make client-id regex idempotent for repeated runs (minio, paperless) - Fix cloudflared init container CA mount path (homelab-ca.crt -> ca.crt) - Remove Authentik ingress and cloudflared tunnel entry --- k8s/apps/cloudflared/configmap.yaml | 2 - k8s/apps/cloudflared/deployment.yaml | 2 +- k8s/apps/homarr/homarr-values.yaml | 6 +- k8s/apps/paperless/paperless-ai-new.yaml | 4 +- k8s/apps/paperless/paperless-ai.yaml | 4 +- .../secrets/grafana-oidc-secrets.enc.yaml | 6 +- k8s/argocd/secrets/homarr-auth-oidc.enc.yaml | 30 +++---- k8s/argocd/secrets/minio-secrets.enc.yaml | 86 +++++++++---------- .../secrets/paperless-ai-secrets.enc.yaml | 6 +- k8s/bootstrap/ingress/ingress.yaml | 24 ------ .../phase4-argocd/argocd-values.yaml | 12 +-- k8s/infra/logging/grafana-values.yaml | 24 +++--- k8s/infra/minio/minio-tenant.yaml | 12 +-- .../monitoring/blackbox-exporter-values.yaml | 4 +- scripts/iam/zitadel-provision.py | 20 ++--- 15 files changed, 108 insertions(+), 134 deletions(-) diff --git a/k8s/apps/cloudflared/configmap.yaml b/k8s/apps/cloudflared/configmap.yaml index 48bdccb..656469c 100644 --- a/k8s/apps/cloudflared/configmap.yaml +++ b/k8s/apps/cloudflared/configmap.yaml @@ -33,8 +33,6 @@ data: ingress: - hostname: paperless.riotpiao.com service: https://paperless.riotpiao.com - - hostname: authentik.riotpiao.com - service: https://authentik.riotpiao.com - hostname: img.riotpiao.com service: https://img.riotpiao.com - hostname: riotpiao.com diff --git a/k8s/apps/cloudflared/deployment.yaml b/k8s/apps/cloudflared/deployment.yaml index 8087b62..aa6f459 100644 --- a/k8s/apps/cloudflared/deployment.yaml +++ b/k8s/apps/cloudflared/deployment.yaml @@ -26,7 +26,7 @@ spec: command: - sh - -c - - (cat /etc/ssl/certs/ca-certificates.crt 2>/dev/null; cat /homelab-ca/homelab-ca.crt) > /merged/ca-bundle.crt + - (cat /etc/ssl/certs/ca-certificates.crt 2>/dev/null; cat /homelab-ca/ca.crt) > /merged/ca-bundle.crt volumeMounts: - name: homelab-ca mountPath: /homelab-ca diff --git a/k8s/apps/homarr/homarr-values.yaml b/k8s/apps/homarr/homarr-values.yaml index f85d126..434fe83 100644 --- a/k8s/apps/homarr/homarr-values.yaml +++ b/k8s/apps/homarr/homarr-values.yaml @@ -29,12 +29,12 @@ replicaCount: 1 # Chart supports these via top-level env dict (not array) env: AUTH_PROVIDERS: "oidc,credentials" - AUTH_OIDC_ISSUER: "https://authentik.riotpiao.com/application/o/homarr/" + AUTH_OIDC_ISSUER: "https://zitadel.riotpiao.com" # AUTH_OIDC_URI (authorize endpoint) is REQUIRED in addition to ISSUER — homarr # hides the "Sign in with Authentik" button entirely when it's absent (per the # authentik Homarr integration + homarr SSO docs). This was the missing var. - AUTH_OIDC_URI: "https://authentik.riotpiao.com/application/o/authorize/" - AUTH_OIDC_CLIENT_NAME: "Authentik" + AUTH_OIDC_URI: "https://zitadel.riotpiao.com/oauth/v2/authorize" + AUTH_OIDC_CLIENT_NAME: "Zitadel" AUTH_OIDC_GROUPS_ATTRIBUTE: "groups" AUTH_OIDC_SCOPE_OVERWRITE: "openid email profile groups" AUTH_OIDC_AUTO_LOGIN: "false" diff --git a/k8s/apps/paperless/paperless-ai-new.yaml b/k8s/apps/paperless/paperless-ai-new.yaml index 041d2f2..f1ac7a5 100644 --- a/k8s/apps/paperless/paperless-ai-new.yaml +++ b/k8s/apps/paperless/paperless-ai-new.yaml @@ -49,9 +49,9 @@ spec: echo "[init] Fetching JWT token from Authentik for paperless-ai-agent..." # Get JWT token via client_credentials grant - TOKEN_RESPONSE=$(curl -s -X POST https://authentik.riotpiao.com/application/o/token/ \ + TOKEN_RESPONSE=$(curl -s -X POST https://zitadel.riotpiao.com/oauth/v2/token \ -d "grant_type=client_credentials" \ - -d "client_id=paperless-ai-agent" \ + -d "client_id=396757803559027071" \ -d "client_secret=29skAhE0tBzWn0qrPvmPTQMva0joFjcCr7msDQr_VXQ" \ -d "scope=openid llm:inference" 2>/dev/null) diff --git a/k8s/apps/paperless/paperless-ai.yaml b/k8s/apps/paperless/paperless-ai.yaml index 2980782..dbd8f5d 100644 --- a/k8s/apps/paperless/paperless-ai.yaml +++ b/k8s/apps/paperless/paperless-ai.yaml @@ -43,9 +43,9 @@ spec: # Get JWT token via client_credentials grant # Client secret sourced from environment (injected from paperless-ai-config Secret) - TOKEN_RESPONSE=$(curl -s -X POST https://authentik.riotpiao.com/application/o/token/ \ + TOKEN_RESPONSE=$(curl -s -X POST https://zitadel.riotpiao.com/oauth/v2/token \ -d "grant_type=client_credentials" \ - -d "client_id=paperless-ai-agent" \ + -d "client_id=396757803559027071" \ -d "client_secret=${LLM_AUTH_CLIENT_SECRET}" \ -d "scope=openid llm:inference" 2>/dev/null) diff --git a/k8s/argocd/secrets/grafana-oidc-secrets.enc.yaml b/k8s/argocd/secrets/grafana-oidc-secrets.enc.yaml index e768d9f..b6c4639 100644 --- a/k8s/argocd/secrets/grafana-oidc-secrets.enc.yaml +++ b/k8s/argocd/secrets/grafana-oidc-secrets.enc.yaml @@ -5,7 +5,7 @@ metadata: namespace: ENC[AES256_GCM,data:ZHnVZyd3wA==,iv:3evUnFTQfRz/ZilUQIl/9dsCba3nsX6o/iQsGXMBfgM=,tag:+9kp+VRu1ksroDTbTGmGPA==,type:str] type: ENC[AES256_GCM,data:bHX+Bu9o,iv:8qqNddsuw4YoHAuJfUcPIqjcqNySIh5Ln8OhsfZxuk8=,tag:P7QGUXo9Io2Y19YqlWv5sQ==,type:str] stringData: - GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ENC[AES256_GCM,data:qW2SQSsGD0YupU2SMzRaYwkLRjXAwV1OJBnhATpCJrOHWbEytw6IphOmPPd3VZKfHncekA4agXi4K3rUPHIx0Q==,iv:NCeW/UfIl0RvHV39ubN2mDfFp7GTQNuFv8KiEF+7KOE=,tag:lisx4h4PqUfLwBX8ca9Siw==,type:str] + GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: ENC[AES256_GCM,data:nJz9pqa6w9ZGOjanZ32D38Bolg2YrdHs1crhb2uZkKyaaKDpklVb4FOKZ46K+NS1NXjoS6XDUAlV69CSvm1PeA==,iv:g9F/iLuzNC0WogtDJ/TAb3+97N6iGkbOTlb4SzYzwx8=,tag:4F3kUheGbtW2U6jgOvRO1A==,type:str] sops: age: - enc: | @@ -17,7 +17,7 @@ sops: LS0cIDUhjPFbeu7qUATOrat6vMUi0UREbXSFZ1KyAjNAwJZaUusPMA== -----END AGE ENCRYPTED FILE----- recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla - lastmodified: "2026-08-12T18:02:01Z" - mac: ENC[AES256_GCM,data:yUBBV7dLEPld9yJacn+YaOT8M1NJ2ploGIxV66aVOH5x4tDAsskcUQJlzApWCQmORuCuMKg6qfpGgLTojfD5msha+ufVVJXHl7ua+STar1/FPJXaHm+z7YyZdsD5j6f3AH8q18DuLy2DYLjXELmPBsp50g7MDJEm3xLhCUXygjQ=,iv:xl0OtA/2umz0YH5b+6QjlAByZWbvfGOmHqomziNjlkw=,tag:xwkQDZx19Lf6QSwU2zPYTQ==,type:str] + lastmodified: "2026-10-26T04:03:16Z" + mac: ENC[AES256_GCM,data:0cgVNtFMwhbEHBxu7T1XfstcQlgUTxijq9Mz36zC60E7kYXRNwGt2/0KoCaqaE0leypwAdrPn2OjeoDPeCN6x+n0vLRVX2tnli6HazBLbRsPHljDAV0ASF07iNhb0PvgtWay432Mm+sGATkel3ITdAaP0lISNLUYfEe1/TbYHkk=,iv:FZ9Bgtk7LOC3aOSOo1+50zwLGhlcUZYJaUQ9r05cAn0=,tag:tsLQ1frZ7li5l717CCtgoQ==,type:str] unencrypted_suffix: _unencrypted version: 3.13.2 diff --git a/k8s/argocd/secrets/homarr-auth-oidc.enc.yaml b/k8s/argocd/secrets/homarr-auth-oidc.enc.yaml index 13c3451..d08ddb3 100644 --- a/k8s/argocd/secrets/homarr-auth-oidc.enc.yaml +++ b/k8s/argocd/secrets/homarr-auth-oidc.enc.yaml @@ -1,24 +1,24 @@ -apiVersion: ENC[AES256_GCM,data:uiQ=,iv:vknDc16Zrz/lxS1WG3ksG4SHjhJSTDdklVxxwL8eylA=,tag:1srpVd6QTYQGGGYGXT8Cbw==,type:str] -kind: ENC[AES256_GCM,data:K6EPYaNE,iv:VWzawfTDNlag+shjBqwSzZGcQQq/TGxeHDqd4fAh5Vc=,tag:ncPqMYjq/73oMzphWhuuFA==,type:str] +apiVersion: v1 +kind: Secret metadata: - name: ENC[AES256_GCM,data:vaRNQHFYfdUz1hzuYO0JxQ==,iv:W7aUJID5mxoO1NQAcyPDYV1FGtBT8myKVMA3s2WgrPk=,tag:4Ef4GBtTR3dVanscvigRmw==,type:str] - namespace: ENC[AES256_GCM,data:HDlmfGwad37/,iv:JFPXxz9xp5vDJRcWy73SYGWDMyHKWQq9Bvy/NJTwTnU=,tag:2ldVXgzKn4xp1rZI/VYgUw==,type:str] -type: ENC[AES256_GCM,data:UgolDLIV,iv:O4himI90UVwx+OjKJXobu10Zg3a5IoQcjAORsdhaWpg=,tag:xltntRsamhQkPEoBNtMytw==,type:str] + name: homarr-oidc + namespace: dashboard +type: Opaque stringData: - oidc-client-id: ENC[AES256_GCM,data:RLdxXY/a,iv:US/fBReK7u8WA4IS6TSzYiHQkte2CuVgugIMpA/pIlA=,tag:QKrHGQrzI9YpfNQD+ts68A==,type:str] - oidc-client-secret: ENC[AES256_GCM,data:SS+I92xEkOcJHTfWvZ1AA8hh1bFAtND5adZ6Uh73nIPiqAo958GSag==,iv:9HcmXWrGtflc6WWQe0n/pgB1Xveakofc3hvz5vuE29A=,tag:0emgeyM4GBftVKn/fx7XPg==,type:str] + client-id: ENC[AES256_GCM,data:IFNjfDMrObaxfGwJpiYqPP60,iv:0WR2C+DEsq8I031A9Bopotn5TYdISApbTF46jJBgtVs=,tag:LGijMS27pbbZnOLJqGNXQg==,type:str] + client-secret: ENC[AES256_GCM,data:q0MHjvuTIKbbMUZpsptOuo3zrabwaWxZS3MLqRdI1Rt7eod5ORAYeZmpllHmojRFve9x/SKkJ+sctafzwPuAPA==,iv:1ZLXOc3a85+MRx3v/XsW8JhsxbCQ26laou8PvvrQvH0=,tag:PRRTsyE2fGGS3dKPh43a8g==,type:str] sops: age: - enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCWkR5V0ZYSTBSSU1OTWF1 - TzFxdnZkUFFIcGJWZ1VUU21ZUnU2cUdob1drCkhVTm02dkZPZ2lNWktOcWZ2c0Vi - dG1PSmZBR2M2anA2Z09iYUU0SjVzTlEKLS0tIGF1eVBBUHdhVk9YSzJlQXVwd3RE - bUd3UTlwR1NiYjNlcGpweTBhdmk5b0UK8pwUoVCr1uBX9dQ7EGRgFMAPk/z8N9Fg - 6GRUORFug5h7x5kjXK9sU0yTHjFSekwBi75GLng0IrLEGJxHVM5wVQ== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBpV1ZlMWxaNTlCUjJjbG9F + NzFGMXBXYThZT285UHA0bGdwNFJqQTNMckhvClFSTEdseExjbVFtbkdsTUpSSGVn + WGsyUmxvcVViVVVNZjFRdmJWZlB6TGcKLS0tIE01QjFtVFBGUlpXbjVJcDMwMzRS + MEh4dUZTZ3puaXF2Uk03Ym50QmdSM1EK6Kmdv3s8/4nI/6Uk4G5FOh1Gxp1u9eYI + L7MOW0TFkScTuDiu9iJB+CjB8b8Z0kwSYTigYj9ue2y0a1hDYCfQtg== -----END AGE ENCRYPTED FILE----- recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla - lastmodified: "2026-08-12T21:07:30Z" - mac: ENC[AES256_GCM,data:NupjQWBJHMOo3/VX5n5nRx+RFT14N71XkrjXRC/VCP7i7H8yn3tcRdH9g14usgWunmqg2KKpGMUIZ9bsQ9Vds9sBvUKvLyRkfQnPwPh9+PFCF3ZZLPgzqcT19kg4I3QabmO+LPzQXsZWLKIqgF9/B60EVIgYyKhZmP7XblY5AVo=,iv:r0deq8+txE8TwzdtxOMaIAZbZc2fu+Tp4Jd2HZGbkhM=,tag:qMpZp6LcWjJHYMDp9ecBkA==,type:str] - unencrypted_suffix: _unencrypted + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-10-26T03:56:19Z" + mac: ENC[AES256_GCM,data:tV7o9zY6dpTdTvNJY8GBkdIB71thoy79RQSHsczednmXXzlNFddZxR/UenJ87jJ1JyzMUKL3CxArYojpuamahQcSXifVar0kotgNCs67A7+xfhHudCnKLMWDfDOom6DdWto6bMNIk/0TF6Wy0m1hkSsxwkdqETVs3b6ZUg/l8Rc=,iv:PE7y5oVMEM9XKQnDZmdRFRR3blzz2SZQFLxOMyILuec=,tag:SM9vJU1zlQ02XJijSiGCMg==,type:str] version: 3.13.2 diff --git a/k8s/argocd/secrets/minio-secrets.enc.yaml b/k8s/argocd/secrets/minio-secrets.enc.yaml index 76898fb..777e38f 100644 --- a/k8s/argocd/secrets/minio-secrets.enc.yaml +++ b/k8s/argocd/secrets/minio-secrets.enc.yaml @@ -1,72 +1,72 @@ -apiVersion: ENC[AES256_GCM,data:Mdg=,iv:u/JjNJfdyz7ehk8lHZRh1u9zkNuufHosyXv10Gm+T6E=,tag:pkgZVJid6kpGuy61+oEMlQ==,type:str] -kind: ENC[AES256_GCM,data:/kWhhqyz,iv:DymezGpBHmKT3BM/CcUKEGIWZw/JXNutReSKfhfmoD4=,tag:DnA+316Z+0eqBWN8YLyv1A==,type:str] +apiVersion: v1 +kind: Secret metadata: - name: ENC[AES256_GCM,data:xs1k//nWy1iJ52Q=,iv:NDGvM319FCypxFLWwONcF12osaU/EL0IwD0c2xtqYnU=,tag:6IhfISF08WQbxrUmT49d7g==,type:str] - namespace: ENC[AES256_GCM,data:jpUzI7BmZA==,iv:PQrfm67la6RJCRq0H7qpiY8n2hPUjcaK6gmJ/zPQW30=,tag:bp/3GYbRpYOIub5nT0zz/A==,type:str] -type: ENC[AES256_GCM,data:fndXB4Y1,iv:EXOe0day1ScxvZ7ozn1QyHgaZgnvlPNmvSQG74aw7lM=,tag:quPmKvFTjRqtZoVUEoQAHQ==,type:str] + name: minio-creds + namespace: storage +type: Opaque stringData: - config.env: ENC[AES256_GCM,data:3RIQ1i6NtY1Tv2eJY8JXSgfBEqi9px/9TJVNrPf9+brtGy9Mx3SwjKVnjGXUcA2cYcNZb1JR7temXmznXQdsRfZ9yohe4oc1vL3yPGPY9YfP7AHGYj9E7tghu/rgkHOf93RmfdKv0Rrq6miBcA==,iv:0RQVMi2mMHBRTJ1OX6IDFvq9Qr7zpqgQzt+MPPgBIHA=,tag:HNXLKf5X4P3E+hc7YbDtNQ==,type:str] + config.env: ENC[AES256_GCM,data:1yogGyBdwrlg27H8R5Kr9VcPu+xwS5/nPhi7eLcmVYtXmPlNokCsWHA70qnn/+I6i4ODys8GML++VeZ/lmcHiP7RIbY+G6xmw6nfBCXitgE8kQwTPsUUzn9bobOn4Sr1YMBf7FQ/Q0N8aupGJA==,iv:EfCdim4TLkP/FP1u1yoKZCq8YGFzVh7ca4yu1+WzXwE=,tag:rTYe0SIAEsUhh0KHQIEKNw==,type:str] sops: age: - enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtUVJFQ1ErNmkzd1N6b29t - L0VWR2tlcEIxeG1CR1hXRkw2cEt0Q2crMWlVClg2UlNhUzdJZGpLZjJtYlAvbk16 - eHk0ZHZiNzVRTWhyQzlieTU5Y3QraGsKLS0tIHA5UVhvUXhNSDk0QlV0R2RzR1dP - azVEQzgvSFNwak9TcW1USFNOb3RvSkUKNLuW3h3cHDSSCZGe9Vyiv5m+wXihtCYX - oJtbRK+kJF8oOUo4emI+SLGrCsWq5cG2wN/wgP+ChR1zUgRgfq5oOA== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2SStVR0lpOWs0VnN6dkdp + RktMUmNRR2diTUtiVk9tRk1wSjVTMnRtVlJBCkk1S0dwNUNHb25RQlZ4ZFRocisv + aS9kbmQydWVmN1AxU3VvbFZsS2tmTHcKLS0tICtRY0ZKeFhCTUdaNW5PSFRNZ2dG + OERIZ0xMbHBvSVNyaGxCVFpqRzlxa1EKbrjfk/CFlTSyJYgTzQ67W3JyR1fwv8VF + dxGeAgLY4EQrf+2PbODtazRFreS+sl7dxq+rKMOVZTsjGHakkqHy9A== -----END AGE ENCRYPTED FILE----- recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla - lastmodified: "2026-08-12T18:02:01Z" - mac: ENC[AES256_GCM,data:hQfimKuhfiIiZQAyNDZQR25HZ5biU+kaWig3ElVPiqMY3B0LrmKyOEs7ittd3j3meUkPEFSmm1yYSXcdCuf6w7cwSbEiL5DG8t7QKPbnx/aRdyHAJW+1VC0e3Lye43gaHya0iUaWS/YFQcrT01oEbckLIDWPKfvA6pg2bZ4kMZ4=,iv:AydyWMVcjTQu+7Fzw5vtqkvTTeQEMt8y6snESoLUDE8=,tag:SoKklNqa9nH9G6d9+PnolQ==,type:str] - unencrypted_suffix: _unencrypted + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-10-26T04:03:16Z" + mac: ENC[AES256_GCM,data:gxu4WcJEuFgjtWCfAjose8R8a566/OLrXoiJmKfcvVfTFDTdGM7I4gzQVvpjEfra5Hjyt7kvLlaV2K5bKJ0k6NXbbmgrqAqEOEcPg+cnPy1FtQGAYOo3VzgKqrpLOCs/7wh4YR23XHHsjQlHAcP8BmhrqnpHXR9FMZObrKjnQj8=,iv:KJZfw0n2DOtdZHeVDk6TrpO8q57yD+xdEVt9+hM0D4Q=,tag:rHQaMjvQwaB8Am/AlTKq1g==,type:str] version: 3.13.2 --- -apiVersion: ENC[AES256_GCM,data:Mdg=,iv:u/JjNJfdyz7ehk8lHZRh1u9zkNuufHosyXv10Gm+T6E=,tag:pkgZVJid6kpGuy61+oEMlQ==,type:str] -kind: ENC[AES256_GCM,data:/kWhhqyz,iv:DymezGpBHmKT3BM/CcUKEGIWZw/JXNutReSKfhfmoD4=,tag:DnA+316Z+0eqBWN8YLyv1A==,type:str] +apiVersion: v1 +kind: Secret metadata: - name: ENC[AES256_GCM,data:XEQHsQqTgwukYw==,iv:wTw2eoooDA9EfB2df3jzb0IWUdtLSwcuKzx8yDBaihQ=,tag:oncXt2gM1k6Qppafpu/D1w==,type:str] - namespace: ENC[AES256_GCM,data:jpUzI7BmZA==,iv:PQrfm67la6RJCRq0H7qpiY8n2hPUjcaK6gmJ/zPQW30=,tag:bp/3GYbRpYOIub5nT0zz/A==,type:str] -type: ENC[AES256_GCM,data:fndXB4Y1,iv:EXOe0day1ScxvZ7ozn1QyHgaZgnvlPNmvSQG74aw7lM=,tag:quPmKvFTjRqtZoVUEoQAHQ==,type:str] + name: minio-oidc + namespace: storage +type: Opaque stringData: - MINIO_IDENTITY_OPENID_CLIENT_SECRET: ENC[AES256_GCM,data:IYBfNW19cTo8WkCGT4zuCljzi2bkbYWJ8DY/yedR2MJNqEO+bMVvjx0OqSqbPp6rnwGyMQOhBN0gXPbEVRDBGg==,iv:I1aGIikbHTKvh//4Inhp7UoNfhjlIXSpcyP4RqljvqM=,tag:Zh3Vh5DQYAsTtiW59D0/QA==,type:str] + MINIO_IDENTITY_OPENID_CLIENT_SECRET: ENC[AES256_GCM,data:eFfwW2ZnV+o3/6XHjUvyFsDbaIAdosY2PXOnRdN4Ie49YDIjfJPe+Mg1FsbgoEHEjuB33hfxesb4TWOJQfW8Og==,iv:+kRr9A3auts1XHgZWa+t5b2m4lF0x9dVhiRpB17fzv4=,tag:tH/jszFzLQdqORGr9vfTGw==,type:str] sops: age: - enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtUVJFQ1ErNmkzd1N6b29t - L0VWR2tlcEIxeG1CR1hXRkw2cEt0Q2crMWlVClg2UlNhUzdJZGpLZjJtYlAvbk16 - eHk0ZHZiNzVRTWhyQzlieTU5Y3QraGsKLS0tIHA5UVhvUXhNSDk0QlV0R2RzR1dP - azVEQzgvSFNwak9TcW1USFNOb3RvSkUKNLuW3h3cHDSSCZGe9Vyiv5m+wXihtCYX - oJtbRK+kJF8oOUo4emI+SLGrCsWq5cG2wN/wgP+ChR1zUgRgfq5oOA== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2SStVR0lpOWs0VnN6dkdp + RktMUmNRR2diTUtiVk9tRk1wSjVTMnRtVlJBCkk1S0dwNUNHb25RQlZ4ZFRocisv + aS9kbmQydWVmN1AxU3VvbFZsS2tmTHcKLS0tICtRY0ZKeFhCTUdaNW5PSFRNZ2dG + OERIZ0xMbHBvSVNyaGxCVFpqRzlxa1EKbrjfk/CFlTSyJYgTzQ67W3JyR1fwv8VF + dxGeAgLY4EQrf+2PbODtazRFreS+sl7dxq+rKMOVZTsjGHakkqHy9A== -----END AGE ENCRYPTED FILE----- recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla - lastmodified: "2026-08-12T18:02:01Z" - mac: ENC[AES256_GCM,data:hQfimKuhfiIiZQAyNDZQR25HZ5biU+kaWig3ElVPiqMY3B0LrmKyOEs7ittd3j3meUkPEFSmm1yYSXcdCuf6w7cwSbEiL5DG8t7QKPbnx/aRdyHAJW+1VC0e3Lye43gaHya0iUaWS/YFQcrT01oEbckLIDWPKfvA6pg2bZ4kMZ4=,iv:AydyWMVcjTQu+7Fzw5vtqkvTTeQEMt8y6snESoLUDE8=,tag:SoKklNqa9nH9G6d9+PnolQ==,type:str] - unencrypted_suffix: _unencrypted + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-10-26T04:03:16Z" + mac: ENC[AES256_GCM,data:gxu4WcJEuFgjtWCfAjose8R8a566/OLrXoiJmKfcvVfTFDTdGM7I4gzQVvpjEfra5Hjyt7kvLlaV2K5bKJ0k6NXbbmgrqAqEOEcPg+cnPy1FtQGAYOo3VzgKqrpLOCs/7wh4YR23XHHsjQlHAcP8BmhrqnpHXR9FMZObrKjnQj8=,iv:KJZfw0n2DOtdZHeVDk6TrpO8q57yD+xdEVt9+hM0D4Q=,tag:rHQaMjvQwaB8Am/AlTKq1g==,type:str] version: 3.13.2 --- -apiVersion: ENC[AES256_GCM,data:Mdg=,iv:u/JjNJfdyz7ehk8lHZRh1u9zkNuufHosyXv10Gm+T6E=,tag:pkgZVJid6kpGuy61+oEMlQ==,type:str] -kind: ENC[AES256_GCM,data:/kWhhqyz,iv:DymezGpBHmKT3BM/CcUKEGIWZw/JXNutReSKfhfmoD4=,tag:DnA+316Z+0eqBWN8YLyv1A==,type:str] +apiVersion: v1 +kind: Secret metadata: - name: ENC[AES256_GCM,data:rYX7nN6SYoG5HycaNNrprDM=,iv:4uX7+Sl1UzgsImBH+qm9p8DlOfJu0i0W3bpoE7cFxXg=,tag:RROCnYBVKVwPBNSGgV30FQ==,type:str] - namespace: ENC[AES256_GCM,data:jpUzI7BmZA==,iv:PQrfm67la6RJCRq0H7qpiY8n2hPUjcaK6gmJ/zPQW30=,tag:bp/3GYbRpYOIub5nT0zz/A==,type:str] -type: ENC[AES256_GCM,data:fndXB4Y1,iv:EXOe0day1ScxvZ7ozn1QyHgaZgnvlPNmvSQG74aw7lM=,tag:quPmKvFTjRqtZoVUEoQAHQ==,type:str] + name: minio-user-ollama + namespace: storage +type: Opaque stringData: - CONSOLE_ACCESS_KEY: ENC[AES256_GCM,data:FhXl6yqTIzHsbJO6P96KQrA=,iv:gGTaSKxk721GwMQWWyymqUPZlGLidrBzWCWdRtnlOr0=,tag:IaVl8GQqPstBuUjD4N6JTQ==,type:str] - CONSOLE_SECRET_KEY: ENC[AES256_GCM,data:s22GOXY4B/m+S7tgDw8P2gU2F15vdn1tqAp53FWzFTI=,iv:GXBhHQ8YiaVjRRMw2yG4FxdulVOuOurkLXXC0u/SFRA=,tag:pvHcD9+4qaxnvhOSkNxoHA==,type:str] + CONSOLE_ACCESS_KEY: ENC[AES256_GCM,data:Zxr/Nj0SUKSJbBD1cFjt3GE=,iv:r/B9ULn68hsXdH8uRQkKAoQTJIJIYKMoJuWOZfxS2I8=,tag:Xo4i+IVxuqrp+qDpYQysPw==,type:str] + CONSOLE_SECRET_KEY: ENC[AES256_GCM,data:PoIDNZ+Xs6BdhGm10y1HdL5jy47EVHfHHIJhGy/KX6Q=,iv:voAv32GoTKi9s9/4MQy2yT6i9VY4/igA5XXzehwUqRQ=,tag:DmPyk5biVvlxHjeyDyrLbQ==,type:str] sops: age: - enc: | -----BEGIN AGE ENCRYPTED FILE----- - YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtUVJFQ1ErNmkzd1N6b29t - L0VWR2tlcEIxeG1CR1hXRkw2cEt0Q2crMWlVClg2UlNhUzdJZGpLZjJtYlAvbk16 - eHk0ZHZiNzVRTWhyQzlieTU5Y3QraGsKLS0tIHA5UVhvUXhNSDk0QlV0R2RzR1dP - azVEQzgvSFNwak9TcW1USFNOb3RvSkUKNLuW3h3cHDSSCZGe9Vyiv5m+wXihtCYX - oJtbRK+kJF8oOUo4emI+SLGrCsWq5cG2wN/wgP+ChR1zUgRgfq5oOA== + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2SStVR0lpOWs0VnN6dkdp + RktMUmNRR2diTUtiVk9tRk1wSjVTMnRtVlJBCkk1S0dwNUNHb25RQlZ4ZFRocisv + aS9kbmQydWVmN1AxU3VvbFZsS2tmTHcKLS0tICtRY0ZKeFhCTUdaNW5PSFRNZ2dG + OERIZ0xMbHBvSVNyaGxCVFpqRzlxa1EKbrjfk/CFlTSyJYgTzQ67W3JyR1fwv8VF + dxGeAgLY4EQrf+2PbODtazRFreS+sl7dxq+rKMOVZTsjGHakkqHy9A== -----END AGE ENCRYPTED FILE----- recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla - lastmodified: "2026-08-12T18:02:01Z" - mac: ENC[AES256_GCM,data:hQfimKuhfiIiZQAyNDZQR25HZ5biU+kaWig3ElVPiqMY3B0LrmKyOEs7ittd3j3meUkPEFSmm1yYSXcdCuf6w7cwSbEiL5DG8t7QKPbnx/aRdyHAJW+1VC0e3Lye43gaHya0iUaWS/YFQcrT01oEbckLIDWPKfvA6pg2bZ4kMZ4=,iv:AydyWMVcjTQu+7Fzw5vtqkvTTeQEMt8y6snESoLUDE8=,tag:SoKklNqa9nH9G6d9+PnolQ==,type:str] - unencrypted_suffix: _unencrypted + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-10-26T04:03:16Z" + mac: ENC[AES256_GCM,data:gxu4WcJEuFgjtWCfAjose8R8a566/OLrXoiJmKfcvVfTFDTdGM7I4gzQVvpjEfra5Hjyt7kvLlaV2K5bKJ0k6NXbbmgrqAqEOEcPg+cnPy1FtQGAYOo3VzgKqrpLOCs/7wh4YR23XHHsjQlHAcP8BmhrqnpHXR9FMZObrKjnQj8=,iv:KJZfw0n2DOtdZHeVDk6TrpO8q57yD+xdEVt9+hM0D4Q=,tag:rHQaMjvQwaB8Am/AlTKq1g==,type:str] version: 3.13.2 diff --git a/k8s/argocd/secrets/paperless-ai-secrets.enc.yaml b/k8s/argocd/secrets/paperless-ai-secrets.enc.yaml index 1c0a0f3..d4b9f83 100644 --- a/k8s/argocd/secrets/paperless-ai-secrets.enc.yaml +++ b/k8s/argocd/secrets/paperless-ai-secrets.enc.yaml @@ -6,7 +6,7 @@ metadata: type: Opaque stringData: PAPERLESS_API_TOKEN: ENC[AES256_GCM,data:wlBC85ep6zioWLhfhNczkKfTMR8tmciN+4r3of8GpQoJmKyE2r7pOg==,iv:Rem/UJl5wUNi8G+uEV7f2eCLabaY26sZ7+AsKYiOZ4I=,tag:bGS1fu13Mwsrr+5NUEzzLw==,type:str] - LLM_AUTH_CLIENT_SECRET: ENC[AES256_GCM,data:co7FfmRXKgrz9fNgR10KtlHL/iG4F5WKJgjcKem1H5hF5FviVi4+CQNekA==,iv:ADtBHiI6BtrkuH1iIfKV3I5odNLMQe5y/9GGTJrPNN0=,tag:j6wX6zO5VhKoLzdr7BQZCw==,type:str] + LLM_AUTH_CLIENT_SECRET: ENC[AES256_GCM,data:OEZqGgJ4cpMsWr/PJH50lsQKvk6sjVNPCFcZP3yQ/7nvlKP3giPlLmW9XgWr1ELq2hwWDs61+f3fAyENt4ln+Q==,iv:W0Tii/nvgm9ZMIEtfzgO5pj4Li+6fpxfd3zFtQhj874=,tag:tsM/fGeduMOEfMZsDZ6lrQ==,type:str] sops: age: - enc: | @@ -19,6 +19,6 @@ sops: -----END AGE ENCRYPTED FILE----- recipient: age1e5fq3hwxy78psus2nfvmtmua36g0u3suk78ephw6246l974d2utsvn0hla encrypted_regex: ^(data|stringData)$ - lastmodified: "2026-09-14T14:19:39Z" - mac: ENC[AES256_GCM,data:jDdmiFg6uB1za7sAjO1CnGHdRPyUnmWCMJxqHrE3BO82pNbtm8yLOf+W6CfFKAjfZYqEE2mdKhABm7zeJgJlub3eXJbAirAFrp5jrbfHw0qsFo85Gj/BsrSDB401Aky6PxvRomz66FyX5zDDXIGAMEMlyNEadMplTfrZQQ9hQWM=,iv:UTSF72ofUyWs1LXGKQwBP60hK64UxhqTFM8QTVozGoo=,tag:OKivMDAqwWgrX+kNjvQrPg==,type:str] + lastmodified: "2026-10-26T04:03:16Z" + mac: ENC[AES256_GCM,data:gOrqy2fPX+77303L+E1EypaTU8mdUXYjbRZnTIJtoRpZhEmHsVvS/Ho8bVcl5KiTPN7UoFNmGyDz8fpyQ/rLTbeCiUYmyxvH7ym9DT4oWvRxITpOqBxz83pWv1BB++HJvqNQ5FllqpnAz60/bAVyxOdOXk4J3lDnuJibNOpxhW8=,iv:suV68u1Uiejl0NNJ3WlAHshgdrSL+aTwUxUBeMXlfWs=,tag:5wug4NeDZ+qclvFGPiEviQ==,type:str] version: 3.13.2 diff --git a/k8s/bootstrap/ingress/ingress.yaml b/k8s/bootstrap/ingress/ingress.yaml index 9f8c20e..9079675 100644 --- a/k8s/bootstrap/ingress/ingress.yaml +++ b/k8s/bootstrap/ingress/ingress.yaml @@ -49,30 +49,6 @@ spec: port: number: 3100 ---- -# ── Authentik ───────────────────────────────────────────────────────────────── -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: authentik - namespace: iam - annotations: - nginx.ingress.kubernetes.io/proxy-buffer-size: "16k" - nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" -spec: - ingressClassName: nginx - rules: - - host: authentik.riotpiao.com - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: authentik-server - port: - number: 80 - --- # ── Vault ───────────────────────────────────────────────────────────────────── apiVersion: networking.k8s.io/v1 diff --git a/k8s/bootstrap/phase4-argocd/argocd-values.yaml b/k8s/bootstrap/phase4-argocd/argocd-values.yaml index a8b7bc4..46acd6a 100644 --- a/k8s/bootstrap/phase4-argocd/argocd-values.yaml +++ b/k8s/bootstrap/phase4-argocd/argocd-values.yaml @@ -138,12 +138,12 @@ configs: # cicd — automation-only; apiKey (token) for the CD pipeline, no UI login. accounts.rock: apiKey,login accounts.cicd: apiKey - # Authentik OIDC. clientSecret pulled from the argocd `oidc-secret` Secret - # (created by authentik-provision). The groups claim drives RBAC below. + # Zitadel OIDC. clientSecret pulled from the argocd `oidc-secret` Secret. + # The groups claim drives RBAC below. oidc.config: | - name: Authentik - issuer: https://authentik.riotpiao.com/application/o/argocd/ - clientID: argocd + name: Zitadel + issuer: https://zitadel.riotpiao.com + clientID: 396757801210282367 clientSecret: $oidc-secret:client-secret requestedScopes: - openid @@ -158,7 +158,7 @@ configs: server.insecure: false # RBAC. local `admin` + `rock` + the `cicd` pipeline account all get role:admin; - # the Authentik `homelab-admins` group (rock is a member) maps to admin so SSO + # the Zitadel `homelab-admins` role (rock is a member) maps to admin so SSO # logins are admin too. rbac: policy.default: role:readonly diff --git a/k8s/infra/logging/grafana-values.yaml b/k8s/infra/logging/grafana-values.yaml index cef8e08..ebb3459 100644 --- a/k8s/infra/logging/grafana-values.yaml +++ b/k8s/infra/logging/grafana-values.yaml @@ -60,28 +60,28 @@ grafana.ini: database: wal: true - # ── Authentik OIDC (generic OAuth2) ─────────────────────────────────────── + # ── ZITADEL OIDC (generic OAuth2) ──────────────────────────────────────── # Grafana v10+ supports OIDC auto-discovery; we wire it manually here because - # Authentik's discovery endpoint is internal-only (no external DNS for iam.svc). + # ZITADEL's discovery endpoint is internal-only (no external DNS for iam.svc). # All URLs use the external hostname so CoreDNS rewrites them to - # authentik-server.iam.svc — this keeps the Host header correct so Authentik + # zitadel.iam.svc — this keeps the Host header correct so ZITADEL # doesn't return localhost redirects in its token responses. # # role_attribute_path: JMESPath expression evaluated against the userinfo # response. akadmin gets GrafanaAdmin (server admin, can impersonate); - # homelab-admins members get Admin (org admin); everyone else Viewer. + # homelab-admins role members get Admin (org admin); everyone else Viewer. auth.generic_oauth: enabled: true - name: Authentik + name: Zitadel allow_sign_up: true # Allow sync to create/update users from OAuth claims - client_id: grafana + client_id: 396757801965191169 scopes: openid email profile groups - auth_url: https://authentik.riotpiao.com/application/o/authorize/ - token_url: https://authentik.riotpiao.com/application/o/token/ - api_url: https://authentik.riotpiao.com/application/o/userinfo/ + auth_url: https://zitadel.riotpiao.com/oauth/v2/authorize + token_url: https://zitadel.riotpiao.com/oauth/v2/token + api_url: https://zitadel.riotpiao.com/oidc/v1/userinfo # Read identity straight from the userinfo/id_token claims. Without these, # Grafana falls back to the GitHub-style "/emails" call, which - # Authentik doesn't serve — request 404s with "Error getting email address" + # ZITADEL doesn't serve — request 404s with "Error getting email address" # and the whole OAuth login fails. email_attribute_path: email login_attribute_path: preferred_username # Use preferred_username — OpenID standard claim, always present @@ -91,10 +91,10 @@ grafana.ini: use_pkce: false use_refresh_token: false skip_org_role_sync: false # Sync org roles from OAuth groups - tls_skip_verify_insecure: true # Authentik uses self-signed cert; verify in prod + tls_skip_verify_insecure: true # In-cluster cert; verify in prod # GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET is injected from the grafana-oidc K8s -# Secret (created by k8s/talos-iam/setup_talos_iam.sh). envFromSecret mounts +# Secret (k8s/argocd/secrets/grafana-oidc-secrets.enc.yaml). envFromSecret mounts # every key in that secret as an environment variable — keeps secrets out of # values files and out of git. envFromSecret: grafana-oidc diff --git a/k8s/infra/minio/minio-tenant.yaml b/k8s/infra/minio/minio-tenant.yaml index ab61e93..1dc54f2 100644 --- a/k8s/infra/minio/minio-tenant.yaml +++ b/k8s/infra/minio/minio-tenant.yaml @@ -103,17 +103,17 @@ spec: - https://minio-api.riotpiao.com console: https://minio.riotpiao.com - # ── OIDC via Authentik (server-side env, valid in v2 schema) ──────────────── - # Use in-cluster URL for config fetch (pod→authentik); browser redirects use - # public URLs embedded in the OIDC metadata response (issuer stays public). + # ── OIDC via ZITADEL (server-side env, valid in v2 schema) ────────────────── + # Use external URL for config fetch (hairpins through ingress-nginx); browser + # redirects use public URLs embedded in the OIDC metadata response. env: - name: MINIO_IDENTITY_OPENID_CONFIG_URL # Must use external URL — well-known response contains external issuer/jwks_uri. # MinIO validates issuer in JWT matches well-known issuer. Internal URL = mismatch. # Hairpins through ingress-nginx but stays in-cluster. - value: "https://authentik.riotpiao.com/application/o/minio/.well-known/openid-configuration" + value: "https://zitadel.riotpiao.com/.well-known/openid-configuration" - name: MINIO_IDENTITY_OPENID_CLIENT_ID - value: "minio" + value: "396757803257036801" - name: MINIO_IDENTITY_OPENID_CLIENT_SECRET valueFrom: secretKeyRef: @@ -126,7 +126,7 @@ spec: - name: MINIO_IDENTITY_OPENID_REDIRECT_URI value: "https://minio.riotpiao.com/oauth_callback" - name: MINIO_IDENTITY_OPENID_DISPLAY_NAME - value: "Authentik" + value: "Zitadel" - name: MINIO_IDENTITY_OPENID_SCOPES value: "openid,profile,email,groups" - name: MINIO_BROWSER_LOGIN_ANIMATION diff --git a/k8s/infra/monitoring/blackbox-exporter-values.yaml b/k8s/infra/monitoring/blackbox-exporter-values.yaml index a05a075..39b4284 100644 --- a/k8s/infra/monitoring/blackbox-exporter-values.yaml +++ b/k8s/infra/monitoring/blackbox-exporter-values.yaml @@ -43,8 +43,8 @@ serviceMonitor: url: https://grafana.riotpiao.com/ - name: loki url: https://loki.riotpiao.com/ready - - name: authentik - url: https://authentik.riotpiao.com/-/health/ready/ + - name: zitadel + url: https://zitadel.riotpiao.com/debug/ready - name: vault url: https://vault.riotpiao.com/v1/sys/health - name: minio-console diff --git a/scripts/iam/zitadel-provision.py b/scripts/iam/zitadel-provision.py index 46f3aa0..716605e 100755 --- a/scripts/iam/zitadel-provision.py +++ b/scripts/iam/zitadel-provision.py @@ -362,10 +362,14 @@ def phase_apply(_args: argparse.Namespace) -> int: SECRET_MAP = { "grafana": [("grafana-oidc-secrets.enc.yaml", "grafana-oidc", "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET", "secret")], + "homarr": [("homarr-auth-oidc.enc.yaml", "homarr-oidc", + "client-id", "id"), + ("homarr-auth-oidc.enc.yaml", "homarr-oidc", + "client-secret", "secret")], "minio": [("minio-secrets.enc.yaml", "minio-oidc", "MINIO_IDENTITY_OPENID_CLIENT_SECRET", "secret")], - "paperless-ai-agent": [("paperless-ai-secrets.enc.yaml", "paperless-ai-config", - "LLM_AUTH_CLIENT_SECRET", "secret")], + "paperless": [("paperless-ai-secrets.enc.yaml", "paperless-ai-config", + "LLM_AUTH_CLIENT_SECRET", "secret")], } # Clients whose client_id lives in a plain (unencrypted) manifest. They need the @@ -375,18 +379,14 @@ CLIENT_ID_MAP = { "grafana": ("k8s/infra/logging/grafana-values.yaml", r"(?m)^(\s*client_id:\s*)\S+\s*$", r"\g<1>{value}"), "minio": ("k8s/infra/minio/minio-tenant.yaml", - r"(?m)^(\s*value:\s*)\"minio\"\s*$", r'\g<1>"{value}"'), - "paperless-ai-agent": ("k8s/apps/paperless/paperless-ai.yaml", - r"client_id=paperless-ai-agent", "client_id={value}"), + r'(?m)^(\s*value:\s*)"(?:minio|\d+)"\s*$', r'\g<1>"{value}"'), + "paperless": ("k8s/apps/paperless/paperless-ai.yaml", + r"client_id=(?:paperless-ai-agent|\d+)", "client_id={value}"), } # Secrets that a live workload mounts but no SOPS file in git defines. The phase # cannot re-point them safely, so it names them. -MANUAL_ONLY = { - "homarr": "Secret homarr-oidc (keys client-id, client-secret) has no SOPS file in git — " - "adopt it first, then add it to SECRET_MAP. The repo also carries an orphan " - "homarr-auth-oidc.enc.yaml that nothing consumes.", -} +MANUAL_ONLY: dict[str, str] = {} def state_outputs() -> dict: -- 2.54.0