Terraform ile Gizli Bilgi Yönetimi: Vault ve Secret Manager Entegrasyonu
Altyapı kodunuzu Git’e push ettiğinizde database şifrenizin de beraberinde gitmesi, kariyerinizin en kötü anlarından biri olabilir. Terraform ile çalışan ekiplerin büyük çoğunluğu bu hatayı bir kez yapar ve bir daha yapmamak için ciddi önlemler alır. Gizli bilgi yönetimi, DevOps olgunluk seviyesinin en kritik göstergelerinden biridir ve Terraform bu konuda oldukça güçlü entegrasyon seçenekleri sunar.
Bu yazıda HashiCorp Vault ve AWS/GCP Secret Manager ile Terraform’u nasıl entegre edeceğinizi, production ortamında gerçekten işe yarayan patternleri ve sık yapılan hataları ele alacağız.
Neden Terraform’da Secret Yönetimi Bu Kadar Kritik
Terraform state dosyaları varsayılan olarak plaintext JSON formatında saklanır. terraform.tfstate dosyasını açıp içine baktığınızda, oluşturduğunuz tüm kaynakların detaylarını ve çoğu zaman gizli kalması gereken değerleri görebilirsiniz. Bu durum beraberinde birkaç kritik riski getirir.
State dosyası sızıntısı: S3 bucket yanlış konfigüre edilmişse veya local state kullanılıyorsa, tüm gizli bilgiler açığa çıkar.
Plan çıktısı riski: terraform plan çıktısı terminale yazılır ve CI/CD loglarında görünebilir.
Versyon kontrol riski: .tfvars dosyaları yanlışlıkla commit edilebilir.
Bunları önlemek için merkezi secret yönetim sistemleriyle entegrasyon şart haline gelmiştir.
HashiCorp Vault Entegrasyonu
Vault, Terraform’un yapımcısı olan HashiCorp’un geliştirdiği secret yönetim çözümüdür. İkili ürünün bu kadar iyi entegre olması tesadüf değil.
Vault Provider Kurulumu
Önce Vault provider’ını tanımlamanız gerekiyor:
terraform {
required_providers {
vault = {
source = "hashicorp/vault"
version = "~> 3.20"
}
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "vault" {
address = var.vault_address
# Token ortam degiskeninden okunur: VAULT_TOKEN
# Ya da auth method kullanilabilir
}
Vault token’ını doğrudan kod içine yazmak yerine her zaman ortam değişkeni veya auth method kullanın. CI/CD pipeline’larında bu token’ı nasıl yönettiğiniz ayrı bir güvenlik katmanı oluşturur.
Vault’tan Secret Okuma
Vault’ta bir secret oluşturup Terraform’dan okuyalım:
# Once Vault CLI ile secret olusturalim
vault kv put secret/myapp/database
username="appuser"
password="SuperSecure123!"
host="db.internal.company.com"
# Dogrulayalim
vault kv get secret/myapp/database
Terraform tarafında bu secret’ı okumak için data source kullanıyoruz:
# Vault'tan database bilgilerini oku
data "vault_kv_secret_v2" "database" {
mount = "secret"
name = "myapp/database"
}
# RDS instance olusturma (sifre artik Vault'tan geliyor)
resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
engine_version = "15.3"
instance_class = "db.t3.medium"
allocated_storage = 20
db_name = "appdb"
username = data.vault_kv_secret_v2.database.data["username"]
password = data.vault_kv_secret_v2.database.data["password"]
skip_final_snapshot = false
tags = {
Environment = "production"
ManagedBy = "terraform"
}
}
Bu yaklaşımda dikkat etmeniz gereken nokta: state dosyasında bu değerler hala plaintext olarak görünebilir. Bu yüzden state’i de şifrelemek kritik.
Vault Dynamic Secrets ile Gelişmiş Senaryo
Vault’un en güçlü özelliklerinden biri dynamic secrets. Statik şifre yerine her seferinde geçici credential oluşturulur:
# Vault'ta AWS secret engine aktifleştir
vault secrets enable aws
# AWS rolü tanımla
vault write aws/roles/terraform-role
credential_type=iam_user
policy_document=-<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ec2:*", "s3:*"],
"Resource": "*"
}
]
}
EOF
Terraform’da dynamic secret kullanımı:
# Dynamic AWS credentials al
data "vault_aws_access_credentials" "creds" {
backend = "aws"
role = "terraform-role"
type = "iam_user"
}
# Bu credentials ile AWS provider'i configure et
provider "aws" {
access_key = data.vault_aws_access_credentials.creds.access_key
secret_key = data.vault_aws_access_credentials.creds.secret_key
region = "eu-west-1"
}
Bu pattern özellikle kısa ömürlü credential’ların gerektiği güvenlik politikalarında çok değerlidir. Her Terraform run’ı farklı ve geçici bir AWS credential seti kullanır.
AppRole Authentication
Production ortamında Vault token’ını manuel olarak yönetmek yerine AppRole kullanmak çok daha güvenli:
# Vault'ta AppRole aktifleştir
vault auth enable approle
# Terraform icin policy olustur
vault policy write terraform-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
path "aws/creds/terraform-role" {
capabilities = ["read"]
}
EOF
# AppRole olustur
vault write auth/approle/role/terraform
secret_id_ttl=10m
token_num_uses=10
token_ttl=20m
token_max_ttl=30m
secret_id_num_uses=40
policies="terraform-policy"
# Role ID ve Secret ID al
vault read auth/approle/role/terraform/role-id
vault write -f auth/approle/role/terraform/secret-id
Terraform provider konfigürasyonunda AppRole kullanımı:
provider "vault" {
address = "https://vault.company.internal:8200"
auth_login {
path = "auth/approle/login"
parameters = {
role_id = var.vault_role_id
secret_id = var.vault_secret_id
}
}
}
# Bu degerleri ortam degiskenlerinden al
# TF_VAR_vault_role_id ve TF_VAR_vault_secret_id
AWS Secrets Manager Entegrasyonu
AWS ortamında çalışıyorsanız ve ayrı bir Vault kurulumu istemiyorsanız, AWS Secrets Manager doğal tercih haline gelir.
Temel Secrets Manager Kullanımı
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Mevcut bir secret'i oku
data "aws_secretsmanager_secret" "db_credentials" {
name = "production/myapp/database"
}
data "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = data.aws_secretsmanager_secret.db_credentials.id
}
# JSON formatindaki secret'i parse et
locals {
db_creds = jsondecode(
data.aws_secretsmanager_secret_version.db_credentials.secret_string
)
}
# ECS Task Definition'da kullan
resource "aws_ecs_task_definition" "app" {
family = "myapp"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 256
memory = 512
execution_role_arn = aws_iam_role.ecs_execution.arn
container_definitions = jsonencode([
{
name = "myapp"
image = "myapp:latest"
environment = [
{
name = "DB_HOST"
value = local.db_creds.host
}
]
# Sifre icin secret reference kullan, direkt environment degil
secrets = [
{
name = "DB_PASSWORD"
valueFrom = data.aws_secretsmanager_secret.db_credentials.arn
}
]
}
])
}
Dikkat edin: secrets bloğunu environment yerine kullanmak, değerin ECS task definition’da plaintext görünmesini engeller.
Terraform ile Secret Oluşturma ve Rotasyon
Sadece okumakla kalmayıp Terraform ile secret oluşturabilir ve rotasyon konfigürasyonu yapabilirsiniz:
# Random sifre olustur
resource "random_password" "db_password" {
length = 32
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
}
# Secrets Manager'da sakla
resource "aws_secretsmanager_secret" "db_password" {
name = "production/myapp/db-password"
description = "Production database password for myapp"
recovery_window_in_days = 30
tags = {
Environment = "production"
Application = "myapp"
ManagedBy = "terraform"
}
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = jsonencode({
username = "appuser"
password = random_password.db_password.result
host = aws_db_instance.main.address
port = 5432
dbname = "appdb"
})
}
# Otomatik rotasyon aktifleştir
resource "aws_secretsmanager_secret_rotation" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
rotation_lambda_arn = aws_lambda_function.secret_rotator.arn
rotation_rules {
automatically_after_days = 30
}
}
GCP Secret Manager Entegrasyonu
Google Cloud kullanıyorsanız benzer bir yaklaşım GCP Secret Manager için de geçerli:
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
# Mevcut secret'i oku
data "google_secret_manager_secret_version" "db_password" {
project = var.project_id
secret = "myapp-db-password"
version = "latest"
}
# Cloud Run service'de kullan
resource "google_cloud_run_service" "app" {
name = "myapp"
location = "europe-west1"
template {
spec {
containers {
image = "gcr.io/${var.project_id}/myapp:latest"
env {
name = "DB_PASSWORD"
value_from {
secret_key_ref {
name = "myapp-db-password"
key = "latest"
}
}
}
}
service_account_name = google_service_account.app.email
}
}
}
# Secret Manager'a erisim icin IAM
resource "google_secret_manager_secret_iam_member" "app_access" {
project = var.project_id
secret_id = "myapp-db-password"
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.app.email}"
}
State Dosyasını Güvence Altına Alma
Secret’ları external sistemlerden okusanız bile Terraform state’i hala risk içerebilir. S3 backend için şifreleme zorunlu tutulmalı:
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "production/myapp/terraform.tfstate"
region = "eu-west-1"
encrypt = true
kms_key_id = "arn:aws:kms:eu-west-1:123456789:key/mrk-abc123"
# State locking icin DynamoDB
dynamodb_table = "terraform-state-lock"
# Versioning aktif olmali
# Bucket seviyesinde ayarlanir
}
}
State dosyasındaki hassas değerleri işaretlemek için sensitive = true kullanın:
variable "db_password" {
description = "Database password"
type = string
sensitive = true # Plan ve apply ciktisinda gizlenir
}
output "db_connection_string" {
value = "postgresql://${var.db_user}:${var.db_password}@${aws_db_instance.main.address}/appdb"
sensitive = true # Output'ta gizlenir
}
CI/CD Pipeline Entegrasyonu
GitLab CI örneği ile Vault entegrasyonunu görelim:
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
variables:
TF_ROOT: ${CI_PROJECT_DIR}/terraform
VAULT_ADDR: "https://vault.company.internal:8200"
.terraform_base:
image: hashicorp/terraform:1.6
before_script:
# Vault JWT auth ile token al
- |
export VAULT_TOKEN=$(vault write -field=token auth/jwt/login
role="gitlab-terraform"
jwt="${CI_JOB_JWT_V2}")
# Vault'tan Terraform degiskenlerini cek
- |
export TF_VAR_db_password=$(vault kv get
-field=password secret/myapp/database)
- cd ${TF_ROOT}
- terraform init
terraform_plan:
extends: .terraform_base
stage: plan
script:
- terraform plan -out=tfplan -input=false
artifacts:
paths:
- ${TF_ROOT}/tfplan
expire_in: 1 hour
terraform_apply:
extends: .terraform_base
stage: apply
script:
- terraform apply -input=false tfplan
when: manual
only:
- main
Bu pipeline’da CI_JOB_JWT_V2 GitLab’ın otomatik oluşturduğu JWT token’dır ve Vault’a güvenli kimlik doğrulama sağlar. Root token veya statik secret ID kullanmaktan çok daha güvenlidir.
Sık Yapılan Hatalar ve Kaçınma Yolları
tfvars dosyalarını commit etmek: .gitignore dosyanıza mutlaka ekleyin.
# .gitignore
*.tfvars
*.tfvars.json
.terraform/
terraform.tfstate
terraform.tfstate.backup
crash.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
Output’larda hassas veri: Her output için sensitive = true kontrolü yapın.
State dosyasını local tutmak: Tek kişilik projeler dahil remote backend kullanın.
Vault token’ını environment variable yerine kod içine yazmak: VAULT_TOKEN, VAULT_ROLE_ID, VAULT_SECRET_ID gibi standart env var isimleri kullanın.
Aşırı geniş Vault policy: En az ayrıcalık prensibini uygulayın, Terraform sadece ihtiyaç duyduğu path’lere erişebilmeli.
Sonuç
Terraform ile secret yönetimi, “nasılsa internal network” anlayışından uzak, gerçek anlamda güvenli bir altyapı kültürü oluşturmanın temel taşlarından biridir. Vault veya cloud provider’ınızın native çözümü, bu noktada tercih meselesi olarak kalıyor; ancak her iki durumda da core prensipler aynı: secret’ı koddan ayır, state’i şifrele, en az ayrıcalık prensibini uygula, rotasyonu otomatize et.
Küçük bir ekip için başlangıç noktası olarak AWS Secrets Manager veya GCP Secret Manager genellikle daha kolay kurulum sunar. Çoklu cloud veya hybrid ortamlarda ise Vault merkezi bir kontrol katmanı sağladığı için daha uygun tercih haline gelir. Hangi çözümü seçerseniz seçin, bu sistemleri ilk sprint’ten itibaren kurmanız, sonradan güvenlik borcu ödemekten çok daha az maliyetli olacaktır.
