İçeriğe geç
NetDevOps
16 dk okuma Güvenlik

HashiCorp Vault ile Kubernetes Secret Management

Modern mikroservis mimarilerinde bir gerçek, yazılım dünyasının en yaygın ve en az dikkat edilen güvenlik açıklarından birini oluşturuyor: sırların yönetimi. API anahtarları, veritabanı kimlik bilgileri, sertifikalar ve token'lar — bunların hepsi uygulamaların çalışması için zorunlu, ancak yanlış ellere geçtiklerinde yıkıcı sonuçlar doğurabilecek hassas veriler. 2025 Verizon DBIR raporuna göre, tüm veri ihlallerinin yüzde 30'u stolen credentials'dan kaynaklanıyor. Bu istatistiği bir adım öteye taşırsak: credential'ların nasıl saklandığı, dağıtıldığı ve döndürüldüğü, ihlal ile güvenlik arasındaki temel farkı belirliyor.

HashiCorp Vault, bu soruna kapsamlı bir çözüm sunan açık kaynaklı bir secrets management aracıdır. Tek bir merkezi sistem üzerinden API anahtarlarını, veritabanı parolalarını, sertifikaları ve diğer hassas verileri depolar, erişim kontrolü uygular, her erişimi denetler ve kritik olarak, sırları otomatik olarak döndürür. Ancak Vault'un gerçek gücü, Kubernetes ortamlarıyla entegrasyonunda ortaya çıkar. Bu yazıda, Vault Agent Injector ile Kubernetes pod'larına sırların nasıl güvenli biçimde enjekte edildiğini, dinamik credential üretimi ile sıfır güven mimarisinin nasıl inşa edildiğini ve production ortamında dikkat edilmesi gereken kritik konuları derinlemesine inceliyoruz.

Neden Kubernetes Secrets Yeterli Değil?

Kubernetes, native bir "Secret" kaynağı sunmasına rağmen, bu kaynağın varsayılan davranışı birçok güvenlik açığı barındırır. Kubernetes Secret nesneleri, temel değerleri yalnızca base64 ile kodlar — şifreleme değildir. Bu kodlanmış veriler, etcd veritabanında düz metin olarak saklanır ve etcd'ye erişimi olan herkes bu sırları açıkça görebilir. RBAC ile erişim kontrolü yapılabilir, ancak bir Secret bir kez bir pod'a mount edildiğinde, o pod'un çalıştığı node'daki dosya sisteminde düz metin olarak görünür kalır. Kubeconfig veya etcd erişimi olan bir saldırgan, bu yollarla tüm sırlara erişebilir.

Bunun ötesinde, Kubernetes Secrets statik yapılardır. Bir veritabanı parolası değiştiğinde, Secret güncellenir, pod'lar yeniden başlatılır veya env değişkenleri yeniden çekilir. Ancak uygulamanın bağlantı havuzu zaten açık bağlantılarla çalışıyorsa, yeni credential'lar anında uygulanamaz ve geçici bir kesinti yaşanır. Vault'un dinamik secrets yaklaşımı, bu sorunların tamamını radikal biçimde çözer.

Vault Mimarisi: Storage, Sealer ve İşlevsel Katmanlar

Vault'un üç temel bileşeni, güvenliğin temel taşlarını oluşturur. Storage backend, sırların fiziksel olarak nerede saklandığını belirler. Development ortamında inmem (bellek içi) yeterli olsa da production ortamında Consul, Raft (yerleşik) veya GCS, S3 gibi bulut depolama çözümleri tercih edilir. Raft backend, Vault'un 1.4 sürümünden itibaren yerleşik olarak sunulur ve tek bir sunucudan beş node'luk bir cluster'a kesintisiz geçiş yapılabilmesiyle operasyonel karmaşıklığı önemli ölçüde azaltır.

Sealer mekanizması, Vault'un şifreleme anahtarlarını yönetir. Production ortamında Vault başlatıldığında, operatörün "unseal" işlemi yapması gerekir — bu, şifrelenmiş verilerin okunabilir hale gelmesi için gereken anahtarların sağlanmasıdır. Shamir's Secret Sharing algoritması ile 5 parçaya bölünmüş anahtarlar farklı kişilere dağıtılır ve belirlenen eşik sayısına ulaşıldığında (genellikle 3/5) Vault açılır. Bu yapı, tek bir kişinin tüm verilere erişememesini garanti altına alır.

İşlevsel katman ise "secrets engine" olarak adlandırılır. Vault, farklı kullanım senaryoları için optimize edilmiş birçok secrets engine sunar: Key-Value (statik key-value depolama), Database (dinamik veritabanı credential üretimi), PKI (sertifika üretimi ve yönetimi), SSH (anlık SSH anahtarları), Transit (veri şifreleme as a service) ve AWS IAM gibi bulut entegrasyonları. Her engine, kendi erişim politikaları ve TTL mekanizmasıyla çalışır.

Kubernetes Auth Method: Service Account ile Güven Köprüsü

Vault'un Kubernetes ile entegrasyonunun kalbi, Kubernetes Authentication Method'dur. Bu yöntem, Kubernetes Service Account token'larını kullanarak Vault'a kimlik doğrulaması yapar. Mimari akış şu şekilde işler: Bir pod, Kubernetes API'den kendi Service Account'unun JWT token'ını alır. Bu token, Kubernetes'in TokenRequest API'si tarafından imzalanmış ve pod'a özgü bir metadata taşıyan güvenli bir belgedir. Pod (veya Vault Agent), bu JWT'yi Vault'a gönderir. Vault, Kubernetes API'ye bağlanarak token'ın geçerliliğini doğrular — pod'un hangi Service Account'a ait olduğunu, namespace'ini ve mevcut olup olmadığını kontrol eder. Doğrulama başarılıysa, Vault ilgili policy ile ilişkilendirilmiş bir client token üretir.

Bu sürecin en kritik özelliği, "Secret Zero" problemini çözmesidir. Geleneksel sistemlerde, Vault'a ilk erişim için bir başlangıç credential'ı gerekir — bu credential'ın güvenli biçimde dağıtılması kendi başına bir güvenlik sorunudur. Kubernetes Auth Method ile bu sorun ortadan kalkar: zaten Kubernetes'in yönettiği Service Account kimlikleri, Vault ile güven köprüsü kurmak için kullanılır. Uygulama kodu değişmez, başka bir credential yönetilmez.

Kubernetes Auth Method'u etkinleştirmek ve bir role tanımlamak için gereken Vault komutları şunlardır:

vault auth enable kubernetes

vault write auth/kubernetes/config \
    token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
    kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

vault write auth/kubernetes/role/app \
    bound_service_account_names=app-sa \
    bound_service_account_namespaces=production \
    policies=app-policy \
    ttl=24h

Bu yapılandırmada, yalnızca production namespace'indeki app-sa Service Account'u bu role ile Vault'a erişebilir. Başka bir namespace veya Service Account denediğinde, Vault reddeder. Bu, erişim kontrolünü Kubernetes'in kendi kimlik katmanına entegre eder.

Vault Agent Sidecar Injector: Pod'lara Sırları Enjekte Etmek

Vault Agent Sidecar Injector, Kubernetes'in Mutating Admission Webhook mekanizmasını kullanarak çalışan bir Kubernetes addon'udur. Vault Helm chart ile kurulur ve Kubernetes API'yi dinler. Bir pod oluşturulurken veya güncellenirken, webhook pod'un annotation'larını kontrol eder. Belirli Vault annotation'larını tespit ederse, pod spec'i mutate ederek Vault Agent container'larını ekler.

İki tür container enjekte edilir: Init container ve Sidecar container. Init container, uygulama container'ı başlamadan önce çalışır. Vault ile kimlik doğrulaması yapar, ilk sırları alır ve bunları pod'un paylaşılan memory volume'üne (/vault/secrets/) yazar. Uygulama container'ı başladığında, sırlar zaten hazır ve dosya sisteminde görünür durumdadır. Sidecar container ise uzun ömürlü bir süreç olarak çalışmaya devam eder. Vault token'ını yeniler, secret'ların Time-To-Live (TTL) süresini izler ve süre dolmadan yeni sırlar alarak paylaşılan dosyayı gerçek zamanlı günceller.

Bu mimarinin en büyük avantajı, uygulama kodunun Vault'tan habersiz kalmasıdır. Uygulama sırları bir dosyadan okur — bu dosyanın kaynağı Kubernetes Secret, ConfigMap veya Vault olabilir. Vault Agent Injector, bu detayı uygulamadan soyutlar ve sırları dosya sisteminde sunar. Uygulama kodu değişmez, Vault SDK'sı entegre edilmez, sadece bir dosya okunur.

Production-grade bir Helm kurulumu için gerekli yapılandırma:

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

helm install vault hashicorp/vault \
    --namespace vault \
    --create-namespace \
    --set "injector.enabled=true" \
    --set "injector.replicas=2" \
    --set "server.dev.enabled=false" \
    --set "server.dataStorage.enabled=true" \
    --set "server.auditStorage.enabled=true"

Production ortamında server.dev.enabled=false kesinlikle kullanılmalıdır; dev modunda Vault tüm verileri bellekte tutar ve kalıcılık sağlamaz, ayrıca unseal koruması yoktur.

Annotations ile Sır Enjeksiyonu Yapılandırma

Vault Agent Injector, pod annotation'ları üzerinden yapılandırılır. Her annotation vault.hashicorp.com/ ön eki ile başlar ve farklı davranışları kontrol eder. Temel bir veritabanı credential enjeksiyonu için gerekli annotation'lar:

apiVersion: v1
kind: Pod
metadata:
  name: postgres-app
  namespace: production
  annotations:
    # Injector'ı etkinleştir
    vault.hashicorp.com/agent-inject: "true"
    
    # Vault role'u
    vault.hashicorp.com/role: "app"
    
    # Kubernetes Auth Method kullan
    vault.hashicorp.com/auth-method: "kubernetes"
    
    # Hangi secret'ı çek
    vault.hashicorp.com/agent-inject-secret-db: "database/creds/app-role"
    
    # Secret'ın hangi dosyaya yazılacağı (varsayılan: /vault/secrets/db)
    vault.hashicorp.com/agent-inject-file-db: "db-creds"
    
    # Template ile formatla
    vault.hashicorp.com/agent-inject-template-db: |
      {{- with secret "database/creds/app-role" -}}
      username: {{ .Data.username }}
      password: {{ .Data.password }}
      host: postgres.database.svc.cluster.local
      port: 5432
      lease_duration: {{ .LeaseDuration }}
      {{- end -}}
    
    # Init container çalışsın, sidecar çalışsın (varsayılan)
    vault.hashicorp.com/agent-pre-populate-only: "false"
    
    # Pod shutdown olduğunda lease'i revoke et
    vault.hashicorp.com/agent-revoke-on-shutdown: "true"
    
    # Grace period: lease süresi dolmadan ne kadar önce revoke et
    vault.hashicorp.com/agent-revoke-grace: "5s"

spec:
  serviceAccountName: app-sa
  containers:
    - name: app
      image: myapp:latest
      volumeMounts:
        - name: vault-secret
          mountPath: /vault/secrets
          readOnly: true
  volumes:
    - name: vault-secret
      emptyDir:
        medium: Memory

Uygulama bu pod'da çalıştığında, /vault/secrets/db-creds dosyası içeriği şöyle görünür:

``

username: vault-token-abc123-xyz789

password: A1b2C3d4E5f6G7h8I9j0

host: postgres.database.svc.cluster.local

port: 5432

lease_duration: 3600

`

Çoklu Sır Enjeksiyonu: Birden Fazla Kaynak

Bir pod'un birden fazla sır kaynağına ihtiyacı olabilir. Bir mikroservis, veritabanı credential'ları, Redis bağlantı bilgileri, bir üçüncü taraf API anahtarı ve TLS sertifikası için dört farklı kaynaktan sır çekebilir. Vault Agent Injector, her sır için ayrı bir annotation satırı tanımlamayı destekler:

annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "app"
    
    # Veritabanı credential
    vault.hashicorp.com/agent-inject-secret-db: "database/creds/app-role"
    vault.hashicorp.com/agent-inject-template-db: |
      {{- with secret "database/creds/app-role" -}}
      DB_USER={{ .Data.username }}
      DB_PASS={{ .Data.password }}
      {{- end -}}
    
    # Redis credential
    vault.hashicorp.com/agent-inject-secret-redis: "redis/creds/app-role"
    vault.hashicorp.com/agent-inject-template-redis: |
      {{- with secret "redis/creds/app-role" -}}
      REDIS_PASS={{ .Data.password }}
      {{- end -}}
    
    # Statik API key (KV engine)
    vault.hashicorp.com/agent-inject-secret-api: "secret/data/app/api-keys"
    vault.hashicorp.com/agent-inject-template-api: |
      {{- with secret "secret/data/app/api-keys" -}}
      {{- range $k, $v := .Data.data -}}
      {{ $k }}={{ $v }}
      {{- end -}}
      {{- end -}}
    
    # TLS sertifikası
    vault.hashicorp.com/agent-inject-secret-tls: "pki/issue/app-tls"
    vault.hashicorp.com/agent-inject-template-tls: |
      {{- with secret "pki/issue/app-tls" -}}
      {{ .Data.certificate }}
      {{ .Data.private_key }}
      {{ .Data.issuing_ca }}
      {{- end -}}
    vault.hashicorp.com/agent-inject-file-tls: "tls.crt"
    vault.hashicorp.com/agent-inject-file-tls-cert: "tls.crt"
    vault.hashicorp.com/agent-inject-file-tls-key: "tls.key"

Bu yapılandırma ile uygulama, dört farklı secret kaynağından gelen verileri /vault/secrets/ dizini altında ayrı dosyalarda bulur. Uygulama kodu değişmez; sadece ilgili dosyaları okur.

Dinamik Veritabanı Credential Üretimi

Vault'un dinamik secrets yeteneklerinin en etkileyici kullanım alanı, veritabanı credential üretimidir. Geleneksel yaklaşımda, bir uygulama başlatıldığında sabit bir veritabanı kullanıcı adı ve şifresi ile bağlanır. Bu şifre ne kadar güçlü olursa olsun, bir kez sızdırıldığında süresiz olarak geçerli kalır. Vault'un dinamik yaklaşımında ise her pod, her CI/CD pipeline run'ı veya her API isteği için benzersiz, kısa ömürlü credential'lar alır.

Dinamik veritabanı credential üretimi için önce Vault'ta database secrets engine'in etkinleştirilmesi ve veritabanı bağlantısının yapılandırılması gerekir:

vault secrets enable database

vault write database/config/postgres \
    plugin_name=postgresql-database-plugin \
    connection_url="postgresql://vaultadmin:{{VAULT_ADMIN_PASSWORD}}@postgres:5432/?sslmode=disable" \
    allowed_roles=app-readonly \
    username="vaultadmin" \
    password="{{VAULT_ADMIN_PASSWORD}}"

vault write database/roles/app-readonly \
    db_name=postgres \
    creation_statements="
      CREATE ROLE '{{name}}' WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
      GRANT CONNECT ON DATABASE myapp TO '{{name}}';
      GRANT USAGE ON SCHEMA public TO '{{name}}';
      GRANT SELECT ON ALL TABLES IN SCHEMA public TO '{{name}}';
      ALTER ROLE '{{name}}' SET search_path TO public;
    " \
    default_ttl="1h" \
    max_ttl="24h"

Bu yapılandırmada, her dinamik credential talep edildiğinde Vault, PostgreSQL'e bağlanarak benzersiz bir kullanıcı oluşturur, geçici bir şifre atar ve role atar. Bu credential'ın ömrü 1 saat ile sınırlıdır. 1 saat sonra Vault, PostgreSQL'den bu kullanıcıyı otomatik olarak siler. Artık o credential geçersizdir; çalınmış olsa bile kullanılamaz.

Pod'lar bu credential'ları Vault Agent aracılığıyla çeker. Pod çalıştığı sürece, sidecar token'ı yeniler ve lease'i uzatır. Pod silindiğinde, agent-revoke-on-shutdown: "true" annotation'ı sayesinde Vault, kullanıcıyı hemen revoke eder. Bu, credential'ın artık kullanılamayacağı anlamına gelir — pod öldüğünde, onun credential'ı da ölmüş olur.

Token Renewal ve Bağlantı Havuzu Yönetimi

Dinamik credential'ların otomatik döndürülmesi mükemmel bir güvenlik özelliği olsa da, uygulama tarafında dikkat edilmesi gereken bir husus vardır: bağlantı havuzu yönetimi. Bir uygulama, veritabanına bir bağlantı açtığında, bu bağlantı credential'ların o anki değerlerini taşır. Credential döndüğünde, mevcut bağlantılar geçersiz hale gelmez — çünkü PostgreSQL, authentication bilgisini bağlantı kurulurken alır ve sonraki her sorguda yeniden doğrulamaz. Ancak yeni bağlantılar kurulurken artık eski credential geçersizdir.

Bu sorunu çözmek için iki strateji kullanılır. Birincisi, Vault Agent'ın agent-inject-command annotation'ı ile credential değişikliğinde uygulamaya SIGHUP sinyali gönderilmesidir. Bu sinyal, uygulamaya bağlantı havuzunu yeniden kurması gerektiğini bildirir:

vault.hashicorp.com/agent-inject-command-db: |
  pgrep -f my-app-process | xargs kill -HUP || true

İkincisi ve daha güvenilir olanı, uygulamanın bağlantı havuzunun max-lifetime parametresini Vault TTL'sinden kısa tutmaktır. Eğer Vault TTL'si 60 dakika ise, uygulamanın bağlantı havuzu max-lifetime'ı 45 dakika olmalıdır. Bu, uygulamanın credential'lar geçerliliğini yitirmeden önce bağlantıları proaktif olarak yenilemesini sağlar:

HikariCP (Java/Kotlin/Spring) örneği


spring:
datasource:
hikari:
max-lifetime: 2700000 # 45 dakika (milisaniye)


Audit Logging: Kim, Ne zaman, Nereye Erişti?

Vault'un audit sistemi, secrets erişiminin tam izlenebilirliğini sağlar. Her okuma, yazma, kimlik doğrulama ve hata, audit log'larına yazılır. Production ortamında audit log'ların en az iki farklı backend'e yazılması önerilir: biri yerel dosya sistemi (hızlı erişim için), diğeri syslog veya Fluentd gibi merkezi log toplama sistemi (uzun süreli saklama ve analiz için). Syslog backend'i, log'ları sistem log daemon'una gönderir; buradan Elasticsearch, Splunk veya Grafana Loki gibi araçlarla merkezi olarak izlenebilir.

vault audit enable file file_path=/vault/audit/audit.log
vault audit enable syslog tag= vault-audit

Audit log'ları, güvenlik olaylarını tespit etmek için hayati öneme sahiptir. Bir API anahtarına beklenmedik bir saatte veya beklenmedik bir IP'den erişim, potansiyel bir ihlal göstergesidir. Vault'un audit log'ları, bu anomalilerin tespitini ve forensic analizi mümkün kılar.

Production Checklist: Vault ve Kubernetes Entegrasyonunda Dikkat Edilecekler

Vault'u Kubernetes ortamında production olarak çalıştırırken göz ardı edilmemesi gereken birkaç kritik nokta vardır. Bunların herhangi biri eksik bırakıldığında, güvenlik mimarisinde ciddi boşluklar oluşabilir.

İlk olarak, Vault server'ın kendisi Kubernetes dışında çalıştırılmalıdır. Vault'u bir pod olarak Kubernetes içinde çalıştırmak, pod ömrü boyunca verilerin kaybedilmesi riskini taşır. External SDS (Storage) backend olarak Consul, Raft veya bulut tabanlı çözümler kullanılmalı ve Vault server'lar ayrı VM'lerde veya farklı bir Kubernetes cluster'ında barındırılmalıdır. Bu, Vault'un yüksek erişilebilirliğini ve veri kalıcılığını garanti altına alır.

İkinci olarak, Network Policies ile Vault erişimi sınırlandırılmalıdır. Production namespace'indeki pod'lar dışındaki namespace'lerden Vault API'sine erişim engellenmelidir. Yalnızca yetkili namespace'lerdeki Service Account'lar, Vault Agent Injector üzerinden sır çekebilmelidir.

Üçüncü olarak, Policy'ler minimum yetki ilkesiyle tasarlanmalıdır. Her rol, yalnızca ihtiyaç duyduğu secret path'leri okuyabilmelidir. default` policy'yi hiçbir zaman genişletmemeli, her uygulama için özelleştirilmiş, dar kapsamlı policy'ler oluşturulmalıdır. Bir microservice'in veritabanı credential'ına erişimi varsa, bu credential yalnızca kendi veritabanı için geçerli olmalıdır — başka bir servisinkine erişememelidir.

Dördüncü olarak, Unseal key'lerin yönetimi fiziksel güvenlik gerektirir. Shamir key'ler farklı kişilere dağıtılmalı ve dijital olarak saklanmamalıdır. Auto-unseal kullanılıyorsa, KMS anahtarının güvenliği aynı derecede kritiktir — çünkü KMS anahtarı ele geçirilirse, Vault'un tüm verilerine erişilebilir hale gelinebilir.

Sonuç: Sıfır Güven Mimarisinin Temel Taşı

HashiCorp Vault, modern mikroservis güvenliğinin vazgeçilmez bir bileşenidir. Kubernetes Auth Method, Service Account kimliklerini Vault erişim haklarına çevirerek "Secret Zero" problemini çözer. Vault Agent Sidecar Injector, uygulama kodunu değiştirmeden sırları pod'lara güvenli biçimde sunar. Dinamik secrets motoru, her erişim için benzersiz credential'lar üreterek saldırı yüzeyini dramatik biçimde daraltır. Audit logging, her erişimi kaydederek şeffaflık ve uyumluluk sağlar.

Bir kez daha formüle edersek: Credential sızdırmanın maliyeti, credential çalmanın değil, çalınamaz kılmanın maliyetinden çok daha yüksektir. Vault'un dinamik secrets üretimi, credential'ları kısa ömürlü, benzersiz ve anında iptal edilebilir kılarak bu denklemi kökten değiştirir. Credential çalındığında bile, dakikalar içinde geçersiz hale gelir — çünkü TTL dolmuş veya lease revoke edilmiştir. Bu, sıfır güven mimarisinin Vault ile somut bir teknolojik gerçekliğe dönüştüğü noktadır.