เมื่อเขียนโค้ด IaC มักจะต้องจัดการกับข้อมูลที่อ่อนไหว เช่น API key, password, certificate, database credentials หากไม่ระวังอาจถูก commit เข้า Git หรือ log ซึ่งจะทำให้ข้อมูลรั่วไหลและกลายเป็นช่องโหว่ด้านความปลอดภัยที่สำคัญ บทความนี้จะอธิบายกลไกที่ HCL มีให้สำหรับป้องกัน sensitive data และ best practice ในการจัดการ secret อย่างปลอดภัย
การจัดการ sensitive data ต้องคิดทั้งตอนเขียน (ป้องกันไม่ให้ commit), ตอน plan/apply (ป้องกันไม่ให้โผล่ใน log), และตอนเก็บ state (ป้องกันไม่ให้รั่วผ่านไฟล์ .tfstate) ในแต่ละจุดมี technique และ tool ที่เหมาะสมต่างกัน
Sensitive Variables
การประกาศ variable ด้วย flag sensitive = true จะบอกให้เครื่องมือซ่อนค่าจาก output ของ plan และ apply ทำให้ไม่เห็นค่าจริงใน CLI และ log ของ CI:
variable "db_password" {
description = "Database admin password"
type = string
sensitive = true
}
variable "api_key" {
description = "Third-party API key"
type = string
sensitive = true
}
เมื่อรัน plan ผลลัพธ์จะแสดงเป็น (sensitive value) แทนค่าจริง:
resource "aws_db_instance" "main" {
password = (sensitive value)
...
}
ค่า sensitive จะ propagate
หาก local variable หรือ expression ใช้ sensitive variable ค่า local นั้นจะถูกมอง sensitive ตามไปด้วย:
locals {
db_conn = "postgresql://admin:${var.db_password}@localhost/app"
# local นี้จะ sensitive อัตโนมัติเพราะใช้ sensitive var
}
Sensitive Outputs
Output ที่มีค่าที่อ่อนไหวต้องประกาศ sensitive = true เช่นกัน ไม่เช่นนั้นจะ error ตอน apply:
output "db_endpoint" {
value = aws_db_instance.main.endpoint
sensitive = false # endpoint ไม่ใช่ secret
}
output "db_connection_string" {
value = local.db_conn
sensitive = true # มี password ปน
}
การเรียกดูค่า sensitive output ต้องใช้ -json:
# ไม่แสดงค่า
terraform output db_connection_string
# แสดงค่าจริง (ใช้เมื่อจำเป็น)
terraform output -json db_connection_string | jq -r .
tfvars และ .gitignore
ไฟล์ terraform.tfvars และ *.auto.tfvars มักจะมี secret อยู่ห้าม commit เข้า Git เด็ดขาด เพิ่ม .gitignore เพื่อกัน:
# .gitignore
*.tfvars
*.tfvars.json
!example.tfvars # อนุญาต example ที่ใช้ dummy value
!terraform.tfvars.example
# state files
*.tfstate
*.tfstate.*
*.tfstate.backup
# crash logs
crash.log
crash.*.log
# override files
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# CLI configuration
.terraformrc
terraform.rc
แทนที่จะเก็บ terraform.tfvars ใน Git ให้เก็บ terraform.tfvars.example ที่มีเฉพาะตัวแปรและ dummy value เพื่อเป็น template สำหรับคนอื่นคัดลอก
การส่ง Secret ผ่าน Environment Variable
วิธีหนึ่งในการส่ง secret โดยไม่ต้อง commit ไฟล์ คือใช้ environment variable ชื่อ TF_VAR_<name>:
# ตั้ง environment variable
export TF_VAR_db_password='SecurePass123!'
export TF_VAR_api_key='sk-xxxxx'
# รัน
terraform apply
วิธีนี้เหมาะกับ CI/CD ที่มี secret manager built-in เช่น GitHub Actions secrets, GitLab CI variables, AWS Systems Manager:
# .github/workflows/terraform.yml
jobs:
apply:
runs-on: ubuntu-latest
steps:
- name: Apply
env:
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
TF_VAR_api_key: ${{ secrets.API_KEY }}
run: terraform apply -auto-approve
State File Security
ไฟล์ state เก็บค่า resource ทุกอย่างรวมถึง sensitive attribute โดยไม่ mask กล่าวคือ password ที่ generate โดย resource จะปรากฏเป็น plaintext ใน state ดังนั้นการจัดการ state อย่างปลอดภัยคือเรื่องที่สำคัญไม่น้อยกว่า source code
Remote State + Encryption
ใช้ remote backend ที่รองรับ encryption at rest เสมอ:
terraform {
backend "s3" {
bucket = "my-tfstate-bucket"
key = "prod/infra.tfstate"
region = "ap-southeast-1"
encrypt = true # SSE-S3
kms_key_id = "alias/tfstate-key" # ใช้ CMK (optional)
dynamodb_table = "tfstate-locks"
}
}
IAM Access Control
จำกัด IAM permissions ให้ S3 bucket ของ state:
- Bucket policy: block public access เสมอ
- Versioning: เปิด เพื่อกู้ state เก่าได้หากถูก corrupt
- MFA Delete: ต้องยืนยัน MFA ก่อนลบ version
- Separate bucket per environment: prod, staging, dev แยก bucket/key
- Audit log: เปิด CloudTrail หรือ S3 access log เพื่อ track การเข้าถึง
การใช้ External Secret Manager
วิธีที่ปลอดภัยที่สุดคือไม่เก็บ secret ใน HCL เลย ให้ดึงผ่าน data source จาก secret manager เช่น HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager:
AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/db-admin"
}
locals {
db_creds = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string)
}
resource "aws_db_instance" "main" {
username = local.db_creds.username
password = local.db_creds.password
# ...
}
HashiCorp Vault
provider "vault" {
address = "https://vault.example.com"
}
data "vault_generic_secret" "db" {
path = "secret/data/prod/database"
}
resource "aws_db_instance" "main" {
username = data.vault_generic_secret.db.data["username"]
password = data.vault_generic_secret.db.data["password"]
}
ข้อดี: ไม่ต้องใส่ค่า secret ใน tfvars หรือ environment variable ใด ๆ secret หมุนเวียนโดยอัตโนมัติโดย secret manager และ audit log ชัดเจน
ensurenonempty และ ephemeral resources
ตั้งแต่เวอร์ชัน 1.10+ ได้เพิ่ม ephemeral resource และ value ที่ใช้ได้เฉพาะ runtime โดยไม่เก็บใน state:
ephemeral "aws_secretsmanager_secret_version" "db" {
secret_id = "prod/db-admin"
}
resource "aws_db_instance" "main" {
password = ephemeral.aws_secretsmanager_secret_version.db.secret_string
# ค่าจะไม่ถูกเก็บใน state เพราะเป็น ephemeral
}
Ephemeral value ทำให้ state ปลอดภัยขึ้นเพราะไม่มี plaintext secret ค้างอยู่ในไฟล์ .tfstate อีกต่อไป
Anti-pattern ที่ควรหลีกเลี่ยง
- Hard-code secret ใน .tf: ห้ามเขียน password/key ลงไฟล์
.tfโดยตรง — commit เข้า Git = เสียแน่นอน - Commit tfvars: อย่า commit
terraform.tfvarsที่มี secret — ใช้.exampleแทน - Local state บน shared disk: อย่าเก็บ state ในเครื่อง dev หรือ shared folder — ใช้ remote backend เสมอ
- Plaintext state file: อย่า disable encryption ใน backend — เปิด
encrypt = trueทุกครั้ง - Output secret โดยไม่ mark sensitive: แม้จะเจตนาแสดงค่า ก็ต้องประกาศ
sensitive = trueเพื่อไม่ให้ log รั่ว - Log การ apply เต็มรูปแบบ: debug log อาจเผลอ print sensitive value — ตั้ง
TF_LOGเฉพาะตอนจำเป็น
ตรวจจับ Secret ด้วย Pre-commit Hook
ติดตั้ง tool ตรวจจับ secret ก่อน commit เช่น detect-secrets, gitleaks, trufflehog:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
Tool เหล่านี้จะ scan หา pattern ที่ดูเหมือน secret (เช่น AWS key, JWT, private key) และบล็อก commit เมื่อเจอ ช่วยป้องกันการ commit โดยไม่ตั้งใจได้มาก
Checklist ด้าน Security
- ทุก variable ที่เก็บ credential ต้อง
sensitive = true - ทุก output ที่เกี่ยวข้องกับ credential ต้อง
sensitive = true - ใช้ remote backend + encryption at rest เสมอ
- เปิด versioning และจำกัด IAM ของ state bucket
.gitignoreครอบคลุม*.tfvars,*.tfstate*,crash.log- มี pre-commit hook ตรวจจับ secret
- ใช้ secret manager เป็น source of truth ไม่ใช่ไฟล์
tfvars - หมุนเวียน credential อย่างสม่ำเสมอ (rotate)
- รัน
terraform planด้วย service account ที่มี permission น้อยที่สุด - Audit ใครรัน apply เมื่อไร — เก็บ log ที่ CI
สรุป
การจัดการ sensitive data ในโค้ด IaC เริ่มต้นจากการประกาศ sensitive = true บน variable และ output ที่เกี่ยวข้อง ใช้ .gitignore ป้องกันการ commit ไฟล์ tfvars และ state จากนั้นย้าย state ไปอยู่บน remote backend ที่เข้ารหัส และจำกัดสิทธิการเข้าถึงด้วย IAM
ขั้นสุดท้ายคือไม่เก็บ secret ในโค้ดเลย โดยใช้ secret manager (Vault, AWS Secrets Manager, Azure Key Vault) เป็น source of truth และใช้ ephemeral resource ใน version ใหม่เพื่อไม่ให้ secret ค้างใน state เลย การผสมทุกวิธีรวมกันจะทำให้ infrastructure code ปลอดภัยทั้งตอนพัฒนา ตอนรัน และตอนเก็บ state อย่างสมบูรณ์

