Skip to main content

Sensitive Data in State

State file มักมี secrets (passwords, keys, tokens) เป็น plain text — เรียนวิธีจัดการ + ป้องกัน

ปัญหา: Secrets ใน State Plain Text

ตัวอย่าง:

resource "aws_db_instance" "main" {
username = "admin"
password = "supersecret123" # ← state เก็บค่านี้!
}

State file:

{
"resources": [{
"type": "aws_db_instance",
"instances": [{
"attributes": {
"username": "admin",
"password": "supersecret123" // plain text!
}
}]
}]
}

→ ใครเข้าถึง state file ได้ = เห็น password

Threats

ThreatImpact
State file commit ลง Git🔴 ทุกคนใน repo เห็น
S3 bucket public🔴 ทั่วโลกเห็น
Local laptop hacked🔴 secret leak
State sent in PR🔴 secret in CI logs

Mitigations: Multiple Layers

Layer 1: Encrypt at Rest

S3 Server-Side Encryption

resource "aws_s3_bucket_server_side_encryption_configuration" "tfstate" {
bucket = aws_s3_bucket.tfstate.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms" # ← KMS encryption
kms_master_key_id = aws_kms_key.tfstate.arn
}
}
}

resource "aws_kms_key" "tfstate" {
description = "Encryption for Terraform state"
enable_key_rotation = true
deletion_window_in_days = 30
}

Backend ระบุ encryption

terraform {
backend "s3" {
bucket = "my-tfstate"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true # ⭐
kms_key_id = "alias/my-tfstate-key" # ⭐
dynamodb_table = "terraform-locks"
}
}

Layer 2: Encrypt in Transit

S3 + DynamoDB ใช้ HTTPS อัตโนมัติ — แต่ enforce policy:

resource "aws_s3_bucket_policy" "tfstate" {
bucket = aws_s3_bucket.tfstate.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Sid = "DenyInsecureTransport"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.tfstate.arn,
"${aws_s3_bucket.tfstate.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}]
})
}

Layer 3: IAM Restriction

resource "aws_iam_policy" "tfstate_access" {
name = "tfstate-access"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "${aws_s3_bucket.tfstate.arn}/*"
},
{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
]
Resource = aws_dynamodb_table.tfstate_lock.arn
}
]
})
}

→ มอบสิทธิ์เฉพาะคน/role ที่จำเป็น

Layer 4: Use External Secret Manager

แทนที่จะให้ Terraform manage secret — fetch จากที่อื่นทุกครั้ง

# อ่าน secret จาก AWS Secrets Manager (ไม่ใช่ create)
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
}

→ State เก็บ ARN reference, ไม่เก็บค่าจริง... WAIT — จริงๆ Terraform เก็บค่าใน state เพราะ argument ตอน apply

แก้: ใช้ ignore_changes + write-only

resource "aws_db_instance" "main" {
username = "admin"
password = data.aws_secretsmanager_secret_version.db_password.secret_string

lifecycle {
ignore_changes = [password]
}
}

Layer 5: Rotate ผ่าน External Tool

ให้ AWS Secrets Manager rotate password เอง:

resource "aws_secretsmanager_secret_rotation" "db" {
secret_id = aws_secretsmanager_secret.db.id
rotation_lambda_arn = aws_lambda_function.rotate.arn

rotation_rules {
automatically_after_days = 30
}
}

→ Password เปลี่ยนนอกเหนือ Terraform — state ไม่ track

Sensitive Marker

ทำให้ Terraform ซ่อนค่าจาก plan/apply log:

variable "db_password" {
type = string
sensitive = true # ← ซ่อนจาก log
}

output "db_endpoint" {
value = aws_db_instance.main.endpoint
sensitive = true # ← ซ่อนจาก output
}
Sensitive ≠ Encrypted
  • Sensitive = ซ่อนจาก log/output
  • ค่ายังเก็บ plain text ใน state

ต้องใช้ encryption + access control ปกป้อง state file ตัวเอง

Detect Sensitive Leak

ใช้ tools:

git-secrets (AWS)

brew install git-secrets
git secrets --register-aws --global
git secrets --install

→ เตือนถ้าเผลอ commit AWS credential

detect-secrets (Yelp)

pip install detect-secrets
detect-secrets scan

→ Scan repo หา hard-coded secret

Pre-commit Hook

.pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets

ตัวอย่าง: Secure Pattern

main.tf
# Generate password (ครั้งเดียว)
resource "random_password" "db" {
length = 32
special = true

lifecycle {
ignore_changes = [length, special] # อย่าสร้างใหม่
}
}

# Store ใน Secrets Manager
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 in RDS
resource "aws_db_instance" "main" {
identifier = "prod-db"
username = "admin"
password = random_password.db.result # state มีค่านี้

lifecycle {
ignore_changes = [password]
}
}

# Output reference, not value
output "db_secret_arn" {
value = aws_secretsmanager_secret.db_password.arn
description = "Get password: aws secretsmanager get-secret-value --secret-id $(terraform output -raw db_secret_arn)"
}

Best Practices

✅ DO:
- Encrypt state (S3 + KMS)
- IAM policy ที่จำกัดเฉพาะคน/role ที่จำเป็น
- Use external secret manager (Vault, Secrets Manager)
- Sensitive variables/outputs (ซ่อนจาก log)
- Audit access via CloudTrail
- Rotate secrets นอกเหนือ Terraform

❌ DON'T:
- ห้าม commit state file
- ห้าม hard-code password ใน .tf
- ห้าม share state file ผ่าน email/Slack
- ห้ามคิดว่า sensitive = encrypted

หาก State Leak แล้วทำยังไง?

  1. Rotate ทุก secret ที่อยู่ใน state (assume compromised)
  2. Audit access logs (CloudTrail) ใครดู
  3. Delete leaked file (S3 versions, Git history with git filter-repo)
  4. Block source ของ leak
  5. Re-deploy infrastructure ที่ผูกกับ secret เก่า

สรุป

  • State มี secrets เป็น plain text
  • ป้องกัน 5 layers: encrypt at rest, encrypt in transit, IAM, secret manager, rotation
  • Sensitive marker ซ่อนจาก log แต่ไม่ encrypt state
  • ใช้ external secret manager (Vault, Secrets Manager) แทนเก็บใน state
  • Detect leak ด้วย git-secrets / detect-secrets / pre-commit
  • Plan recovery — รู้ว่าจะทำยังไงถ้า leak

ต่อไป → Best Practices for State