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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir