Sızıntıların büyük kısmı egzotik bir açıktan değil, yıllardır değişmemiş bir veritabanı parolasından çıkar. O parola bir .env dosyasında, bir CI değişkeninde, bir Ansible deposunda ve birkaç geliştiricinin not uygulamasında aynı anda durur. HashiCorp Vault'un çözdüğü asıl problem "parolaları şifreli bir kasada tutmak" değil; parolaların ömrünü dakikalara indirip her kullanımı denetlenebilir hâle getirmektir.

Bu rehber Vault'u üretimde çalıştırırken en çok fark yaratan üç yeteneği uygulamalı olarak ele alıyor: dinamik kimlik bilgileri, iş yükü kimliği için AppRole ve şifreleme hizmeti olarak Transit. Sonunda da üretim sertleştirme kontrol listesi var.

Statik secret ile dinamik secret arasındaki fark


Statik secret, kasada duran sabit bir değerdir. Vault onu şifreler, erişimini denetler ve kim okuduğunu kaydeder; ancak değer hâlâ uzun ömürlüdür ve sızarsa siz döndürene kadar geçerli kalır.

Dinamik secret ise istek anında üretilir. Uygulama veritabanına bağlanmak istediğinde Vault ilgili veritabanında o an için bir kullanıcı oluşturur, kimlik bilgisini uygulamaya verir ve kira (lease) süresi bitince kullanıcıyı siler. Sızan bir kimlik bilgisinin değeri, kalan kira süresi kadardır.

BoyutStatik secret (KV)Dinamik secret
ÖmürSiz döndürene kadarTTL kadar (dakikalar veya saatler)
Sızma etkisiTespit edilene kadar sürerKira bitince kendiliğinden kapanır
DöndürmeElle veya ayrı otomasyonDoğası gereği her istekte
İzlenebilirlikKim okudu belli, kim kullandı belirsizHer kimlik bilgisi tek tüketiciye bağlı
Uygun olduğu yerAPI anahtarları, sertifikalar, üçüncü taraf tokenlarıVeritabanı, bulut IAM, SSH, PKI


Veritabanı secret motorunu kurmak


Aşağıdaki örnek PostgreSQL için dinamik kullanıcı üretir. Vault'un kullandığı yönetici hesabı yalnızca rol oluşturma yetkisine sahip olmalı ve bu hesabın parolası kurulumdan hemen sonra Vault üzerinden döndürülmelidir.

CODE TERMINAL
vault secrets enable database

vault write database/config/uygulama-pg \
  plugin_name=postgresql-database-plugin \
  allowed_roles="uygulama-okuma" \
  connection_url="postgresql://{{username}}:{{password}}@pg.ic.ornek.net:5432/uygulama?sslmode=verify-full" \
  username="vault_yonetici" \
  password="ILK-KURULUM-PAROLASI"

# Yonetici parolasini Vault'a devret: bu andan sonra kimse bilmiyor
vault write -force database/rotate-root/uygulama-pg

vault write database/roles/uygulama-okuma \
  db_name=uygulama-pg \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  revocation_statements="REASSIGN OWNED BY \"{{name}}\" TO vault_yonetici; DROP OWNED BY \"{{name}}\"; DROP ROLE IF EXISTS \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="4h"


Kimlik bilgisi almak tek komut:

CODE TERMINAL
vault read database/creds/uygulama-okuma

Key                Value
lease_id           database/creds/uygulama-okuma/oVvJ...
lease_duration     1h
username           v-approle-uygulama-o-3Tq1...
password           A1a-...


rotate-root adımı atlanırsa, kurulumda kullandığınız yönetici parolası shell geçmişinizde ve CI günlüklerinizde kalıcı bir zafiyet olarak yaşamaya devam eder.

AppRole: uygulamalar Vault'a kimliğini nasıl kanıtlar


Dinamik secret'lar tek başına yeterli değildir; uygulamanın Vault'a kim olduğunu kanıtlaması gerekir. Bunun için token'ı koda gömmek en yaygın hatadır. Doğru yaklaşım, çalışma ortamına uygun bir kimlik doğrulama yöntemi seçmektir:


  • Kubernetes'te çalışan iş yükleri için Kubernetes auth (pod'un service account token'ı ile).
  • Bulut sanal makineleri için AWS, Azure veya GCP auth (örnek kimliği ile).
  • CI/CD işleri için JWT/OIDC auth (iş kimliğine bağlı kısa ömürlü token).
  • Bunların uygulanamadığı klasik sanal makineler ve konteyner dışı servisler için AppRole.



AppRole iki parçadan oluşur: değişmeyen role_id (kimlik) ve kısa ömürlü secret_id (kanıt). Bu ayrım, "AppRole anti-pattern" olarak bilinen hatayı önlemek için kritiktir: her iki değeri aynı dosyaya yazarsanız AppRole'ü statik bir parolaya dönüştürmüş olursunuz.

CODE TERMINAL
vault auth enable approle

vault write auth/approle/role/uygulama \
  token_policies="uygulama-okuma" \
  secret_id_ttl="10m" \
  secret_id_num_uses=1 \
  token_ttl="20m" \
  token_max_ttl="1h" \
  bind_secret_id=true \
  secret_id_bound_cidrs="10.20.30.0/24" \
  token_bound_cidrs="10.20.30.0/24"

vault read auth/approle/role/uygulama/role-id

# Dagitim aninda tek kullanimlik ve 10 dakika omurlu secret_id uretilir
vault write -f auth/approle/role/uygulama/secret-id


İlgili politika, en az yetki ilkesiyle yalnızca gereken yolu açar:

CODE TERMINAL
path "database/creds/uygulama-okuma" {
  capabilities = ["read"]
}

path "sys/leases/renew" {
  capabilities = ["update"]
}

path "auth/token/renew-self" {
  capabilities = ["update"]
}


secret_id dağıtımı


secret_id'yi uygulamaya ulaştırmanın güvenli yolu, onu uygulamanın kendisine hiç göstermemektir. Vault Agent, secret_id'yi dosya izinleriyle korunan bir yoldan okur, kimlik doğrulamasını kendisi yapar ve uygulamaya yalnızca hazır secret'ı bir şablon dosyası olarak sunar:

CODE TERMINAL
# /etc/vault-agent.hcl
vault { address = "https://vault.ic.ornek.net:8200" }

auto_auth {
  method "approle" {
    config = {
      role_id_file_path                   = "/etc/vault/role-id"
      secret_id_file_path                 = "/run/vault/secret-id"
      remove_secret_id_file_after_reading = true
    }
  }
  sink "file" {
    config = { path = "/run/vault/token", mode = 0600 }
  }
}

template {
  destination = "/run/uygulama/db.env"
  perms       = "0640"
  contents    = "DB_USER={{ with secret \"database/creds/uygulama-okuma\" }}{{ .Data.username }}\nDB_PASS={{ .Data.password }}{{ end }}\n"
  command     = "systemctl reload uygulama"
}


remove_secret_id_file_after_reading ayarı, tek kullanımlık secret_id'nin diskte artık kalmasını engeller. Uygulama Vault API'sini hiç bilmez; yalnızca kira yenilendikçe güncellenen bir env dosyası okur.

Transit: şifreleme hizmeti olarak Vault


Transit motoru veri saklamaz; veriyi sizin adınıza şifreler ve çözer. Anahtar Vault'tan hiç çıkmadığı için uygulama sunucusu ele geçse bile saldırgan anahtarı alamaz. Elde ettiği şey Vault'a şifre çözme isteği gönderme yetkisidir ve bu istekler denetim günlüğüne düşer.

CODE TERMINAL
vault secrets enable transit
vault write -f transit/keys/musteri-pii type=aes256-gcm96

# Sifreleme: ciphertext vault:v1: oneki ile doner
vault write transit/encrypt/musteri-pii \
  plaintext=$(echo -n "ornek-hassas-veri" | base64)

# Cozme
vault write transit/decrypt/musteri-pii ciphertext="vault:v1:8SDd3..."


Anahtar döndürme, eski verileri yeniden şifrelemeyi gerektirmez. Yeni sürümle şifrelenen veriler vault:v2: önekini alır; eski sürümler çözülmeye devam eder. Veriyi yeni sürüme taşımak için rewrap kullanılır ve bu işlem açık metni hiç görmez:

CODE TERMINAL
vault write -f transit/keys/musteri-pii/rotate
vault write transit/rewrap/musteri-pii ciphertext="vault:v1:8SDd3..."

# Eski surumlerle cozmeyi tamamen kapatmak icin
vault write transit/keys/musteri-pii/config min_decryption_version=2


Aranabilir alanlar için convergent encryption


Aynı açık metnin her seferinde farklı ciphertext üretmesi güvenlik açısından doğrudur, ancak "bu e-postayla kayıtlı müşteri var mı?" sorgusunu imkânsızlaştırır. Bu ihtiyaç için convergent encryption kullanılır; karşılığında aynı değerlerin aynı ciphertext'e dönüştüğü, yani eşitlik bilgisinin sızdığı kabul edilir. Bu yüzden yalnızca yüksek kardinaliteli alanlarda (e-posta, kimlik numarası) tercih edilir; cinsiyet veya şehir gibi az sayıda değer alan alanlarda kullanılmaz.

CODE TERMINAL
vault write -f transit/keys/eposta-arama \
  type=aes256-gcm96 convergent_encryption=true derived=true


Üretim sertleştirme kontrol listesi



  • Vault'u root veya Administrator değil, ayrı ve yetkisiz bir servis hesabıyla çalıştırın; systemd biriminde CapabilityBoundingSet=CAP_IPC_LOCK, ProtectSystem=full ve PrivateTmp=yes kullanın.
  • Uçtan uca TLS zorunlu olsun. İstemci ile Vault arasında ve Raft düğümleri arasında TLS 1.2 veya üstünü kullanın; tls_disable ayarını üretimde asla açmayın.
  • mlock etkin kalsın, bellek takas alanına (swap) yazılmasın. Konteynerde çalıştırıyorsanız swap'ı tamamen kapatın.
  • Auto-unseal planı yapın (bulut KMS veya Transit unseal). Shamir paylarını gece yarısı elle girmeye bağlı bir tasarım, ilk yeniden başlatmada kesintiye dönüşür.
  • İlk root token'ı kurulum bittikten sonra iptal edin (vault token revoke). Gerektiğinde yeniden üretilebilir.
  • Denetim günlüğünü (audit device) en az iki hedefe yazın (dosya ve syslog). Vault tek denetim hedefine yazamadığında istekleri reddeder; bu bilinçli bir tasarımdır ve yedek hedef gerektirir.
  • Politikaları varsayılan reddetme ilkesiyle yazın, sudo yeteneğini yalnızca gerçekten gereken sys/ yollarına verin ve politika dosyalarını sürüm kontrolünde tutun.
  • TTL'leri mümkün olan en kısa değere çekin: veritabanı kimlik bilgisi için 1 saat, CI token'ı için 15 dakika iyi bir başlangıçtır.
  • Namespace veya ayrı mount yollarıyla ekipleri ayırın; üretim ve test secret'larını aynı yolun altında tutmayın.
  • Hassas yanıtları response wrapping ile taşıyın: secret_id gibi değerler tek kullanımlık bir sarma token'ı ile aktarılır ve iki kez açılmaya çalışılırsa olay kaydı oluşur.



İzleme: hangi sinyaller önemli


SinyalNeden izlenir
vault.core.unsealed0 değeri, kümenin secret dağıtamadığı anlamına gelir
vault.expire.num_leasesSürekli artıyorsa kiralar iptal edilmiyor, ölü kimlik bilgileri birikiyor
vault.token.creationAni artış, kimlik doğrulama döngüsüne giren hatalı istemciyi gösterir
Denetim günlüğünde 403 yoğunluğuPolitika hatası veya yetkisiz erişim denemesi
rotate-root ve politika değişiklikleriAyrıcalıklı yapılandırma değişimi; SIEM'de uyarı üretilmeli


Denetim günlüğü secret değerlerini değil yalnızca isteğin özetini (kim, hangi yol, hangi yanıt) tutar; bu yüzden SIEM'e göndermek güvenlidir ve olay sonrası soruşturmada en değerli kaynaktır.

Sık yapılan hatalar



  • Vault'u yalnızca KV deposu olarak kullanmak. Dinamik motorlar devreye girmedikçe elde edilen tek kazanç merkezî bir şifreli dosya sunucusudur.
  • role_id ve secret_id'yi aynı imaja veya aynı yapılandırma dosyasına koymak. Bu, AppRole'ü statik parolaya çevirir.
  • Uzun token_ttl değerleriyle yenilemeyi hiç uygulamamak, sonra sorun çıktıkça TTL'leri büyütmek.
  • Kira iptalini unutmak. Uygulama kapanırken lease'i iptal etmiyorsa veritabanında ölü kullanıcılar birikir.
  • Root token'ı otomasyonlarda kullanmak. Root token politika tanımaz ve denetim kaydında ayrıştırılamayan sınırsız erişim üretir.
  • Transit anahtarını uygulama sunucusuna kopyalamak. Transit'in tüm değeri anahtarın Vault dışına çıkmamasıdır.



Sonuç


Vault'un güvenlik kazancı kasa metaforundan değil, ömrü kısaltmaktan gelir. Dinamik kimlik bilgileri sızıntının etkisini kira süresine indirir; AppRole ve Vault Agent uygulamanın hiçbir zaman kalıcı bir sır taşımamasını sağlar; Transit ise şifreleme anahtarlarını uygulama sunucusunun erişim alanından tamamen çıkarır.

Uygulamaya en hızlı dönüş veren sıra şudur: önce veritabanı kimlik bilgilerini dinamik motora taşıyın, ardından CI/CD'deki statik token'ları JWT/OIDC veya AppRole ile değiştirin, en sonunda uygulama içinde elle yapılan şifrelemeyi Transit'e devredin. Her adımda TTL'leri kısaltın ve denetim günlüğünü SIEM'e bağlayın.
TR Siber Ekibi Yazar · TRSiber
Kaynak bağlantısı ← Ana sayfaya dön