Terraform Security Best Practices: ค้นป่วง Infrastructure as Code

Infrastructure as Code ช่วยให้การสร้างระบบเกิดเร็วและทำซ้ำได้ แต่ในขณะเดียวกันก็เพิ่มผิวการโจมตี (attack surface) เพราะโค้ดชุดเดียวสามารถ provisioning โครงสร้างพื้นฐานระดับร้อยเครื่องได้ด้วย apply เพียงครั้งเดียว ถ้าโค้ดหรือ credential หลุดรั่ว ผลกระทบจะลุกลามทันที บทความนี้จะสรุปหลัก security best practices ที่ผู้ดูแลระบบควรใช้ในทุกโครงการ IaC

บทความนี้ครอบคลุมทั้งฝั่งโค้ด (Git, module source, version pinning), ฝั่งรันไทม์ (credential, state, audit log) และฝั่ง pipeline (CI/CD, policy-as-code, secret scanner) โดยเน้นแนวทางที่สามารถเริ่มใช้ทีละข้อจนครบทั้งระบบ

1. ไม่เก็บ Secret ในโค้ดหรือ State

กฎข้อแรกและสำคัญที่สุด คือ ห้าม hardcode รหัสผ่าน, API key, private key หรือ certificate ในไฟล์ .tf, tfvars หรือ Git repository เด็ดขาด เพราะเมื่อโค้ดรั่วออกไปต้องหมุน credential ทุกชุดทันที ซึ่งเสียเวลาและเสี่ยงต่อระบบ production

  • ใช้ environment variable (เช่น TF_VAR_db_password) สำหรับค่าที่ไม่ควรปรากฏในไฟล์
  • ใช้ external secret manager — HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault
  • ถ้าต้องใช้ tfvars ให้เพิ่ม .gitignore และห้ามลืม pre-commit hook ตรวจก่อน commit
  • ระวัง state file — ค่าที่อ่านจาก data source จะถูก cache ใน state ทำให้ต้องเข้ารหัสทุกชั้น

2. เข้ารหัส State ทั้ง At-Rest และ In-Transit

State file ประกอบด้วยข้อมูล sensitive ทั้งหมดที่ provider คืนกลับมา รวมถึง password, connection string, private IP การจัดเก็บ state ต้องใช้ backend ที่รองรับการเข้ารหัสแบบ end-to-end

terraform {
  backend "s3" {
    bucket         = "tfstate-prod"
    key            = "app/terraform.tfstate"
    region         = "ap-southeast-1"
    encrypt        = true                 # SSE-S3 หรือ SSE-KMS
    kms_key_id     = "arn:aws:kms:..."    # ใช้ CMK ของทีมคุณ
    dynamodb_table = "tfstate-lock"
  }
}
  • เปิด versioning ของ S3 bucket เพื่อ recovery จากการถูกแก้ไขโดยไม่ตั้งใจ
  • ตั้ง bucket policy ป้องกัน public access และจำกัด IP/IAM ที่เข้าถึงได้
  • ใช้ state locking เสมอ (DynamoDB สำหรับ S3, Azure Blob lease, GCS object locking)
  • เปิด CloudTrail / audit log ของ bucket เพื่อตรวจย้อนหลังว่าใครเข้า state เมื่อใด

3. หลัก Least Privilege สำหรับ Runner

Runner ที่รัน apply ควรมีสิทธิ์เฉพาะ resource ที่โครงการนั้นต้องสร้างเท่านั้น ไม่ใช่สิทธิ์ admin ทั้งบัญชี ตัวอย่าง IAM policy ที่จำกัดตาม tag โครงการ

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["ec2:*", "s3:*", "iam:PassRole"],
    "Resource": "*",
    "Condition": {
      "StringEquals": {
        "aws:ResourceTag/project": "web-app"
      }
    }
  }]
}

สำหรับ pipeline ใช้ short-lived credential เช่น OIDC federation จาก GitHub Actions ไป AWS แทนการเก็บ access key ถาวร วิธีนี้ไม่ต้อง rotate key และจำกัด role ได้ชัดเจน

4. Pin Provider และ Module Version

การปล่อยให้ provider/module อัปเดตอัตโนมัติเป็นช่องทางหนึ่งของ supply-chain attack ควร pin version ให้ชัดเจนและ commit ไฟล์ .terraform.lock.hcl เข้า Git เพื่อให้ทุกเครื่องติดตั้ง binary ชุดเดียวกัน

terraform {
  required_version = "~> 1.10"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.60"     # pin minor
    }
  }
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.8.1"           # pin exact
  # ...
}

เวลา init ให้ใช้ -upgrade อย่างตั้งใจเท่านั้น ไม่ปล่อยให้ pipeline upgrade อัตโนมัติ และตรวจสอบ checksum ใน lock file ทุกครั้งก่อน apply

5. ใช้ Trusted Module Source

  • ชอบใช้ Public Registry ของ HashiCorp หรือ registry ภายในองค์กรมากกว่า Git URL ปลายทางที่ไม่น่าเชื่อถือ
  • ตรวจสอบว่า module มีการ maintain และมี issue tracker ใช้งานจริง
  • สำหรับ module ภายในองค์กรให้ sign ด้วย GPG หรือ release ผ่าน CI ที่มี audit trail
  • อย่าอ้าง module จาก Git branch (เช่น ref=main) เพราะเนื้อหาเปลี่ยนได้ตลอด ใช้ tag หรือ commit SHA แบบ pin ไปเลย

6. Policy-as-Code และ Pre-apply Check

เครื่องมือ policy-as-code ช่วยบังคับมาตรฐานความปลอดภัยและ compliance โดยอัตโนมัติก่อนที่โค้ดจะถูก apply ตัวอย่างเครื่องมือที่นิยมใช้:

  • Checkov / tfsec / Trivy — scan HCL file หา misconfiguration (เช่น bucket public, security group เปิดทุก port)
  • OPA + Conftest — เขียน policy ด้วย Rego สำหรับทีมที่ใช้ OPA อยู่แล้ว
  • Sentinel — ของ Terraform Enterprise/Cloud รองรับ policy แบบ soft-mandatory/hard-mandatory
  • terraform validate + TFLint — ตรวจ syntax และ lint rules ทั่วไป
# ตัวอย่าง pipeline step
terraform init -backend=false
terraform validate
tflint --format=compact
checkov -d . --quiet --soft-fail=false
terraform plan -out=tfplan.binary
conftest test tfplan.json --policy policies/

7. แยก Workspace ตาม Environment และ Blast Radius

การรวม dev/staging/prod ไว้ใน workspace เดียวอาจทำให้ apply ผิดไปยังสภาพแวดล้อมผิดได้โดยไม่ตั้งใจ แนวทางที่ปลอดภัยกว่าคือ แยก state และ credential ตาม environment และตาม bounded context

  • แยก directory ต่อ environment (envs/dev, envs/prod) แต่ละ dir มี backend key ของตัวเอง
  • ใช้ IAM role ต่างกันสำหรับแต่ละ environment เพื่อให้ apply ข้ามไม่ได้
  • จำกัด blast radius ด้วยการแตก module ใหญ่เป็นหลาย root module ตาม lifecycle (network, data, compute)
  • ให้ prod ต้องผ่าน manual approval step ใน pipeline

8. Audit Log และ Drift Detection

การตรวจสอบย้อนหลังเป็นหัวใจของ security หาก apply ถูกรันนอกช่องทางปกติ (เช่น มีคน apply จากเครื่องตัวเอง, คนเข้าไปแก้ resource ผ่าน console) ต้องตรวจเจอได้เร็ว

  • เปิด cloud audit log (CloudTrail, Activity Log, Cloud Audit Logs) ทุกบัญชี
  • ใช้ Terraform Cloud/Enterprise audit trail หรือเก็บ plan artifact ใน CI ของคุณเอง
  • รัน terraform plan ตาม schedule (เช่น nightly) แล้ว alert ถ้ามี diff — เรียกว่า drift detection
  • เปิด MFA สำหรับทุกบัญชีที่มีสิทธิ์ apply

9. ปกป้อง Pipeline เอง

pipeline ที่รัน IaC คือ high-value target เพราะมีสิทธิ์ระดับ admin การรักษาความปลอดภัยของ pipeline เองจึงสำคัญ

  • ใช้ protected branch + required review — ห้าม push ตรงเข้า main
  • แยก runner สำหรับ prod ออกจาก dev ไม่ใช้ runner pool เดียวกัน
  • เปิด signed commit (GPG / Sigstore) เพื่อยืนยันตัวผู้เขียน
  • รัน secret scanner (gitleaks, trufflehog) ใน pre-commit และ pipeline
  • Rotate PAT, runner token, OIDC trust relationship ตามระยะเวลา

10. Sensitive Output และการแสดงผล Plan

การ plan มักสร้าง log ที่มี credential ปนอยู่ ถ้า log นี้เก็บใน CI artifact หรือส่งเข้า monitoring จะทำให้ข้อมูลหลุด การทำเครื่องหมาย sensitive จึงช่วยปกปิดค่าใน plan/apply output

variable "api_key" {
  type      = string
  sensitive = true
}

output "endpoint" {
  value     = aws_lambda_function.api.invoke_arn
  sensitive = true   # ป้องกันการแสดงใน plan
}

นอกจากนี้อย่าเก็บ plan log เป็น public artifact และจำกัดสิทธิ์การดู pipeline log เฉพาะผู้ที่ต้องการเท่านั้น

11. Backup และ Disaster Recovery

  • เปิด versioning บน S3/GCS/Azure Blob ที่เก็บ state
  • Backup state file แบบ offsite สัปดาห์ละครั้งเป็นอย่างน้อย
  • ทดสอบกู้คืน state จาก backup จริง ๆ ทุกไตรมาส
  • เก็บสำเนา Git repository ใน mirror อื่น (เช่น internal GitLab mirror) เผื่อ GitHub ล่ม

12. Checklist ประจำโครงการ

  • ไม่มี secret ในโค้ดและ tfvars (ตรวจด้วย gitleaks)
  • Backend state เปิด encryption + locking + versioning
  • Runner ใช้ OIDC หรือ short-lived credential ไม่มี static key
  • Pin provider/module version + commit lock file
  • ทุก PR ต้องผ่าน validate + lint + scan (TFLint, Checkov, tfsec)
  • Prod ต้องมี manual approval และ plan file artifact เพื่อตรวจสอบ
  • เปิด cloud audit log + drift detection nightly
  • มี runbook สำหรับการหมุน credential และกู้คืน state

สรุป

Security ของ Infrastructure as Code ไม่ได้มาจากเครื่องมือตัวเดียว แต่เป็นผลรวมของวินัยหลายชั้น ได้แก่ การจัดการ secret ผ่าน vault, เข้ารหัส state, ใช้ short-lived credential, pin version, ใช้ policy-as-code, แยก environment, เปิด audit และทดสอบ disaster recovery การเริ่มต้นควรใช้ checklist ข้างต้นเป็น baseline แล้วค่อย ๆ เพิ่มเครื่องมืออัตโนมัติใน pipeline

เมื่อพื้นฐาน security แข็งแรง การขยาย IaC ไปสู่ทีมใหญ่หรือ multi-cloud จะปลอดภัยและไม่สะดุด เพราะทุกการเปลี่ยนแปลงผ่าน pipeline เดียวกัน, มี audit ย้อนหลังได้ และถ้าเกิดเหตุก็สามารถ rollback จาก state backup ได้ภายในเวลาสั้น การลงทุนด้าน security ตั้งแต่วันแรกจึงคุ้มค่าเสมอเมื่อเทียบกับต้นทุนของการกู้คืนระบบหลังเกิดเหตุ