背景#

我的 Homelab 由两个 K3s 集群组成:

集群 部署位置 服务
homelab (k3s-homelab) Proxmox 虚拟机 Calibre-Web, Grafana, ArgoCD, Vault, Kopia, ZITADEL
oracle-k3s Oracle Cloud IT-Tools, Stirling-PDF, Squoosh, Homepage, Uptime Kuma, Miniflux

之前所有服务各自管理登录——有的没有认证(公开访问),有的用内置用户名密码。这带来了几个问题:

  1. 安全性差:部分服务直接暴露在公网无认证
  2. 体验碎片化:每个服务都需要单独登录
  3. 管理困难:密码分散在各处,无法统一管控

目标是部署一个自托管的 OIDC 身份提供者(Identity Provider),让所有受保护服务共用一套认证,实现 一次登录,多处访问

为什么选择 ZITADEL#

对比了几个方案:

方案 优点 缺点
Keycloak 功能最全,社区大 太重(Java),内存 512MB+,配置复杂
Authentik 现代 UI,功能丰富 Python,内存需求 ~500MB
Authelia 轻量,Go 编写 不是完整的 IdP,更偏向认证中间件
ZITADEL Go 编写,轻量(~100MB),内置多租户,完整的 OIDC 支持 相对较新,社区较小

ZITADEL 是用 Go 编写的云原生身份管理平台,资源占用低,提供完整的 OIDC/OAuth2 协议支持,非常适合 Homelab 场景。

整体架构#

最终实现的 SSO 架构如下:

Internet → Cloudflare DNS → Cloudflare Tunnel
                                    │
              ┌─────────────────────┼─────────────────────┐
              │                     │                     │
        oracle-k3s            homelab k3s            homelab k3s
              │                     │                     │
         Traefik              Traefik               Traefik
              │                     │                     │
    ┌─────────┴──────┐        ZITADEL              其他服务
    │ ForwardAuth    │    (auth.meirong.dev)     (book/grafana/...)
    │ Middleware     │          │
    │    ↓           │          │
    │ oauth2-proxy   │──OIDC──→│
    │ (auth-system)  │          │
    └────────────────┘          │
              │                 │
         Protected Services     │
    (tool/pdf/squoosh/home/...) │

核心组件:

组件 集群 命名空间 作用
ZITADEL homelab zitadel OIDC 身份提供者
oauth2-proxy oracle-k3s auth-system ForwardAuth 认证代理
Traefik Middleware oracle-k3s 各业务命名空间 请求拦截,调用 ForwardAuth

认证流程:

  1. 用户访问 tool.meirong.dev
  2. Traefik 拦截请求,通过 ForwardAuth 检查 oauth2-proxy
  3. 无有效 session → 302 重定向到 ZITADEL 登录页 (auth.meirong.dev)
  4. 用户输入用户名密码完成认证
  5. ZITADEL 回调 oauth2-proxy → 设置 .meirong.dev 域 Cookie
  6. 后续访问同域名下的任何服务无需再次登录

第一步:部署 ZITADEL (homelab 集群)#

1.1 前置准备 — 密钥管理#

ZITADEL 需要三组密钥。我使用 HashiCorp Vault + External Secrets Operator (ESO) 管理:

# 在 Vault 中创建 ZITADEL 密钥
vault kv put secret/homelab/zitadel \
  master-key=$(openssl rand -hex 16) \
  db-password=$(openssl rand -base64 24)

对应的 ExternalSecret 定义:

# zitadel-masterkey — ZITADEL 数据加密主密钥(32字符)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: zitadel-masterkey
  namespace: zitadel
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: zitadel-masterkey
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        masterkey: "{{ .masterkey }}"
  data:
    - secretKey: masterkey
      remoteRef:
        key: secret/homelab/zitadel
        property: master-key
---
# zitadel-postgres-auth — PostgreSQL 认证密码
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: zitadel-postgres-auth
  namespace: zitadel
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: zitadel-postgres-auth
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        postgres-password: "{{ .db_password }}"
        password: "{{ .db_password }}"
  data:
    - secretKey: db_password
      remoteRef:
        key: secret/homelab/zitadel
        property: db-password
---
# zitadel-config — ZITADEL 数据库连接密码(config secret 格式)
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: zitadel-config
  namespace: zitadel
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: zitadel-config
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        config-yaml: |
          Database:
            Postgres:
              User:
                Password: "{{ .db_password }}"
              Admin:
                Password: "{{ .db_password }}"          
  data:
    - secretKey: db_password
      remoteRef:
        key: secret/homelab/zitadel
        property: db-password

如果你没有 Vault + ESO,也可以直接用 kubectl create secret 手工创建这三个 Secret。关键是三个 Secret 中的数据库密码要一致

1.2 部署 PostgreSQL#

ZITADEL 需要 PostgreSQL 作为后端数据库。我使用 K3s 的 HelmChart CRD 来管理 Helm release:

apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: zitadel-db
  namespace: kube-system  # K3s HelmChart CRD 必须在 kube-system
spec:
  repo: https://charts.bitnami.com/bitnami
  chart: postgresql
  version: 12.10.0
  targetNamespace: zitadel
  createNamespace: true
  valuesContent: |-
    image:
      repository: bitnamilegacy/postgresql
    architecture: standalone
    auth:
      enablePostgresUser: true
      username: zitadel
      database: zitadel
      existingSecret: zitadel-postgres-auth
      secretKeys:
        adminPasswordKey: postgres-password
        userPasswordKey: password
    primary:
      persistence:
        enabled: true
        storageClass: nfs-client
        size: 8Gi    

重要:必须等 PostgreSQL Pod 进入 Running 状态后再部署 ZITADEL。ZITADEL 的 init/setup Jobs 会立即尝试连接数据库,如果 DB 还没就绪,Job 会失败。

# 等待 DB 就绪
kubectl get pod zitadel-db-postgresql-0 -n zitadel -w

1.3 部署 ZITADEL#

apiVersion: helm.cattle.io/v1
kind: HelmChart
metadata:
  name: zitadel
  namespace: kube-system
spec:
  repo: https://charts.zitadel.com
  chart: zitadel
  version: 9.24.0
  targetNamespace: zitadel
  createNamespace: true
  valuesContent: |-
    zitadel:
      masterkeySecretName: zitadel-masterkey
      configSecretName: zitadel-config
      configmapConfig:
        ExternalSecure: true
        ExternalDomain: auth.meirong.dev
        ExternalPort: 443
        TLS:
          Enabled: false
        Database:
          Postgres:
            Host: zitadel-db-postgresql
            Port: 5432
            Database: zitadel
            MaxOpenConns: 20
            MaxIdleConns: 10
            MaxConnLifetime: 30m
            MaxConnIdleTime: 5m
            User:
              Username: zitadel
              SSL:
                Mode: disable
            Admin:
              Username: postgres
              SSL:
                Mode: disable
    ingress:
      enabled: false
    login:
      ingress:
        enabled: false
      service:
        appProtocol: ""  # 关键!见下方踩坑记录    

关键配置说明:

  • ExternalSecure: true + ExternalPort: 443:ZITADEL 本身不启用 TLS,但告诉它外部访问使用 HTTPS(由 Cloudflare 提供证书)
  • TLS.Enabled: false:集群内部通信不加密,TLS 在 Cloudflare 层终止
  • ingress.enabled: false:不使用 ZITADEL 自带的 Ingress,改用 Gateway API HTTPRoute
  • login.service.appProtocol: ""这是一个关键设置,后面会详细解释

部署后会自动运行两个 Job:

$ kubectl get jobs -n zitadel
NAME              STATUS     COMPLETIONS   DURATION
zitadel-init      Complete   1/1           5s        # 初始化数据库 schema
zitadel-setup     Complete   1/1           64s       # 创建管理员用户和密钥

Setup Job 完成后会自动创建三个重要的 Secret:

$ kubectl get secrets -n zitadel | grep -E "iam-admin|login-client"
iam-admin          Opaque   1      # 管理员用户信息
iam-admin-pat      Opaque   1      # 管理员 Personal Access Token
login-client       Opaque   1      # Login UI 的 service account token

1.4 配置 Gateway 路由#

ZITADEL v9+ 将登录 UI 拆分为独立的 Next.js 应用(zitadel-login 服务)。因此需要两条路由规则:

# ReferenceGrant — 允许 Gateway 路由到 zitadel 命名空间
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-to-zitadel
  namespace: zitadel
spec:
  from:
    - group: gateway.networking.k8s.io
      kind: HTTPRoute
      namespace: zitadel
  to:
    - group: ""
      kind: Service
---
# HTTPRoute — 拆分路由
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: zitadel
  namespace: zitadel
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: homelab-gateway
      namespace: kube-system
      port: 8000
  hostnames:
    - "auth.meirong.dev"
  rules:
    # 登录 UI 路由到 zitadel-login (Next.js, 端口 3000)
    - matches:
        - path:
            type: PathPrefix
            value: /ui/v2/login
      backendRefs:
        - group: ""
          kind: Service
          name: zitadel-login
          port: 3000
          weight: 1
    # 其他所有请求路由到 zitadel 主服务 (gRPC/REST API, 端口 8080)
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - group: ""
          kind: Service
          name: zitadel
          port: 8080
          weight: 1

1.5 配置 Cloudflare DNS#

在 Cloudflare Tunnel 中添加 auth 子域名的 ingress 规则:

# cloudflare/terraform/terraform.tfvars
homelab_ingress_rules = {
  "auth"    = { service = "http://traefik.kube-system.svc:80" }
  # ...其他规则
}

执行 terraform apply 后,auth.meirong.dev 的流量将通过 Cloudflare Tunnel 转发到集群内的 Traefik。

1.6 验证 ZITADEL#

# 检查 OIDC Discovery 端点
$ curl -s https://auth.meirong.dev/.well-known/openid-configuration | jq .issuer
"https://auth.meirong.dev"

# 获取管理员 PAT
$ PAT=$(kubectl get secret iam-admin-pat -n zitadel \
    -o jsonpath='{.data.pat}' | base64 -d)

# 查询组织信息
$ curl -s -H "Authorization: Bearer $PAT" \
    https://auth.meirong.dev/management/v1/orgs/me | jq .org.name
"ZITADEL"

第二步:创建 OIDC 客户端#

ZITADEL 运行后,需要创建一个 OIDC 客户端给 oauth2-proxy 使用。

2.1 创建项目#

PAT=$(kubectl get secret iam-admin-pat -n zitadel \
    -o jsonpath='{.data.pat}' | base64 -d)

# 创建项目
curl -s -X POST \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"name":"Homelab SSO","projectRoleAssertion":true}' \
  https://auth.meirong.dev/management/v1/projects | jq .

返回:

{ "id": "361912262883016747" }

2.2 创建 OIDC 应用#

PROJECT_ID="361912262883016747"

curl -s -X POST \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "oauth2-proxy",
    "redirectUris": ["https://oauth.meirong.dev/oauth2/callback"],
    "responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
    "grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"],
    "appType": "OIDC_APP_TYPE_WEB",
    "authMethodType": "OIDC_AUTH_METHOD_TYPE_BASIC",
    "postLogoutRedirectUris": ["https://oauth.meirong.dev"],
    "accessTokenType": "OIDC_TOKEN_TYPE_BEARER",
    "idTokenRoleAssertion": true,
    "idTokenUserinfoAssertion": true
  }' \
  "https://auth.meirong.dev/management/v1/projects/${PROJECT_ID}/apps/oidc" | jq .

返回:

{
  "appId": "361912276724219947",
  "clientId": "361912276724285483",
  "clientSecret": "4ViESg68axzkOWodNGePgjIr52BPgu0S3U7JZgFGUH27v2CE1n35luKln182vHb3"
}

注意clientSecret 只在创建时返回一次,务必立即保存!如果丢失,需要通过 API 调用 _generate_client_secret 重新生成。

2.3 创建登录用户#

# 使用 v2 API 创建用户
curl -s -X POST \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "userName": "admin",
    "profile": {
      "givenName": "Matthew",
      "familyName": "Admin",
      "displayName": "Matthew Admin"
    },
    "email": {
      "email": "[email protected]",
      "verification": {"returnCode":{}}
    },
    "password": {
      "password": "YourSecurePassword!",
      "changeRequired": false
    }
  }' \
  https://auth.meirong.dev/v2/users/human | jq .

# 验证邮箱(跳过邮件验证)
USER_ID="<返回的 userId>"
curl -s -X PUT \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","isEmailVerified":true}' \
  "https://auth.meirong.dev/management/v1/users/${USER_ID}/email"

# 将用户授权到项目
curl -s -X POST \
  -H "Authorization: Bearer $PAT" \
  -H "Content-Type: application/json" \
  -d "{\"userId\":\"${USER_ID}\",\"projectId\":\"${PROJECT_ID}\"}" \
  "https://auth.meirong.dev/management/v1/users/${USER_ID}/grants"

2.4 存储凭据到 Vault#

vault kv put secret/oracle-k3s/oauth2-proxy \
  client-id=361912276724285483 \
  client-secret='4ViESg68axzkOWodNGePgjIr52BPgu0S3U7JZgFGUH27v2CE1n35luKln182vHb3' \
  cookie-secret=$(openssl rand -hex 16)

第三步:部署 oauth2-proxy (oracle-k3s 集群)#

3.1 ExternalSecret — 从 Vault 同步凭据#

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: oauth2-proxy-secret
  namespace: auth-system
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: oauth2-proxy-secret
    creationPolicy: Owner
  data:
    - secretKey: client-id
      remoteRef:
        key: oracle-k3s/oauth2-proxy
        property: client-id
    - secretKey: client-secret
      remoteRef:
        key: oracle-k3s/oauth2-proxy
        property: client-secret
    - secretKey: cookie-secret
      remoteRef:
        key: oracle-k3s/oauth2-proxy
        property: cookie-secret

3.2 Deployment — oauth2-proxy 配置#

apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: auth-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
        - name: oauth2-proxy
          image: quay.io/oauth2-proxy/oauth2-proxy:v7.7.1
          ports:
            - containerPort: 4180
              name: http
          args:
            - --provider=oidc
            - --oidc-issuer-url=https://auth.meirong.dev
            - --scope=openid profile email
            - --email-domain=*
            - --upstream=static://202
            - --http-address=0.0.0.0:4180
            - --cookie-domain=.meirong.dev
            - --cookie-secure=true
            - --cookie-samesite=lax
            - --cookie-expire=168h
            - --redirect-url=https://oauth.meirong.dev/oauth2/callback
            - --skip-provider-button=true
            - --pass-authorization-header=true
            - --set-xauthrequest=true
            - --reverse-proxy=true
            - --whitelist-domain=.meirong.dev
            - --silence-ping-logging=true
          env:
            - name: OAUTH2_PROXY_CLIENT_ID
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secret
                  key: client-id
            - name: OAUTH2_PROXY_CLIENT_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secret
                  key: client-secret
            - name: OAUTH2_PROXY_COOKIE_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secret
                  key: cookie-secret
          resources:
            requests:
              cpu: 10m
              memory: 32Mi
            limits:
              memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
  name: oauth2-proxy
  namespace: auth-system
spec:
  type: ClusterIP
  selector:
    app: oauth2-proxy
  ports:
    - port: 4180
      targetPort: 4180
      name: http

关键参数说明:

参数 说明
--upstream=static://202 纯 ForwardAuth 模式,oauth2-proxy 不反向代理后端服务
--cookie-domain=.meirong.dev Cookie 域覆盖所有子域名,实现跨子域 SSO
--cookie-expire=168h Session 有效期 7 天
--redirect-url 认证回调地址,需要一个专门的子域名
--whitelist-domain=.meirong.dev 允许重定向到任意 *.meirong.dev 子域
--reverse-proxy=true 信任反向代理传递的 X-Forwarded-* 头

第四步:配置 Traefik ForwardAuth#

4.1 定义 Middleware#

apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: sso-forwardauth
  namespace: personal-services  # 需要和 HTTPRoute 在同一命名空间
spec:
  forwardAuth:
    address: http://oauth2-proxy.auth-system.svc.cluster.local:4180/
    trustForwardHeader: true
    authRequestHeaders:
      - Cookie
      - X-Forwarded-Host
      - X-Forwarded-Uri
      - X-Forwarded-Proto
      - X-Real-Ip
    authResponseHeaders:
      - X-Auth-Request-User
      - X-Auth-Request-Email
      - X-Auth-Request-Groups

注意:Traefik 的 Middleware CRD 需要和引用它的 HTTPRoute 在同一命名空间。如果有多个命名空间的 HTTPRoute 需要保护,需要在每个命名空间创建一份 Middleware。

4.2 在 HTTPRoute 中引用 ForwardAuth#

以 IT-Tools 为例:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: it-tools
  namespace: personal-services
spec:
  parentRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: oracle-gateway
      namespace: kube-system
      port: 8000
  hostnames:
    - "tool.meirong.dev"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      filters:
        # 添加 ForwardAuth 过滤器
        - type: ExtensionRef
          extensionRef:
            group: traefik.io
            kind: Middleware
            name: sso-forwardauth
      backendRefs:
        - group: ""
          kind: Service
          name: it-tools
          port: 80
          weight: 1

只需在 filters 中添加 ExtensionRef 引用 sso-forwardauth Middleware,该服务就纳入了 SSO 保护。

验证 SSO 流程#

部署完成后,验证整个认证链路:

# 1. 访问受保护服务 → 应该 302 重定向到 ZITADEL
$ curl -s -o /dev/null -w "%{http_code}" https://tool.meirong.dev/
302

# 2. 检查重定向目标包含正确的 client_id
$ curl -s -D - https://tool.meirong.dev/ 2>&1 | grep location
location: https://auth.meirong.dev/oauth/v2/authorize?client_id=361912276724285483&...

# 3. ZITADEL authorize 端点应返回 302(重定向到登录页),而不是 400
$ curl -s -o /dev/null -w "%{http_code}" \
    "https://auth.meirong.dev/oauth/v2/authorize?client_id=361912276724285483&redirect_uri=https%3A%2F%2Foauth.meirong.dev%2Foauth2%2Fcallback&response_type=code&scope=openid+profile+email&state=test"
302

# 4. 登录页应该可以正常加载
$ curl -s -o /dev/null -w "%{http_code}" \
    "https://auth.meirong.dev/ui/v2/login/healthy"
200

在浏览器中打开 tool.meirong.dev,应该会看到:

  1. 自动跳转到 auth.meirong.dev 的登录页面
  2. 输入用户名(admin[email protected])和密码
  3. 认证成功后自动跳回 tool.meirong.dev
  4. 此时访问 pdf.meirong.devhome.meirong.dev 等同域名服务无需再次登录

踩坑记录#

坑 1:ZITADEL v9 的 Login UI 拆分#

ZITADEL v9 之前,登录 UI 内嵌在主服务中。从 v9 开始,登录 UI 被拆分为独立的 Next.js 应用 (zitadel-login),运行在端口 3000。

如果只配置一条 / → zitadel:8080 的路由,登录页面会返回 404,因为 /ui/v2/login/* 路径需要路由到 zitadel-login:3000

解决方案:在 HTTPRoute 中配置两条规则——/ui/v2/login 路径优先匹配到 login 服务,其余路由到主服务。

坑 2:Traefik 不支持 appProtocol#

ZITADEL Helm chart 为 login 服务的 Service 设置了 appProtocol: kubernetes.io/http。Traefik 的 Gateway API 实现不识别这个字段,会导致路由解析失败:

Cannot load HTTPBackendRef zitadel/zitadel-login: unsupported application protocol kubernetes.io/http

所有经过 login 服务的请求都返回 500

解决方案:在 Helm values 中设置 login.service.appProtocol: "" 清除该字段。

坑 3:数据库初始化顺序#

ZITADEL 的 initsetup Job 在 Helm release 安装时立即创建。如果 PostgreSQL 还没有就绪,Job 会连接失败并进入 Error 状态。Job 失败后不会自动重试(默认 backoffLimit 较低)。

解决方案:分两步部署——先 apply PostgreSQL HelmChart,等 Pod Ready 后再 apply ZITADEL HelmChart。

坑 4:client_secret 换行问题#

通过 API 创建 OIDC 应用时,返回的 clientSecret 在终端中可能因为行宽被拆成多行。如果直接复制粘贴到 vault kv put 命令中,可能会引入换行符,导致 oauth2-proxy 在 token exchange 时报错:

oidc_error.parent="passwap: password does not match hash"
oidc_error.description="invalid secret"
oidc_error.type=invalid_client

解决方案:用 jqpython3 提取 secret 值,避免手动复制:

NEW_SECRET=$(curl -s -X POST ... | python3 -c "import sys,json; print(json.load(sys.stdin)['clientSecret'], end='')")

如果已经出现 invalid_client 错误,可以重新生成 secret:

curl -s -X POST \
  -H "Authorization: Bearer $PAT" \
  "https://auth.meirong.dev/management/v1/projects/${PROJECT_ID}/apps/${APP_ID}/oidc_config/_generate_client_secret"

坑 5:密钥一致性#

ZITADEL 需要三个 Secret 中的数据库密码完全一致:

  • zitadel-postgres-auth:PostgreSQL 使用的认证密码
  • zitadel-config:ZITADEL 连接数据库使用的密码
  • PostgreSQL PVC 中已初始化的密码

如果 PVC 中的数据是用密码 A 初始化的,但 Secret 被改成密码 B,ZITADEL init Job 会报 password authentication failed

解决方案:如果密码不一致,最干净的方式是删除命名空间(包括 PVC),重新从头部署。使用 Vault + ESO 可以从根本上避免这个问题——所有 Secret 引用同一个 Vault 路径的同一个 key。

总结#

最终实现的效果:

效果 说明
统一登录入口 auth.meirong.dev 作为唯一身份提供者
跨域 SSO 一次登录覆盖所有 *.meirong.dev 子域名
异步保护 新增服务只需在 HTTPRoute 添加 ForwardAuth Filter
密钥管理 Vault → ESO → K8s Secret,全自动同步
资源开销 ZITADEL ~100MB,oauth2-proxy ~32MB,总计不到 200MB 内存

后续计划(Phase 2):

  • Grafana 原生 OIDC 集成(auth.generic_oauth
  • ArgoCD 原生 OIDC 集成(Dex config)
  • Miniflux 原生 OAuth2 集成(OAUTH2_* env vars)
  • Vault OIDC auth method

参考资料#