---
title: "Çok Kümeli GitOps’ta Sıfır Statik Yetki"
description: "Uzun ömürlü kubeconfig olmadan çok kümeli GitOps: SPIFFE kimlikleri, SPIRE'ın bastığı kısa ömürlü SVID'ler — kurma sırası ve her adımın maliyeti."
url: https://sade.dev/tr/systems/cok-kumeli-gitops-sifir-statik-yetki/
lang: tr
author: "Muhammet Şafak"
published: 2026-06-20
updated: 2026-09-06
section: System
tags: ["kubernetes","gitops","spiffe","security","architecture"]
summary: "Diskte duran bir kubeconfig, tüm filonuza nişan almış uzun ömürlü bir kimlik bilgisidir — saldırganın tam da istediği şey. Onu, bir saatte sona eren ve hiç diske yazılmayan bir workload kimliğiyle değiştirin. İşte kural, sıra ve karşılığında üstlendiğiniz yeni kontrol düzlemi."
---

# Çok Kümeli GitOps’ta Sıfır Statik Yetki

> Diskte duran bir kubeconfig, tüm filonuza nişan almış uzun ömürlü bir kimlik bilgisidir — saldırganın tam da istediği şey. Onu, bir saatte sona eren ve hiç diske yazılmayan bir workload kimliğiyle değiştirin. İşte kural, sıra ve karşılığında üstlendiğiniz yeni kontrol düzlemi.

Bir GitOps controller’ı — Argo CD, Flux — bir kümede oturur ve birçoğunu reconcile eder. Diğerlerine ulaşmak için her uzak `kube-apiserver`’a kimlik doğrulaması yapması gerekir. Bunu yapmanın varsayılan yolu bir kimlik bilgisini saklamaktır: kontrol kümesinde bir Kubernetes `Secret` olarak tutulan bir bearer token, bir client sertifikası, bir kubeconfig. Argo CD’de bunlar `argocd.argoproj.io/secret-type: cluster` etiketli nesnelerdir.

Dur ve o `Secret`’ın ne olduğuna bak. O, **genelde geniş yetkili, tüm filonuza nişan almış, tek bir yerde atıl duran, uzun ömürlü bir kimlik bilgisidir.** Kendi kendine rotate olmaz. Yedeklerinizdedir. Kontrol kümenizde bir saldırganın bulabileceği en değerli tek şeydir, çünkü ona sahip olmak, eriştiği her kümeye sahip olmaktır. [Boring architecture](/tr/journal/neden-boring-architecture) disiplininin tamamı, tutmak zorunda olmadığınız yükümlülükleri tutmamakla ilgilidir — ve saklanan filo geneli bir token, sırf kullanım anında taze ve kısa ömürlü bir tane basmak daha fazla iş gibi göründüğü için tuttuğunuz bir yükümlülüktür.

Bu yazı o fazladan işi, sırasıyla yapmakla ilgili. Hedef **sıfır statik yetki**: controller’ı bir uzak kümeye doğrulayan hiçbir uzun ömürlü kimlik bilgisi asla yazılı kalmaz. Controller’ın sunduğu kimlik talep anında basılır, yaklaşık bir saatte sona erer ve yalnızca bellekte yaşar. Mekanizma, kimlik modeli için SPIFFE ve onu basmak için SPIRE’dır.

Her şeyden önce: bu, bir elin parmaklarıyla sayabileceğiniz üç küme için bir araç değil. SPIRE, import ettiğiniz bir kütüphane değil, işlettiğiniz bir kontrol düzlemidir — tam da yavaş üstlenmeniz gereken türden bir yetenek; tıpkı [bölünmeyi hak edene kadar modüler monolitin mikroservisi yendiği gibi](/tr/journal/projelere-neden-moduler-monolit-ile-basliyorum). Ağırlığını ancak gerçekten bir filo işlettiğinizde hak eder — çok küme, çok ekip, çalınan bir `Secret`’ın geçemeyeceği bir denetim gereksinimi. Tek paylaşılan bir küme size yetiyorsa, en ucuz güvenli kimlik bilgisi, basmak için makineyi hiç kurmadığınız olandır. Geri kalanı, filoya gerçekten ulaştığınızı varsayar.

## Tek kuralı baştan söyleyeyim

Mekanizmadan önce netleştireyim: **bir küme sınırını geçen kimlik bilgisi kısa ömürlü olmalı ve asla saklanmamalı.** Aşağıdaki her şey bu tek cümlenin hizmetinde. Sonunda kimlik doğrulama yolunda hâlâ bir yere diske yazılmış uzun ömürlü bir token kalıyorsa, uğruna geldiğiniz özelliği satın almadan karmaşıklık inşa etmişsinizdir — eksik bir index’ten kaçmak için read replica eklemekle aynı yanlış dönüş.

"Kısa ömürlü ve asla saklanmaz"ın burada kesin bir anlamı var. Kimlik, varsayılan ömrü **bir saat** olan bir X.509 sertifikası (ya da varsayılan ömrü beş dakika olan bir JWT); workload tarafından yerel bir socket’ten, **sunacak kendine ait bir token’ı olmadan** çekilir ve ömrünün yarısında otomatik rotate edilir. Yarın çalmaya değecek bir sır yok.

## SPIFFE ve SPIRE gerçekte ne

SPIFFE bir spesifikasyon; SPIRE ise spesifikasyonun tarif ettiği şeyleri basan referans implementasyon. Dört isim tüm tasarımı taşıyor.

Bir **SPIFFE ID** bir URI’dir: `spiffe://<trust-domain>/<path>`, örneğin `spiffe://prod.example.org/ns/argocd/sa/application-controller`. Otorite kısmı **trust domain**’dir — tek bir mantıksal güven kökü, tek bir CA. Path workload’u tanımlar. Bir workload’un elinde tuttuğunu kanıtladığı isim budur.

Bir **SVID** (SPIFFE Verifiable Identity Document) ise kanıttır. İki biçim:

- **X.509-SVID** — SPIFFE ID’si URI SAN’ında (CN’de değil, ki bu sonra önemli olacak) bulunan bir X.509 sertifikası. Onunla mTLS yaparsınız.
- **JWT-SVID** — `sub`’ı SPIFFE ID, `aud`’unu hedef doğrulayıcıya scope’ladığın imzalı bir JWT. Onu bearer token olarak sunarsınız.

Asıl mesele ömürler. SPIRE’ın sunucu konfigürasyonunda varsayılanlar `default_x509_svid_ttl: 1h` ve `default_jwt_svid_ttl: 5m`’dir; bu SVID’ler, kendi `ca_ttl`’i varsayılan `24h` olan bir CA tarafından imzalanır. Bunlar bilerek kısadır: sızdırılmış bir SVID bir saat içinde değersizleşir, ki tam da bu yüzden eskiden token sakladığınız yerde onu kullanmak güvenlidir.

"Asla saklanmaz"ı mümkün kılan şey, bir SPIRE **agent**’ı tarafından yerel bir Unix domain socket üzerinden sunulan **Workload API**’dir (belgelenmiş varsayılan `/tmp/spire-agent/public/api.sock`; çoğu kurulum onu `/run/spire/sockets/...` altında mount eder). Workload o socket’e bağlanır ve SVID’sini ister. **Bunu yapmak için hiçbir kimlik bilgisi sunmaz.** Agent, çağıran process’i kernel üzerinden inceleyerek tanır — PID’sini, oradan da container’ını, namespace’ini, service account’unu — ve bir dizi selector’a karşı *workload attestation* yapar. Kimlik, *hangi sırrı tuttuğuyla* değil, *process’in doğrulanabilir biçimde ne olduğuyla* kurulur. Saklanacak bir şeyin olmamasının tüm nedeni o tersine çevirme.

Altında SPIRE iki bileşen ve iki attestation’dır:

- **spire-server** CA’yı tutar, SVID’leri imzalar ve registration entry’leri saklar. CA’sı varsayılan olarak self-signed, ya da bir `UpstreamAuthority` plugin’i aracılığıyla kurumsal PKI’nıza zincirlenmiş bir intermediate’tir — bir kez verdiğiniz ve birlikte yaşadığınız bir karar.
- **spire-agent** her node’da çalışır. Önce *node’u* sunucuya kanıtlar — **node attestation** — `k8s_psat` gibi bir plugin’le; bu, projeli bir service-account token’ını Kubernetes `TokenReview` API’si üzerinden doğrular. Node’a yerleştirilmiş bir paylaşılan sır yoktur. Sonra her yerel process için, ona bir SVID vermeden önce **workload attestation** yapar.

Bir **registration entry**, bir SPIFFE ID’yi bir parent’a (node/agent) ve bir selector kümesine (namespace, service account, image) bağlayan sunucu tarafı kayıttır. Politika odur: *bu workload, bu şekilde attest edilmiş, bu kimliği alır.* Bu entry’lerin yaşam döngüsünü yönetmek gerçek bir iştir ve süregelen operasyonel maliyetin çoğu oradadır.

## Federasyon: başka bir kümeden gelen bir SVID’ye güvenmek

Bir trust domain, bir güven köküdür. Çok kümeli bir filo genellikle birden fazla trust domain demektir — küme başına ya da bölge başına bir tane — ve artık A domain’indeki bir workload, B domain’inde basılmış bir SVID’yi doğrulayabilmek zorundadır. İşte bu **federasyon**’dur ve birimi **trust bundle**’dır: bir domain’in SVID’lerini doğrulamanızı sağlayan public CA sertifikaları ve JWT imzalama anahtarları (bir JWKS).

Her domain bir **bundle endpoint** ayağa kaldırır — güncel bundle’ını sunan bir URL, OIDC’nin `jwks_uri`’sinin SPIFFE karşılığı — ve her peer güncel kalmak için onu poll eder. İki profil var ve fark, tam da bootstrap sorusudur:

- **`https_web`** — endpoint, public bir CA’dan bir Web-PKI sertifikasıyla cephelenir. Peer onu sıradan public güven deposuyla doğrular, yani **bant dışında hiçbir başlangıç bundle’ının değiş tokuş edilmesine gerek yoktur.** Güven mevcut Web PKI üzerinden bootstrap olur.
- **`https_spiffe`** — endpoint kendi X.509-SVID’siyle kimlik doğrular. Onunla ilk kez konuşmak için peer’in o domain’in başlangıç bundle’ını zaten elinde tutması gerekir. Yani **ilk bundle bant dışında gelmek zorundadır**, ondan sonra ileriye dönük olarak en son çekilen bundle kullanılır.

Bir registration entry üzerinde `federatesWith`, bir workload’un kimlik doğrulamasına izin verilen yabancı trust domain’leri listeler; agent sonra o yabancı bundle’ları workload’a Workload API üzerinden teslim eder. Bundle’lar `spiffe_refresh_hint` aralığında (genelde beş dakika civarı) yenilenir ve yeni bir imzalama anahtarını, kullanmaya başlamadan birkaç yenileme döngüsü *önce* yayınlarsınız ki rotation indiğinde federe peer’ler onu çoktan öğrenmiş olsun.

"Sıfır statik yetki"nin dürüst şekline dikkat: federasyon, bootstrap güven kararının **yerini değiştirir**, onu silmez. `https_spiffe` ile bir başlangıç bundle’ını elle gönderirsiniz; `https_web` ile public CA sistemine yaslanırsınız. Her iki yolda da güven kökü hâlâ *bir yerden* gelir — ortadan kaldırdığınız şey, uzun ömürlü, geniş kapsamlı, küme başına kimlik bilgisidir; tek seferlik güven çıpası değil. Bu doğru takastır, ama olduğu gibi adlandırın.

## apiserver’a bir SVID’yi kabul ettirmek

Niyetin Kubernetes API’siyle buluştuğu ve yanılmanın kolay olduğu yer burası. İki mekanizma var ve bugün yalnızca biri temiz.

**JWT-SVID-olarak-OIDC yolu (işleyen).** Kubernetes’e harici bir OIDC issuer’a güvenmesi söylenebilir. SPIRE, `/.well-known/openid-configuration` ve SPIRE’ın JWT-SVID imzalama anahtarlarıyla beslenen bir JWKS sunan bir **OIDC Discovery Provider** ile gelir. Uzak (spoke) kümenin apiserver’ını o provider’a bir OIDC issuer olarak yöneltirsiniz. Controller, yerel Workload API’sinden taze bir JWT-SVID çeker ve onu bir bearer token olarak sunar; apiserver onu SPIRE’ın JWKS’ine karşı doğrular ve bir subject’e map eder. `Secret` yok, saklanan token yok — istek başına yeni bir JWT, dakikalar içinde sona eren.

Bu bir düşünce deneyi değil. Red Hat, SPIFFE/SPIRE desteğini OpenShift 4.20’den itibaren Zero Trust Workload Identity Manager operatörü olarak sevk eder ve tam olarak bu bağlantıyı, OpenShift GitOps’un sevk ettiği bir ürün özelliği olarak değil, bir nasıl-yapılır rehberi olarak belgeler: Argo CD, `execProviderConfig` aracılığıyla bir client-go `ExecCredential` plugin’i (`apiVersion: client.authentication.k8s.io/v1beta1`) kullanır; bu plugin her çağrıda SPIFFE socket’ini (`SPIFFE_ENDPOINT_SOCKET`) okur, spoke apiserver’ın beklediği audience’la (`SPIFFE_JWT_AUDIENCE`) bir JWT-SVID ister ve onu teslim eder. Kimlik bilgisi kullanım anında imal edilir ve atılır.

**X.509-SVID-ile-mTLS yolu (tuzak).** İçgüdü düz mTLS yapmaktır: controller X.509-SVID’sini sunar, apiserver SPIFFE bundle’ına client CA olarak güvenir, bitti. Bitmedi. Kubernetes client-cert kimlik doğrulaması **kullanıcı adını sertifikanın Subject CN’inden, grupları Subject O’sundan** türetir — ve SPIFFE ID **URI SAN**’ında yaşar, ki apiserver client-cert kimlik doğrulaması onu okumaz. Bu yüzden ham bir SPIFFE X.509-SVID bir Kubernetes kullanıcısına map olmaz. mTLS kullanmak için apiserver’ın önüne bir proxy — Ghostunnel ya da Envoy — koyarsınız: SPIFFE mTLS’i terminate eder, URI-SAN SPIFFE ID’sini doğrular ve apiserver’ın anladığı bir kimlikle iletir. Ghostunnel kendi rotate olan SVID’leri için Workload API’yi doğrudan tüketir. Bu işler, ama her apiserver’ın önünde bir hareketli parçadır ve OIDC yolu onu hiç gerektirmez.

Olgunluk konusunda dürüst olun. Upstream Argo CD’nin native SPIFFE kimlik doğrulaması yoktur; jenerik `ExecCredential` plugin örüntüsüyle çalışır ve en olgun ürünleşme OpenShift’inkidir. Flux, SPIRE’ı *deploy etmek* için GitOps ile yönetilebilir ve OCI registry kimlik doğrulaması bir JWT-SVID tüketebilir, ama "Flux’u bir uzak kümeye kubeconfig yerine SPIFFE ile doğrula" şeklinde birinci sınıf bir özellik belgelenmemiştir — aynı OIDC/exec-credential/proxy tesisatının üzerinden gider. Native uzak küme SPIFFE kimlik doğrulamasını, etkinleştirdiğiniz bir kutu değil, monte ettiğiniz bir örüntü olarak görün.

## Ne kırılır ve maliyeti ne

Mimari diyagramların atladığı kısım bu. Saklanan bir kimlik bilgisini kaldırdınız; karşılığında bir kimlik servisini kritik yolda sıkı bir bağımlılık hâline getirdiniz ve kısa ömürlü şeyler, uzun ömürlülerin kırılmadığı yerlerde kırılır.

- **SVID rotation artık bir erişilebilirlik bağımlılığı.** SVID’ler kısadır — X.509 bir saat, JWT beş dakika — ve ömürlerinin yarısında rotate olur. Agent ya da Workload API ayakta değilse, ya da agent yenilemek için sunucuya ulaşamıyorsa, **SVID sona erer ve kimlik doğrulama durur.** SPIRE sunucusu her basım ve yenileme için kritik yoldadır. Agent’lar kimlik bilgilerini cache’ler ve *kısa* sunucu kesintilerine tolerans gösterir; agent’ın `availability_target` ayarı (ayarlanırsa ≥ 24h olmalı) onu erken rotate ettirerek nazik bir kesinti için pay biriktirir. Ama hata modu gerçek ve yeni: kimlik düzlemi ayakta değilse filo kimlik doğrulaması da ayakta değildir.
- **Saat kayması (clock skew) artık bir kesinti sınıfı.** Beş dakikalık bir JWT affetmez. Bir controller ile bir spoke apiserver arasında birkaç dakikalık kayma, geçerli token’ları reddeder. Filo boyunca NTP disiplini, hijyen olmaktan çıkıp sıkı bir gereksinim olur.
- **Kararan bir federasyon bundle endpoint’i kümeler arası kimlik doğrulamasını kırar — sessizce, sonradan.** Bir peer’in bundle endpoint’i yenileme penceresini aşacak kadar ulaşılamazsa ve o domain anahtarlarını rotate ederse, cache’lenmiş bundle’ınız bayatlar ve **domain’ler arası SVID doğrulaması başarısız olur**, her iki küme tek başına sağlıklı olsa bile. Hata, kesinti anında değil rotation anında ortaya çıkar, ki bu onu teşhis etmesi kötü kılar.
- **Kimlik düzlemi artık stateful, HA-kritik bir servis.** HA’daki SPIRE sunucuları tek bir SQL datastore paylaşır; varsayılan SQLite tek-node’dur, yani production demek, *tüm filonun kimlik doğrulayabilme yeteneğinin* bağlı olduğu yüksek erişilebilir bir MySQL/PostgreSQL demektir. Çok küme için bir topoloji seçersiniz — nested SPIRE (bir root sunucunun downstream sunuculara intermediate basması, bir root kesintisinden sağ çıkması) ya da küme başına trust domain’ler arası federasyon — ve her biri kendi operasyonel yüzeyini ekler.
- **Operasyonel ağırlık baştan aşağı yeni bir kontrol düzlemi.** Her kümede sunucu artı agent’lar; registration-entry yaşam döngüsü (workload başına selector’lar, deployment’larla senkron tutulan); upstream-CA kararı; bundle endpoint’ler ve federasyon ilişkileri; OIDC Discovery Provider; ve mTLS yolunda apiserver başına bir proxy. Cevap "bir kubeconfig sakla" olduğunda bunların hiçbiri yoktu.

## Sıra neden önemli?

Adımlar bir menü değil; bir dizidir ve onu atlamak, ekiplerin maliyeti faydası olmadan nasıl aldığıdır.

```
1. SPIRE’ı kur: sunucu + agent’lar, node attestation        (temel)
2. Controller’a Workload API üzerinden workload SVID’leri bas
3. Spoke apiserver’a SPIRE’a güvendir (önce OIDC yolu)
4. Kümeler boyunca trust domain’leri federe et
5. Saklanan kubeconfig Secret’larını sil                     (kazanç)
```

5. adım tüm mesele ve yalnızca 1–4 gerçekten işlediğinde güvenli. Yaygın hata, eski `Secret`’ı "yedek olarak" tutmaktır — bu da uzun ömürlü filo geneli kimlik bilgisinin hâlâ atıl, hâlâ yedeklerinizde, hâlâ saldırganın aldığı şey olması ve artık ayrıca SPIRE çalıştırıyor olmanız demektir. Kontrol düzleminin parasını ödediniz ve yükümlülüğü tuttunuz. Ya saklanan kimlik bilgisi yok olur ya da bunu yapmadınız; yarım kredi yoktur.

Tersi hata, sıra fayda verecek hâle gelmeden SPIRE’a uzanmaktır — `Secret`’ı gayet iyi olacak birkaç küme için federe bir kimlik düzlemi ayağa kaldırmak. Bu, güvenlik rozeti takmış [ileride-lazım-olur karmaşıklığıdır](/tr/journal/ileride-lazim-olur-kodunun-faturasi): henüz sahip olmadığınız bir patlama yarıçapı için kurulmuş makine. Makineyi haklı çıkaran şey filo, denetim gereksinimi ve çalınan bir token’ın patlama yarıçapıdır. O çizginin altında, en ucuz güvenli kimlik bilgisi gerçekten de hiç basmak zorunda olmadığınız sıkıcı olanıdır.

Bu yaklaşımın gerçekten bittiği yer açık: statik bir küme başına kimlik bilgisinin kabul edilemez bir patlama yarıçapı olduğu kadar büyük bir filo, stateful, HA, kritik yolda bir kimlik servisini çalıştıracak ve saatlerini senkron tutacak operasyonel olgunlukla. Oraya varmış bir ekip o olgunluğun parasını zaten başka yerde öder — ve o noktada sıfır statik yetki bir süsleme değil, patlama yarıçapının baştan beri talep ettiği kimlik bilgisi modelidir. Daha önce değil.

---

*İlgili:* [SPIFFE](https://spiffe.io/docs/latest/spiffe-about/overview/) ve [SPIRE](https://spiffe.io/docs/latest/spire-about/) belgeleri kimlik modelini ve sunucu/agent bileşenlerini tanımlar; [SPIFFE Federation spec’i](https://spiffe.io/docs/latest/spiffe-specs/spiffe_federation/) ise bu tasarımın dayandığı bundle-endpoint profillerini belirtir.

## Sık sorulanlar

**apiserver bir SPIFFE X.509-SVID'sine neden doğrudan güvenemiyor?**

Kubernetes client-cert kimlik doğrulaması kullanıcı adını sertifikanın Subject CN'inden, grupları Organization alanlarından türetir. SPIFFE ID ise URI SAN'da yaşar ve o yol URI SAN'ı okumaz; dolayısıyla ham bir X.509-SVID hiçbir Kubernetes kullanıcısına eşlenmez. Ya JWT-SVID-olarak-OIDC yolunu seçersiniz ya da öne kimliği çeviren bir proxy koyarsınız.

**Bu kimlik bilgileri tam olarak ne kadar kısa ömürlü?**

SPIRE sunucu varsayılanları X.509-SVID için bir saat, JWT-SVID için beş dakika; imzalayan CA'nın kendi TTL varsayılanı 24 saat ve rotation ömrün yarısında oluyor. Sızdırılmış bir SVID'yi bir saat içinde değersiz kılacak kadar kısa — ve kümeler arası saat kaymasını bir kesinti sınıfına dönüştürecek kadar da kısa.

**Sıfır statik yetki, bootstrap güvenini tamamen ortadan kaldırır mı?**

Hayır, yerini değiştirir. https_spiffe bundle endpoint profilinde bir başlangıç bundle'ını hâlâ bant dışında iletirsiniz; https_web'de public CA sistemine yaslanırsınız. Ortadan kalkan şey uzun ömürlü, geniş kapsamlı, küme başına kimlik bilgisidir — tek seferlik güven çıpası değil.


## Kaynaklar

- [SPIRE server configuration: default_x509_svid_ttl 1h, default_jwt_svid_ttl 5m, ca_ttl 24h](https://spiffe.io/docs/latest/deploying/spire_server/) — SPIFFE
- [SPIFFE Federation: the https_web and https_spiffe bundle endpoint profiles](https://spiffe.io/docs/latest/spiffe-specs/spiffe_federation/) — SPIFFE
- [Kubernetes authentication: X509 client certificates take the username from the Subject CN and groups from the Organization fields](https://kubernetes.io/docs/reference/access-authn-authz/authentication/) — Kubernetes
- [Argo CD declarative setup: remote clusters are stored as Secrets labelled argocd.argoproj.io/secret-type: cluster](https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/) — Argo CD
