ГлавнаяБлогHashiCorp Vault ile Homelab Secret Yonetimi: 90 Gunluk Notlarim
Security & Compliance

HashiCorp Vault ile Homelab Secret Yonetimi: 90 Gunluk Notlarim

Emre Ferit Aslantas10 июня 2026 г.13 dkСтатья
vault hashicorp homelab secret-management self-hosted security
Sponsored

Üç ay önce homelab'imde dağınık halde duran ~14 farklı .env dosyasını saydım. Her servisin kendi klasöründe, çoğu Git'e accidentally commit edilmiş ve sonradan git filter-repo ile temizlenmiş (ama yine de tarihi hatırlatan), bazılarının izinleri 644 ve hepsi sade plaintext. Bir akşam bu durumu içime sindiremedim ve HashiCorp Vault'u kurmaya karar verdim. Bu yazı, o ilk geceden bugüne — 90 günde — neler yaşadığımı, neyi yanlış kurguladığımı ve şu an gerçekten faydasını gördüğüm akışı anlatıyor.

Vault'un "production-grade" rehberlerinin çoğu kurumsal bir ekibe yönelik. Homelab tarafında "ölçeği küçük tut, ama yine de düzgün yap" şeklinde bir yol gerekiyordu. Üç katmanı (storage, unseal, auth) ayrı ayrı denedim ve birkaçından geri döndüm. Asıl değerli kısım, geriye dönüş hikayeleri.

Neden Vault, Neden Şimdi?

Aslında ilk tercih edeceğim araç Vault değildi. Listede üç aday vardı:

  • SOPS + age — Git-native, sade. Ama her servis için manuel decrypt akışı yorucu.
  • Bitwarden / Vaultwarden — Kullanıyorum zaten (kişisel parolalar için), ama API ile servis-to-service secret cekmek için biraz zorlama.
  • HashiCorp Vault — Aşırı mı gelir diye düşündüm; spoiler: gelmedi.

Karar verdiren şey, dinamik secret ve lease kavramı oldu. Postgres parolasını elle rotate etmek yerine Vault'un her 24 saatte yeni bir database user üretebilmesi, homelab seviyesinde bile değerli. EFA Agent ve Finance Agent gibi servislerim üretim trafiği almasa da, "secret rotate'i bir kez doğru kurgula, üzerine düşünme" tarafı çekti.

İkinci sebep: audit log. Hangi token, hangi path'i, ne zaman okudu — bunu sade dosya sistemiyle takip edemiyordum.

Storage Backend Tercihi: Raft, File, Consul

Vault dokümantasyonu üç ana storage backend sunuyor. Üçünü de farklı dakikalar harcayarak denedim.

File backend ile başladım çünkü "en basiti" demişlerdi. Tek node, lokal disk, sıfır bağımlılık. 20 dakikada kurdum, çalıştı. Sorun şu: snapshot/restore akışı yok, otomatik backup için her şeyi elle yazmam gerekiyor. Üstüne, bir gün başka bir host'a taşımak istesem, dosya kopyalamak dışında yolum yok.

Consul backend'i denemedim — sadece Vault için ayrı bir cluster ayağa kaldırmak homelab'ime fazla geldi. Consul'u zaten başka bir şey için kullanmıyorsam (kullanmıyorum), gereksiz bir katman.

Integrated Storage (Raft) ile karar kıldım. Tek node ile bile çalışır, snapshot komutu native (vault operator raft snapshot save), ileride üç node'a çıkarmak istersem Raft join komutu yetiyor. Üçüncü gün geçtim.

Kurulum konfigürasyonum şu şekilde — minimal ve dürüst:

# /etc/vault.d/vault.hcl
storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-ct103"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/etc/vault.d/tls/vault.crt"
  tls_key_file  = "/etc/vault.d/tls/vault.key"
}

api_addr     = "https://vault.efa.local:8200"
cluster_addr = "https://vault.efa.local:8201"
ui           = true

Bunu Proxmox üzerinde CT103'e kurdum (CT100 AdGuard, CT102 MCP Hub — kalıp belli oldu). 1 GB RAM, 8 GB disk verdim, hâlâ ~280 MB kullanıyor ve disk 1.2 GB'de.

Unseal: 90 Günün En Stresli Konusu

Vault'un en başta verdiği 5 unseal key + 1 root token kombinasyonu, ilk gece kafamı en çok karıştıran şey oldu. Senaryoları kafamda canlandırdım:

  • Tüm key'leri tek bir yere koysam → Vault'un noktası kalmıyor.
  • Ailenin bilmediği iki ayrı yere bölsem → ben olmazsam kimse açamaz, homelab'ın evdeki kritik DNS'i kapanır (CT100 Vault'tan secret almıyor ama EFA Agent alıyor ve onun arızası benim sabahki kahve akışıma bağlanıyor).
  • Auto-unseal kullansam → Cloud KMS (AWS/GCP) ister, bu da bulut hesabı ister, bulut hesabı = aylık masraf ve "homelab self-hosted ruhu" dışına çıkar.

Sonunda kompromiye gittim: Transit auto-unseal. Yani ikinci bir küçük Vault instance'ı (sadece transit secrets engine ile) kuruyorsunuz, ana Vault onu kullanarak kendini unseal ediyor. İkincil Vault'u Mac Mini üzerindeki Docker'da ayağa kaldırdım. Çapraz bağımlılık görünse de, çoğu gün her iki host da açık olduğundan pratik bir çözüm oldu. Ayrıca ana Vault yeniden başlatıldığında ben evden uzakta olsam bile servisler ayağa kalkıyor.

5 unseal key'i offline yedek olarak (gerçekten ihtiyaç olursa) iki ayrı şifreli USB'ye böldüm. Geçici çözüm değil, gerçek çözüm: production ekibi olsaydım Cloud KMS giderdim. Tek başına homelab için bu yeterli.

"Kendi Kendimi Kilitledim" Anı

İlk ayın sonunda, root token'i ilk gün yazdığım Notes uygulamasından sildim — "policy'ler oturdu, AppRole çalışıyor, root token'a gerek yok" diye düşündüm. Bir hafta sonra yeni bir secrets engine mount etmem gerektiğinde root yetkiye ihtiyaç duydum ve elimde token yoktu. Vault'ta vault operator generate-root komutu var ve unseal key sahiplerinin onayıyla yeni root üretebiliyor. Tabii bu komutu daha önce hiç çalıştırmamıştım. 35 dakika dokümantasyon okudum, sonunda regenerate ettim. Şimdi root token'ı sealed bir keepass dosyasında tutuyorum ve silmiyorum. Ders: paranoyaklık iyi, ama recovery prosedürünü test etmeden secret'lardan vazgeçme.

Auth Method: AppRole, Token Değil

İlk hafta her şeyi token ile yaptım. Servislere VAULT_TOKEN=hvs.XXXXX env variable'ı verdim. Çalışıyor, ama:

  1. Token rotate etmek istediğimde her servisin env'ini elle güncellemek zorunda kaldım.
  2. Token TTL süresi dolduğunda servis sessizce 403 yiyordu, debug etmek can sıkıcıydı.

İkinci hafta AppRole auth method'una geçtim. Her servis için role_id (statik, env'de tutuluyor) ve secret_id (kısa ömürlü, başlangıçta tek-kullanımlık) ikilisi alıyor, sonra kendi token'ını alıyor. EFA Agent için policy ve role örneği:

# Policy
cat > efa-agent-policy.hcl <<EOF
path "secret/data/efa-agent/*" {
  capabilities = ["read", "list"]
}

path "database/creds/efa-agent-postgres" {
  capabilities = ["read"]
}
EOF

vault policy write efa-agent efa-agent-policy.hcl

# AppRole
vault auth enable approle
vault write auth/approle/role/efa-agent \
  token_policies="efa-agent" \
  token_ttl=1h \
  token_max_ttl=4h \
  secret_id_ttl=24h

token_ttl=1h ile EFA Agent her saatte token'ını yeniliyor. Bu, "iyi homelab güvenliği" değil ama "rotate'i unutmak imkansız" hissi veriyor. Production'da çok daha kısa süreler tercih edilir; benim için 1 saat sweet spot oldu.

Mac Mini Tarafı: Vault Agent ve Template Render

Mac Mini üzerinde çalışan servisler için Vault Agent çalıştırıyorum. Agent, AppRole ile authenticate olup token'ı bir dosyaya yazıyor, ve env dosyalarını template'ten render ediyor. Eski plaintext .env dosyalarını silip yerlerine template koydum:

# /etc/vault-agent/efa-agent.hcl
auto_auth {
  method "approle" {
    config = {
      role_id_file_path   = "/etc/vault-agent/role_id"
      secret_id_file_path = "/etc/vault-agent/secret_id"
    }
  }
  sink "file" {
    config = {
      path = "/var/run/vault-token"
    }
  }
}

template {
  source      = "/etc/vault-agent/templates/efa-agent.env.tpl"
  destination = "/opt/efa-agent/.env"
  perms       = "0600"
  command     = "launchctl kickstart -k system/com.efa.agent"
}

Template:

TELEGRAM_BOT_TOKEN={{ with secret "secret/data/efa-agent/telegram" }}{{ .Data.data.token }}{{ end }}
OPENAI_API_KEY={{ with secret "secret/data/efa-agent/openai" }}{{ .Data.data.key }}{{ end }}
POSTGRES_URL=postgresql://{{ with secret "database/creds/efa-agent-postgres" }}{{ .Data.username }}:{{ .Data.password }}{{ end }}@db.efa.local:5432/efa

Postgres satırına dikkat: kullanıcı/şifre dinamik. Vault, Postgres'e bağlanıp gerçek bir DB user üretiyor, 24 saat sonra siliyor. EFA Agent her gün taze credential ile çalışıyor ve eski bir secret leak olsa bile günü dolunca çöp.

Bunu kurarken bir noktada Postgres tarafında kullanıcı sayısı patlatıp too many clients hatası aldım — connection pooling'de eski user ile açılmış idle bağlantılar lease iptal edilmiş olsa bile devam ediyordu. PgBouncer'ı önüne koyarak çözdüm, ama küçük ölçekli homelab'de "dinamik database secret + statik connection pool" mantıklı bir kararsızlık. Ders: dinamik secret kullanacaksanız client tarafı bağlantı yönetimini de gözden geçirin.

Vazgeçtiğim Şey: Vault UI'ı Günlük Kullanmak

Vault'un built-in web UI'ı görsel olarak güzel, secret eklemek için ilk haftalar kullandım. Ama 3 ay sonra fark ettim: tek bir kez bile UI'dan secret okumamışım, hep CLI veya servis üzerinden geliyor. UI sadece policy ekleme sırasında işe yarıyordu, onu da Terraform'a taşıdım:

# terraform/vault-policies.tf
resource "vault_policy" "efa_agent" {
  name   = "efa-agent"
  policy = file("${path.module}/policies/efa-agent.hcl")
}

resource "vault_approle_auth_backend_role" "efa_agent" {
  backend        = "approle"
  role_name      = "efa-agent"
  token_policies = [vault_policy.efa_agent.name]
  token_ttl      = 3600
  token_max_ttl  = 14400
  secret_id_ttl  = 86400
}

UI'ı tamamen kapatmadım (acil senaryoda lazım), ama günlük workflow'umda yer almıyor. "Ne hosting istemediğimi öğrenmek, ne hosting istediğimi öğrenmekten önce gelir" tarzı bir ders.

Audit Log: Asla Açıkken Vazgeçilmez

Vault'un audit log özelliğini ilk gün açmadım. "Sonra bakarım" mantığıyla. Bir hafta sonra GitHub Actions runner'ım Vault'a istek atarken yanlış policy ile takılıyordu ve hangi path'i istediğini göremiyordum. O an audit log'u açtım:

vault audit enable file file_path=/var/log/vault/audit.log

JSON formatında her istek, hangi token, hangi path, success/fail — disk üzerinde. Şu an Loki'ye göndermiyorum ama bir cron her gün son 1 günün özetini bana mail ediyor (denied request sayısı + ilginç path'ler). 1 KB/log satırı civarı, günde ~3 MB. Az yer kaplıyor, çok kazandırıyor.

90 Günde Toplam Maliyet ve Kaynak Kullanımı

Vault CT103 üzerinde:

  • RAM: ortalama 280 MB, peak 410 MB
  • CPU: idle ~%0.3, snapshot anı ~%6
  • Disk: 1.2 GB (audit log dahil)
  • Aylık snapshot maliyeti (Backblaze B2'ye): ~0.04 USD

Kurulum süresi (öğrenme dahil): ilk gün ~4 saat, sonraki kullanım/kurgu refinement: dağıtık ~12 saat. AppRole akışına geçiş ayrı bir akşam aldı (~3 saat).

Eski plaintext .env dosyalarını sildim, ama yeni servis eklemek artık 5 dakikalık bir iş: policy yaz, AppRole oluştur, secret koy, Vault Agent template ekle.

Notlar ve Çıkarımlar

  1. Storage backend için Raft'i seçin. File backend ile başlasanız bile, snapshot/restore'sız bir sistem homelab'de bile risk; Raft, tek node modunda da çalışıyor.
  2. Root token'ı silmeyin, ama bir keepass / şifreli vault'ta saklayın. Regenerate prosedürü çalışıyor ama stres ediyor.
  3. AppRole'a geç geçmeyin — ilk haftada kurun. Token-based başlangıç sizi tembelliğe alıştırır; bir ay sonra her şeyi taşımak iki gün alıyor.
  4. Audit log'u kurulumun birinci günü açın. Sonradan açtığınızda, geçmiş olayları debug etmek imkansız.
  5. Dinamik database secret kullanacaksanız PgBouncer / pool tarafına bakın. Lease bitince eski credential ile açık kalan bağlantı, "neden bağlantı sayısı artıyor" sorusunun cevabı.
  6. Auto-unseal stratejinizi yazılı belgeyin. Kafanızdaki çözüm 3 ay sonra hatırlanmıyor; ben kendi vault-recovery.md'mi ~/notes altında tutuyorum.

Vault, homelab için fazla mı? Üç ay önceki ben "evet" derdi. Bugünki ben, plaintext .env salgınına geri dönmeyi düşünmüyorum. Devamında planım, Kubernetes Auth method ile ai-lab namespace'indeki pod'ları Vault'a doğrudan bağlamak — şu an env injection yapıyorum, in-cluster auth daha temiz olacak. Ona başladığımda muhtemelen yine bir hata hikayesi olur, o yazı da gelir.

Sponsored

Еженедельная рассылка DevOps

Новые обзоры инструментов, сравнения и тренды DevOps — еженедельно на вашу почту.