Secret Management
จัดการ secrets (passwords, API keys, certificates) ใน Terraform อย่างปลอดภัย — ไม่ให้ leak ใน state, code, log
ปัญหา: Secrets ใน Terraform
Terraform เก็บ resource attributes ใน state file เป็น plain text — รวมถึง:
- Database passwords
- API tokens
- Private keys
- Auth credentials
→ ใครเข้าถึง state = เห็น secrets
Layered Defense
Anti-Patterns
❌ Hard-coded Secrets
resource "aws_db_instance" "main" {
password = "MyP@ssw0rd123" # ❌ in code → in Git → leak
}
❌ Plain .tfvars Committed
db_password = "MyP@ssw0rd"
api_key = "sk_live_abc123"
❌ Sensitive Variable without External Source
variable "db_password" {
sensitive = true
default = "MyP@ssw0rd" # ❌ still in code
}
✅ Pattern 1: External Secret Manager
AWS Secrets Manager
# Read existing secret (created out-of-band)
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/db-password"
}
resource "aws_db_instance" "main" {
username = "admin"
password = data.aws_secretsmanager_secret_version.db_password.secret_string
lifecycle {
ignore_changes = [password] # rotate by Secrets Manager, ignore drift
}
}
→ Secret อยู่ใน Secrets Manager, Terraform แค่ read
AWS SSM Parameter Store
data "aws_ssm_parameter" "db_password" {
name = "/prod/db/password"
with_decryption = true
}
resource "aws_db_instance" "main" {
password = data.aws_ssm_parameter.db_password.value
}
HashiCorp Vault
provider "vault" {
address = "https://vault.example.com"
# auth via token, AWS auth, etc.
}
data "vault_generic_secret" "db" {
path = "secret/data/prod/db"
}
resource "aws_db_instance" "main" {
password = data.vault_generic_secret.db.data["password"]
}
✅ Pattern 2: Generate + Store
ให้ Terraform generate secret + store ใน Secrets Manager:
# Generate
resource "random_password" "db" {
length = 32
special = true
lifecycle {
ignore_changes = [length, special] # don't regenerate
}
}
# Store
resource "aws_secretsmanager_secret" "db_password" {
name = "prod/db-password"
recovery_window_in_days = 30
kms_key_id = aws_kms_key.secrets.arn
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = random_password.db.result
}
# Use
resource "aws_db_instance" "main" {
username = "admin"
password = random_password.db.result
}
# Output reference, NOT value
output "db_secret_arn" {
value = aws_secretsmanager_secret.db_password.arn
}
→ Generated once, stored encrypted, retrievable via ARN
✅ Pattern 3: TF_VAR via Env
# Fetch จาก Vault ตอน apply
export TF_VAR_db_password=$(vault kv get -field=password secret/db)
terraform apply
variable "db_password" {
type = string
sensitive = true
}
resource "aws_db_instance" "main" {
password = var.db_password
}
→ Secret ไม่อยู่ใน code/Git แต่ยังอยู่ใน state
✅ Pattern 4: SOPS (Encrypted .tfvars)
SOPS = encrypt YAML/JSON ด้วย KMS/PGP
# Install
brew install sops
# Encrypt
sops --encrypt --kms 'arn:aws:kms:...' secrets.tfvars > secrets.tfvars.encrypted
# Commit encrypted file
git add secrets.tfvars.encrypted
ตอนใช้:
sops --decrypt secrets.tfvars.encrypted > secrets.tfvars
terraform apply -var-file=secrets.tfvars
rm secrets.tfvars
EC2 Instance Profile (No Secret Needed!)
แทน password — ใช้ IAM:
resource "aws_iam_instance_profile" "app" {
name = "app"
role = aws_iam_role.app.name
}
resource "aws_iam_role" "app" {
name = "app"
assume_role_policy = jsonencode({...})
}
resource "aws_iam_role_policy" "app" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = ["secretsmanager:GetSecretValue"]
Resource = aws_secretsmanager_secret.db_password.arn
}]
})
}
resource "aws_instance" "app" {
iam_instance_profile = aws_iam_instance_profile.app.name
# Application fetches secret via AWS SDK + IAM role
}
→ EC2 ดึง secret เอง ตอน runtime — Terraform ไม่ต้องรู้
Sensitive Variables/Outputs
variable "api_key" {
type = string
sensitive = true # ซ่อนจาก plan/apply log
}
output "db_password" {
value = random_password.db.result
sensitive = true # ซ่อนจาก output
}
แค่ซ่อนจาก log — state ยังเก็บ plain text
State Encryption
S3 + KMS
terraform {
backend "s3" {
bucket = "my-tfstate"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
kms_key_id = "alias/tfstate" # KMS key
dynamodb_table = "terraform-locks"
}
}
ดูเพิ่มใน Section 10: Sensitive Data in State
Detect Leaked Secrets
git-secrets
brew install git-secrets
git secrets --register-aws
git secrets --install
git secrets --scan
detect-secrets
pip install detect-secrets
detect-secrets scan > .secrets.baseline
Pre-commit hook:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
gitleaks
brew install gitleaks
gitleaks detect --source .
Secret Rotation
ตั้งให้ Secrets Manager rotate อัตโนมัติ:
resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db_password.id
rotation_lambda_arn = aws_lambda_function.rotate.arn
rotation_rules {
automatically_after_days = 30
}
}
→ Password rotate ทุก 30 วัน นอกเหนือ Terraform
Multi-Cloud Secret Managers
| Cloud | Service | Provider Resource |
|---|---|---|
| AWS | Secrets Manager | aws_secretsmanager_secret_version (data) |
| AWS | SSM Parameter Store | aws_ssm_parameter (data) |
| GCP | Secret Manager | google_secret_manager_secret_version (data) |
| Azure | Key Vault | azurerm_key_vault_secret (data) |
| HashiCorp | Vault | vault_generic_secret (data) |
.gitignore (สำคัญ!)
# Terraform
.terraform/
*.tfstate
*.tfstate.*
*.tfplan
# Variables
*.tfvars
!example.tfvars
secrets.auto.tfvars
*.auto.tfvars
# Backups
*.bak
*.backup
# Sensitive files
.env
*.pem
*.key
Best Practices
✅ DO:
- เก็บ secret ใน external secret manager
- Encrypt state at rest (S3 + KMS)
- IAM ที่จำกัด state access
- Rotate secrets automatically
- Use sensitive marker
- Detect secrets ก่อน commit (git-secrets)
- ใช้ EC2 instance profile แทน hard-coded credentials
❌ DON'T:
- ห้าม commit secret ใน .tf หรือ .tfvars
- ห้าม share secret ผ่าน Slack/email
- ห้ามคิดว่า sensitive = encrypted
- ห้ามใช้ provisioner รับ password
- ห้าม keep credentials ใน laptop forever
ตัวอย่าง: Production-Grade Setup
# 1. KMS for state + secrets
resource "aws_kms_key" "secrets" {
description = "Application secrets"
enable_key_rotation = true
deletion_window_in_days = 30
}
# 2. Generate password
resource "random_password" "db" {
length = 32
special = true
lifecycle {
ignore_changes = [length, special]
}
}
# 3. Store in Secrets Manager
resource "aws_secretsmanager_secret" "db" {
name = "prod/db-password"
kms_key_id = aws_kms_key.secrets.arn
}
resource "aws_secretsmanager_secret_version" "db" {
secret_id = aws_secretsmanager_secret.db.id
secret_string = random_password.db.result
}
# 4. Auto rotation
resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db.id
rotation_lambda_arn = aws_lambda_function.rotate_db.arn
rotation_rules {
automatically_after_days = 30
}
}
# 5. RDS uses password
resource "aws_db_instance" "main" {
password = random_password.db.result
lifecycle {
ignore_changes = [password] # rotation handles updates
}
}
# 6. Output ARN, not value
output "db_secret_arn" {
value = aws_secretsmanager_secret.db.arn
description = "Use: aws secretsmanager get-secret-value --secret-id $(terraform output -raw db_secret_arn)"
}
สรุป
- Layered defense: external secret manager + encrypted state + IAM + rotation
- ใช้ AWS Secrets Manager / Vault / SSM แทน hard-code
sensitive = trueซ่อนจาก log (ไม่ใช่ encryption)- ใช้ IAM instance profile แทน password เมื่อทำได้
- Detect leaks ด้วย git-secrets / detect-secrets / gitleaks
- Rotate secrets นอกเหนือ Terraform
ต่อไป → Compliance / Sentinel