Argo CD SSO Entegrasyonu: ZITADEL ile OIDC ve RBAC

Argo CD'nin admin parolası yerine ZITADEL ile OIDC girişi ve gruplara göre RBAC; 04'teki HA lab'ın üzerine, Dex olmadan.

Yazan: Mirac Can Yılmaz 18 dk okuma
Argo CD SSO Entegrasyonu: ZITADEL ile OIDC ve RBAC - Security yazısının kapak görseli

Önceki yazıdaki Argo CD'ye tek bir kullanıcıyla giriliyordu: admin, parolası da argocd-initial-admin-secret içinde. Ekipte herkes aynı parolayı kullandığında, UI'da kimin neyi sync ettiği görünmez ve birini ekipten çıkarmanın tek yolu parolayı herkes için değiştirmektir.

Bu yazıda aynı cluster'a ZITADEL kurup Argo CD'yi ona bağlıyoruz. Dex yok; Argo CD OIDC ile doğrudan ZITADEL'e konuşuyor. Rolleri ZITADEL veriyor, Argo CD RBAC onları okuyor: platform admin'i alice her şeyi yönetiyor, geliştirici dave sadece ürün uygulamalarını görüp sync edebiliyor, bob ise her şeyi görüyor ama sync düğmesine bastığında permission denied alıyor (HTTP 403). ZITADEL ve Postgres'i de Argo CD'nin kendisi kuruyor; temiz bir cluster'da 9 Application 48 saniyede yeşil.

Bir şeyi baştan söyleyeyim: ZITADEL'in Postgres parolası ve masterkey'i bu yazıda Git'te base64 Secret olarak duruyor. Bilerek. Çoğu ekip buradan başlıyor ve base64 bir şifreleme değil. Bir sonraki yazı bu Secret'ları Vault'a taşıyor ve taşırken hepsini değiştiriyor.

Lab argocd-sso-zitadel klasöründe. Platform tarafı platform-gitops reposunun blog-05 branch'inde: önceki yazının blog-04'ü, üstüne ZITADEL.

Neye ihtiyacınız var

  • Önceki yazının lab'ı: Argo CD'yi HA Kurup Bozarak Anlamak. Aynı kind cluster'ı, aynı üç repo. Bu lab'ın Makefile'ı o kurulumu blog-05 ile baştan yapıyor, önceki lab'ı ayrıca kurmanız gerekmiyor.
  • 4 CPU / 8 GB Docker (Colima 0.8, Apple Silicon). ZITADEL ve Postgres, HA Argo CD'nin yanına yaklaşık 320 MB ekliyor (ölçülen working set: ZITADEL 225 MiB, Postgres 97 MiB).
  • kind, kubectl, helm 3.15+, curl, python3.
  • Bu çalıştırmadaki sürümler: argo/argo-cd chart 10.9.2 (Argo CD v3.5.3), zitadel/zitadel chart 10.1.0 (ZITADEL v4.19), postgres:17-alpine.

Neden bu şekilde

Dex değil, doğrudan OIDC. Argo CD'nin chart'ı Dex ile geliyor ve Dex, GitHub, LDAP ya da SAML gibi OIDC konuşmayan kaynakları OIDC'ye çevirmek için var. ZITADEL zaten bir OIDC sağlayıcısı; araya Dex koymak, ayakta tutulacak bir pod ve takip edilecek bir config daha demek. Önceki yazıda dex.enabled: false bu yüzden kapalıydı.

Keycloak değil, ZITADEL. Production'da Keycloak kullandım; CNCF'nin incubating projesi ve olgun. ZITADEL daha yeni, Go ile yazılmış tek bir binary, veritabanı olarak Postgres istiyor ve her şeyi API'den yönetiliyor; bu yazıdaki kurulum scripti de bu yüzden birkaç curl'den ibaret. İki not: ZITADEL bir CNCF projesi değil (CNCF landscape'inde listeli bir ürün) ve v3'ten beri lisansı AGPL-3.0.

ZITADEL'i de Argo CD kuruyor. helm install yok. platform-gitops'a iki Application ekledim ve root onları önceki yazıdaki gibi apps/ dizininden alıyor.

Argo CD giriş sayfası: kullanıcı adı ve parola alanlarının altında "LOG IN VIA ZITADEL" düğmesi

Adım 1 — İki Application: önce veritabanı, sonra ZITADEL

root (Application)
├── platform, products (AppProject, wave -2)
├── argocd (wave -1)
├── applicationsets (wave 0)
├── zitadel-db (wave 1) → zitadel/db: namespace, Secret'lar, Postgres
└── zitadel    (wave 2) → zitadel/zitadel chart 10.1.0 + zitadel/values.yaml

Neden iki ayrı Application: chart'ın init ve setup job'ları Helm pre-install hook'u. Argo CD Helm hook'larını kendi PreSync hook'larına çeviriyor ve bunlar, aynı Application'ın diğer bütün kaynaklarından önce çalışıyor. Postgres ve Secret'lar ZITADEL ile aynı Application'da olsaydı, init job henüz var olmayan bir veritabanına bağlanmaya çalışırdı.

İkinci bir boşluk daha var: root'un sync wave'leri Application kaynaklarının sağlıklı olmasını beklemiyor (Argo CD'de Application için health kontrolü varsayılan olarak kapalı). O yüzden zitadel Application'ında bir retry var:

# platform-gitops/apps/zitadel.yaml (spec)
spec:
  project: platform
  sources:
    - repoURL: https://charts.zitadel.com
      chart: zitadel
      targetRevision: 10.1.0
      helm:
        releaseName: zitadel
        valueFiles:
          - $values/zitadel/values.yaml
    - repoURL: https://github.com/miraccan00/platform-gitops.git
      targetRevision: blog-05
      ref: values
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    retry:
      limit: 10
      backoff:
        duration: 10s
        factor: 2
        maxDuration: 3m

Bu çalıştırmada retry'a gerek kalmadı: zitadel-db 16:11:09'da oluştu, init job 16:11:16'da başladı ve Postgres o sırada hazırdı. Retry, Postgres'in daha yavaş kalktığı bir makine için.

Secret'lar chart'ın içine değil, zitadel-db'nin Secret'larına referansla bağlanıyor. Masterkey için masterkeySecretName, veritabanı bağlantısı için bir env var:

# platform-gitops/zitadel/values.yaml (kısaltılmış)
zitadel:
  masterkeySecretName: zitadel-masterkey
envVarsSecret: zitadel-db          # ZITADEL_DATABASE_POSTGRES_DSN
# platform-gitops/zitadel/db/secrets.yaml (başı)
# ON PURPOSE, the state most teams start from: Secrets in Git, base64 only.
# base64 is encoding, not encryption: anyone who can read this repo can read these values.
make up && make argocd && make bootstrap
NAME                       SYNC STATUS   HEALTH STATUS
applicationsets            Synced        Healthy
argocd                     Synced        Healthy
ns-product-helloapi-dev    Synced        Healthy
ns-product-helloapi-prod   Synced        Healthy
product-helloapi-dev       Synced        Healthy
product-helloapi-prod      Synced        Healthy
root                       Synced        Healthy
zitadel                    Synced        Healthy
zitadel-db                 Synced        Healthy
all 9 Synced/Healthy in 48s

Adım 2 — Tek bir isim, iki farklı dünya

OIDC'de her token bir issuer taşıyor ve Argo CD token'ı ancak issuer, kendi bildiği adresle birebir aynıysa kabul ediyor. Bu adresi iki taraf kullanıyor:

  • Tarayıcı, ZITADEL'in giriş sayfasına gidiyor. Lab'da ZITADEL'e bir port-forward ile, yani 127.0.0.1 üzerinden ulaşılıyor.
  • argocd-server pod'u, aynı adresten ZITADEL'in anahtarlarını ve token endpoint'ini okuyor. Pod için 127.0.0.1 pod'un kendisi.

Çözüm: iki tarafın da aynı ismi kullanması, ama ismin iki tarafta farklı yere çözülmesi.

# platform-gitops/zitadel/values.yaml
zitadel:
  configmapConfig:
    ExternalDomain: zitadel.127.0.0.1.nip.io
    ExternalPort: 8081
    ExternalSecure: false          # lab: plain HTTP behind a port-forward; production terminates TLS
service:
  port: 8081                       # same port inside and outside, so the issuer URL matches

nip.io, içinde IP geçen her ismi o IP'ye çözen genel bir DNS servisi. Tarayıcı için zitadel.127.0.0.1.nip.io = 127.0.0.1 = make zitadel-ui'ın port-forward'u. Pod'lar için CoreDNS'e bir kural ekliyorum:

rewrite name exact zitadel.127.0.0.1.nip.io zitadel.zitadel.svc.cluster.local answer auto
$ make dns
rewrite added: zitadel.127.0.0.1.nip.io -> zitadel.zitadel.svc.cluster.local
issuer seen from a pod: http://zitadel.127.0.0.1.nip.io:8081

Bu kısım tamamen lab tesisatı. Production'da ZITADEL'in gerçek bir DNS adı ve TLS sertifikası oluyor ve kimse CoreDNS'e dokunmuyor. Buraya gelmeden iki kez yanıldım; ikisi de Ne ters gitti'de.

Adım 3 — ZITADEL tarafı: proje, roller, uygulama ve groups claim'i

ZITADEL ilk açılışta bir makine kullanıcısı oluşturuyor ve onun PAT'ini (personal access token) zitadel/iam-admin-pat Secret'ına yazıyor. scripts/zitadel-setup.sh bu token'la ZITADEL'in API'sini çağırıyor:

  1. argocd projesi ve üç rol: platform-admins, product-developers, platform-viewers. Projede "rolleri token'a koy" (projectRoleAssertion) ve "rolü olmayan giremesin" (projectRoleCheck) açık.
  2. Argo CD UI'ı için bir OIDC web uygulaması. Redirect URI http://localhost:8080/auth/callback. devMode: true, yalnızca lab'daki http:// adresi için.
  3. argocd CLI'ı için ikinci, secret'sız (public) bir uygulama: PKCE ve http://localhost:8085/auth/callback. Neden ayrı olduğu Ne ters gitti #5'te.
  4. groupsClaim Action'ı (aşağıda).
  5. alice → platform-admins, dave → product-developers, bob → platform-viewers.
  6. ZITADEL'in ürettiği client ID'leri ve secret'ı Argo CD'nin okuyacağı argocd/argocd-oidc-zitadel Secret'ına yazmak.
$ make setup
project argocd: 393071250492096779
app argocd: client 393071251062587659, secret written to argocd/argocd-oidc-zitadel
app argocd-cli: client 393112607218729227 (public, PKCE), written to argocd/argocd-oidc-zitadel as cliClientID
action groupsClaim: 393071252287258891
user alice (platform-admins): 393071252874461451
user dave (product-developers): 393210437681807618
user bob (platform-viewers): 393071254921281803

Script tekrar çalıştırılabilir; var olan nesneleri adından bulup yeniden kullanıyor.

UI'da: make zitadel-ui açıkken http://zitadel.127.0.0.1.nip.io:8081/ui/console adresine gidin ve [email protected] / Password1! ile girin. Projects → argocd → Roles:

ZITADEL konsolunda argocd projesinin Roles sekmesi: platform-admins, platform-viewers ve product-developers rolleri

Aynı projede Role Assignments, kimin hangi rolü aldığını gösteriyor:

ZITADEL konsolunda Role Assignments: Alice Lab platform-admins, Bob Lab platform-viewers, Dave Lab product-developers

groups claim'i neden ayrı bir iş. ZITADEL rolleri token'a koyuyor, ama groups diye değil. urn:zitadel:iam:org:project:roles adlı bir claim'in içine, rol başına bir nesne olarak koyuyor. Argo CD RBAC ise bir liste bekliyor. Aradaki çeviriyi, Argo CD'nin kendi dokümanındaki yöntemle, bir ZITADEL Action'ı yapıyor:

function groupsClaim(ctx, api) {
  if (ctx.v1.user.grants === undefined || ctx.v1.user.grants.count == 0) { return; }
  let groups = [];
  ctx.v1.user.grants.grants.forEach(g => { g.roles.forEach(r => { groups.push(r); }); });
  api.v1.claims.setClaim("groups", groups);
}

Action, "Complement Token" akışının "Pre Userinfo creation" ve "Pre access token creation" adımlarına bağlı. Actions sayfasında görünüyor:

ZITADEL Actions sayfası: groupsClaim script'i active, üstte Actions V2'nin bu sürümün yerini alacağını söyleyen uyarı

ZITADEL Flows: Complement Token akışında Pre Userinfo creation adımına bağlı groupsClaim

Sayfanın üstündeki uyarıyı atlamayın: bu Actions v1. ZITADEL v4'te çalışıyor ama deprecated ve v5'te kaldırılacak. v2 karşılığı, token oluşturulmadan önce ZITADEL'in çağırdığı harici bir HTTP servisi. Production'da bu servisi yazmak ya da Argo CD'nin ZITADEL'in kendi rol claim'ini okuyabilmesini beklemek gerekecek.

Adım 4 — Argo CD tarafı: oidc.config ve RBAC

Hepsi önceki yazıdaki values dosyasında, helm install'ın ve argocd Application'ının okuduğu yerde. Argo CD'nin ayarları ayrı ConfigMap'lerde duruyor ve argo/argo-cd chart'ı bunları values'tan üretiyor: configs.cm → argocd-cm, configs.rbac → argocd-rbac-cm. Yani SSO'yu da rolleri de kubectl edit ile değil, bu dosyada bir PR ile değiştiriyoruz:

# platform-gitops/argocd/values-ha.yaml (blog-05'te eklenenler)
configs:
  cm:
    url: http://localhost:8080
    oidc.config: |
      name: ZITADEL
      issuer: http://zitadel.127.0.0.1.nip.io:8081
      clientID: $argocd-oidc-zitadel:clientID
      clientSecret: $argocd-oidc-zitadel:clientSecret
      cliClientID: $argocd-oidc-zitadel:cliClientID
      requestedScopes:
        - openid
        - profile
        - email
        - groups
      logoutURL: http://zitadel.127.0.0.1.nip.io:8081/oidc/v1/end_session
  rbac:
    policy.default: ""
    scopes: "[groups]"
    policy.csv: |
      p, role:developer, applications, get,      products/*, allow
      p, role:developer, applications, sync,     products/*, allow
      p, role:developer, applications, action/*, products/*, allow
      p, role:developer, logs,         get,      products/*, allow
      p, role:developer, projects,     get,      products,   allow
      g, platform-admins,    role:admin
      g, product-developers, role:developer
      g, platform-viewers,   role:readonly

helm template ile chart'ın bundan ne ürettiğine bakılabiliyor; policy.csv olduğu gibi argocd-rbac-cm'e gidiyor:

helm template argocd argo/argo-cd --version 10.9.2 -n argocd -f argocd/values-ha.yaml \
  --show-only templates/argocd-configs/argocd-rbac-cm.yaml

Satırların açıklaması:

  • clientID: $argocd-oidc-zitadel:clientID. Client ID'yi ve secret'ı ZITADEL, uygulama oluşturulurken üretiyor; Git'e önceden yazılamıyorlar. $secret:key sözdizimi değeri o Secret'tan okuyor. Argo CD bu referansı sadece app.kubernetes.io/part-of: argocd label'ı taşıyan Secret'larda çözüyor. Önceki yazıda silindiğinde Argo CD'yi düşüren label'ın aynısı; script Secret'ı bu label'la oluşturuyor.
  • cliClientID. Argo CD bu ID'yi CLI'a veriyor; CLI girişte UI'ın uygulamasını değil, secret'sız ikinci uygulamayı kullanıyor.
  • url: http://localhost:8080. Argo CD, ZITADEL'e redirect URI olarak <url>/auth/callback gönderiyor. ZITADEL'deki kayıtla birebir aynı değilse ZITADEL girişi reddeder.
  • g, <grup>, <rol>. <grup>, token'daki groups claim'inin bir elemanı; yani ZITADEL'deki rol anahtarı. role:admin ve role:readonly Argo CD'nin hazır rolleri.
  • p, role:developer, .... Hazır olmayan tek rol bu, p satırlarıyla tanımlanıyor: p, <rol>, <kaynak>, <eylem>, <proje>/<uygulama>, allow. Geliştirici products projesindeki uygulamaları görüyor, sync ediyor, log'larını okuyor ve resource action'larını (örneğin Deployment restart) çalıştırabiliyor. create, update, delete yok: uygulamanın tanımını değiştiremiyor, Git ne diyorsa onu çalıştırıyor. platform projesine (Argo CD'nin kendisi, ZITADEL) hiçbir satır yok, o yüzden o uygulamaları listede bile görmüyor.
  • policy.default: "". Rolü olmayan kimse bir şey göremiyor. Yerel admin kapatılmadı: ZITADEL düştüğünde Argo CD'ye girebilmenin yolu o, parolası da argocd-initial-admin-secret'ta.

Adım 5 — Üç kullanıcı, üç yetki

make zitadel-ui   # ayrı terminal
make ui           # ayrı terminal: http://localhost:8080
make users
# alice / Password1!  -> group platform-admins    -> role:admin
# dave  / Password1!  -> group product-developers -> role:developer
# bob   / Password1!  -> group platform-viewers   -> role:readonly

UI'da: http://localhost:8080 → LOG IN VIA ZITADEL. Tarayıcı ZITADEL'in giriş sayfasına gidiyor; önce kullanıcı adı, sonra parola:

ZITADEL giriş sayfası: kullanıcı adı alanı

ZITADEL giriş sayfası: alice için parola alanı

İlk girişte ZITADEL iki faktörlü doğrulama kurmayı öneriyor. Lab için Skip; production'da bu ekranı zorunlu yapmak istersiniz.

ZITADEL 2-Factor Setup ekranı: Authenticator App ve Device dependent seçenekleri, altta Skip düğmesi

Argo CD'ye geri dönüldüğünde sol menüde User Info, Argo CD'nin token'dan ne okuduğunu gösteriyor. alice için grup platform-admins, issuer da Adım 2'deki adres:

Argo CD User Info: alice@lab.example, issuer http://zitadel.127.0.0.1.nip.io:8081, grup platform-admins

alice dokuz Application'ın hepsini görüyor ve hepsini yönetebiliyor:

Argo CD liste görünümü: alice olarak dokuz Application, zitadel ve zitadel-db dahil, hepsi Synced/Healthy

Çıkış yapıp bob ile girin. Grup platform-viewers:

Argo CD User Info: bob@lab.example, grup platform-viewers

bob da dokuz Application'ı görüyor (role:readonly). product-helloapi-dev → SYNC → SYNCHRONIZE (1):

bob olarak product-helloapi-dev'in Sync paneli, SYNCHRONIZE düğmesi işaretli

Sync reddi: Unable to sync: permission denied: applications, sync, products/product-helloapi-dev

Çıkış yapıp dave ile girin. Grup product-developers:

Argo CD User Info: dave@lab.example, grup product-developers

dave listede sadece iki uygulama görüyor, ikisi de products projesinde. argocd, zitadel, root gibi platform uygulamaları onun için yok:

Argo CD Applications: dave olarak sadece product-helloapi-dev ve product-helloapi-prod, ikisi de products projesinde

product-helloapi-dev'de SYNC → SYNCHRONIZE dave için çalışıyor; bob'u reddeden düğme bu.

Aynı deneme API'den, üç kullanıcı için:

Kullanıcı Grup can-i sync '*/*' can-i sync 'products/*' POST .../product-helloapi-dev/sync POST .../zitadel/sync
alice platform-admins yes yes 200 200
dave product-developers no yes 200 403 permission denied
bob platform-viewers no no 403 permission denied: applications, sync, products/product-helloapi-dev 403

dave'in can-i sync '*/*' cevabı no, çünkü */* platform uygulamalarını da kapsıyor. zitadel için aldığı 403'te bob'unkinden farklı olarak uygulamanın adı yok: dave o uygulamayı göremediği için Argo CD varlığını da söylemiyor. can-i delete applications 'products/*' dave için de no.

Aynısı CLI'dan. make cli tarayıcıda ZITADEL girişini açıyor, giriş bitince terminale dönüyor:

$ make cli
'[email protected]' logged in successfully
$ argocd account get-user-info --port-forward --port-forward-namespace argocd --plaintext
Logged In: true
Username: [email protected]
Issuer: http://zitadel.127.0.0.1.nip.io:8081
Groups: platform-admins
$ argocd account can-i sync applications '*/*' --port-forward --port-forward-namespace argocd --plaintext
yes

dave ile can-i sync applications 'products/*' → yes, '*/*' → no. bob ile aynı komutlar: Groups: platform-viewers, can-i → no, argocd app sync product-helloapi-dev → PermissionDenied: permission denied: applications, sync, products/product-helloapi-dev.

Reddin kaynağı ZITADEL değil, Argo CD. ZITADEL sadece "bu kişi bob, grubu platform-viewers" diyor; ne yapabileceğine policy.csv karar veriyor. Bir kişiye yetki vermek artık Argo CD'de değil, ZITADEL'de rol atamak.

Ne ters gitti

1. zitadel.localhost pod'dan kendine gidiyor. İlk denemede isim olarak zitadel.localhost kullandım. Tarayıcılar ve curl *.localhost'u DNS'e sormadan 127.0.0.1'e çözüyor, o yüzden host tarafı çalıştı: curl http://zitadel.localhost:8081/.well-known/openid-configuration doğru issuer'ı döndü. CoreDNS'e rewrite kuralını ekledikten sonra pod'dan baktım:

$ kubectl -n argocd exec <argocd-server> -- getent hosts zitadel.localhost
::1             zitadel.localhost

Kural hiç devreye girmedi, çünkü .localhost özel bir alan adı (RFC 6761) ve pod'daki çözümleyici de onu DNS'e sormadan loopback'e çözüyor. İsmi nip.io'ya çevirdim.

2. answer auto olmadan rewrite etkisiz. nip.io ile ilk kural rewrite name zitadel.127.0.0.1.nip.io zitadel.zitadel.svc.cluster.local idi. Pod yine 127.0.0.1 gördü. CoreDNS sorgunun adını değiştiriyor ve cevabı yeni adla, zitadel.zitadel.svc.cluster.local olarak dönüyor. İstemci sorduğu adla eşleşmeyen cevabı atıyor ve genel DNS'in 127.0.0.1'ine düşüyor. answer auto cevaptaki adı da geri çeviriyor:

$ kubectl -n argocd exec <argocd-server> -- getent hosts zitadel.127.0.0.1.nip.io
10.96.173.255   zitadel.127.0.0.1.nip.io

3. ZITADEL v4 girişi başka bir uygulamaya gönderiyor. "Log in via ZITADEL"e basınca tarayıcı /ui/v2/login/login?authRequest=V2_... adresine gitti ve şunu gördü:

{"code":5,"message":"Not Found"}

v4'te yeni instance'lar varsayılan olarak Login v2'yi kullanıyor. Login v2 ayrı bir uygulama (zitadel-login, port 3000) ve ZITADEL ile aynı host'ta yol bazlı yönlendirme istiyor, yani bir ingress. Chart onu kuruyor; ben login.enabled: false ile kapatmıştım, çünkü tek port-forward iki servisi aynı adreste birleştiremiyor. Lab'da ZITADEL'in içindeki eski login'i açtım:

zitadel:
  configmapConfig:
    DefaultInstance:
      Features:
        LoginV2:
          Required: false

Bu ayar sadece yeni instance'lara uygulanıyor. Çalışan bir instance'ta aynı şeyi PUT /v2/features/instance ile {"loginV2":{"required":false}} yaptı. Production'da doğrusu Login v2 ve önünde bir ingress ya da Gateway. Actions v1 gibi, bu da lab'ın bilerek kullandığı eski yol.

4. can-i beklediğim soruyu cevaplamıyordu. RBAC'ı API'den kontrol ederken /api/v1/account/can-i/applications/sync/* alice için de no döndü; admin olduğu halde. Uygulama yetkileri proje/uygulama biçiminde, yani * değil */* (/api/v1/account/can-i/applications/sync/*%2F*). Böyle sorunca alice için yes, bob için no.

5. CLI girişi iki ayrı yerden takıldı. argocd login localhost:8080 --sso --plaintext hiçbir şey yazmadan bekledi, sonra gRPC connection not ready: context deadline exceeded ile düştü. Admin parolasıyla girişte de aynısı oldu; yani sorun SSO'da değildi. make ui'ın log'u sebebi gösterdi:

error forwarding port 8080 to pod ...: read: connection reset by peer
error: lost connection to pod

CLI'ın bağlantılarından biri sıfırlanınca kubectl port-forward sadece o bağlantıyı değil, bütün port-forward'u kapatıyor; CLI'ın sonraki isteği kapalı porta düşüyor. Tarayıcı bunu hissetmiyor, çünkü make ui döngüsü port-forward'u bir saniyede yeniden açıyor. (Benim makinemde ayrıca 127.0.0.1:8080'i başka bir süreç tutuyordu ve port-forward sadece [::1]:8080'e bağlanabilmişti; lsof -iTCP:8080 -sTCP:LISTEN ile bakmaya değer.) Çözüm, CLI'ın kendi port-forward'u: argocd login --port-forward --port-forward-namespace argocd. make cli bunu kullanıyor.

Port aşılınca ikinci hata geldi. Giriş sayfası açıldı, parola kabul edildi, dönüşte:

oauth2: "invalid_client" "empty client secret"

CLI bir public client: makinede secret tutamaz, PKCE ile giriyor. Ama Argo CD ona UI'ın uygulamasının ID'sini veriyordu ve o uygulama secret'lı (confidential). Argo CD'nin buna ayrı bir ayarı var, cliClientID: ZITADEL'de auth method'u NONE olan ikinci bir uygulama açıp ID'sini oraya verdim. Sonrasında alice ve bob CLI'dan girdi ve yetkiler UI'dakiyle aynı çıktı.

Doğrulama

make status                                        # 9 Application Synced/Healthy, zitadel ve postgres pod'ları Running
kubectl -n argocd get secret argocd-oidc-zitadel --show-labels   # app.kubernetes.io/part-of=argocd
curl -s http://zitadel.127.0.0.1.nip.io:8081/.well-known/openid-configuration | python3 -m json.tool | grep issuer
make dns                                           # "issuer seen from a pod" aynı adresi yazmalı
make cli                                           # tarayıcıda ZITADEL girişi, sonra:
argocd account can-i sync applications '*/*' --port-forward --port-forward-namespace argocd --plaintext   # alice: yes, dave: no, bob: no
argocd account can-i sync applications 'products/*' --port-forward --port-forward-namespace argocd --plaintext   # dave: yes

UI'da:

  • User Info: issuer http://zitadel.127.0.0.1.nip.io:8081, grup platform-admins, product-developers ya da platform-viewers.
  • dave ile Applications listesi: sadece product-helloapi-dev ve product-helloapi-prod; ikisinde de SYNC çalışıyor.
  • bob ile bir uygulamada SYNC → permission denied.
  • ZITADEL konsolunda Role Assignments: rol değiştirin, çıkış yapıp tekrar girin, User Info'daki grup değişsin.

Kaynaklar: Argo CD dokümanları User Management → Zitadel ve RBAC Configuration, argo/argo-cd chart 10.9.2 values (configs.cm, configs.rbac), ZITADEL dokümanı Migrate from Actions V1 to V2 ve v4.19.3 cmd/defaults.yaml (DefaultInstance.Features.LoginV2), zitadel/zitadel chart 10.1.0 values, CoreDNS rewrite eklentisi.

Bu yazıdan sonra ne değişti

Önce Sonra
Herkes admin Herkes kendi kullanıcısıyla; kimin ne yaptığı görünür
Birini çıkarmak = parolayı herkes için değiştirmek ZITADEL'de kullanıcıyı ya da rolü kaldırmak
Yetki Argo CD'de kişiye bağlı değil Yetki ZITADEL rolünden: platform-admins → admin, product-developers → sadece products projesinde developer, platform-viewers → readonly
— admin break-glass olarak duruyor
Secret'lar Git'te base64 Hâlâ öyle. Sıradaki yazının konusu

Bu seride

Önceki: Argo CD'yi HA Kurup Bozarak Anlamak · Sonraki: Secret'ları Kasaya Taşımak: Git'teki base64'ten Vault ve ESO'ya Kesintisiz Geçiş. Bu yazıda Git'te bıraktığımız Postgres parolasını ve masterkey'i Vault'a taşıyoruz, taşırken değiştiriyoruz ve bunu ZITADEL'i düşürmeden yapıyoruz.

Bu yazının kodu: https://github.com/miraccan00/blog-wiki/tree/main/argocd-sso-zitadel

İnsanın en büyük hatalarından biri de doğru zamanı yanlış kişilerle doldurmaktır.
Charles Bukowski