การเขียน Infrastructure as Code (IaC) ให้มีคุณภาพและปลอดภัยไม่ใช่แค่เรื่องของการเขียนให้ทำงานได้ แต่ต้องตรวจสอบความถูกต้องของโครงสร้าง (syntax), ตรวจสอบความสอดคล้องกับ best practices และค้นหาช่องโหว่ด้านความปลอดภัยก่อนที่โค้ดจะถูกนำไป deploy การใช้เครื่องมือตรวจสอบอัตโนมัติ (validation และ linting) จึงเป็นขั้นตอนสำคัญที่ควรทำเป็นประจำ โดยเฉพาะในทีมที่ต้องดูแล infrastructure ขนาดใหญ่
บทความนี้จะอธิบายเครื่องมือหลักที่ใช้ตรวจสอบและยกระดับคุณภาพของโค้ด IaC ได้แก่ terraform validate, TFLint, Checkov และ Conftest (OPA) พร้อมวิธีการติดตั้ง ใช้งาน และการนำไปผนวกเข้ากับ CI pipeline เพื่อให้การตรวจสอบเป็นส่วนหนึ่งของกระบวนการพัฒนา
ทำไมต้องตรวจสอบและทำ Linting
การตรวจสอบโค้ด IaC มีประโยชน์หลายประการ ทั้งในเชิงคุณภาพและความปลอดภัย ได้แก่:
- จับข้อผิดพลาดก่อน apply: syntax error, argument ที่ไม่ถูกต้อง, reference ที่หาไม่เจอ
- บังคับใช้มาตรฐานทีม: เช่น naming convention, ห้ามใช้ hardcoded credentials
- ตรวจสอบความปลอดภัย: หา misconfigurations เช่น S3 bucket เปิดสาธารณะ, security group เปิด port ทั้งหมด
- ลดค่าใช้จ่าย: เตือนเมื่อใช้ resource ที่ไม่คุ้มค่า หรือ instance type ที่ใหญ่เกินไป
- Enforce policy: บังคับให้ทุก resource มี tag, ใช้ region ที่กำหนด ฯลฯ
terraform validate
คำสั่ง validate เป็นเครื่องมือในตัวของ Terraform CLI ที่ตรวจสอบ syntax และความถูกต้องของ configuration โดยไม่ต้องเชื่อมต่อกับ provider หรือ remote state ใช้รูปแบบดังนี้:
# ต้อง init ก่อน (เพื่อให้ validate เห็น provider schema)
terraform init -backend=false
# ตรวจสอบ configuration
terraform validate
# JSON output (ใช้ใน CI)
terraform validate -json
สิ่งที่ validate ตรวจสอบได้:
- HCL syntax ถูกต้องหรือไม่
- Argument ที่ใช้มีอยู่ใน resource schema หรือไม่
- Reference (เช่น
var.xxx,local.yyy) มีอยู่จริงหรือไม่ - Type ของ value ตรงกับที่ resource ต้องการหรือไม่
- Required argument ครบหรือไม่
ข้อจำกัดของ validate คือไม่สามารถตรวจสอบความสอดคล้องกับ infrastructure จริง (ต้องใช้ plan) และไม่ครอบคลุมถึง best practices หรือ security (ต้องใช้ linter เพิ่มเติม)
ตัวอย่าง Output
$ terraform validate
Success! The configuration is valid.
# กรณีมี error:
$ terraform validate
Error: Reference to undeclared input variable
on main.tf line 15, in resource "aws_instance" "web":
15: instance_type = var.instance_typ
An input variable with the name "instance_typ" has not been declared.
TFLint: Linter สำหรับ Terraform
TFLint เป็น linter ที่เจาะลึกกว่า validate สามารถตรวจจับ error ที่ validate จับไม่ได้ เช่น instance type ที่ไม่มีอยู่จริง, deprecated argument, unused declaration เป็นต้น นอกจากนี้ยังรองรับ plugin เฉพาะ provider (AWS, Azure, Google Cloud)
การติดตั้ง TFLint
# macOS (Homebrew)
brew install tflint
# Linux (script)
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
# ตรวจสอบเวอร์ชัน
tflint --version
การใช้งานพื้นฐาน
# รันในไดเรกทอรีปัจจุบัน
tflint
# รันพร้อม JSON output (ใช้ใน CI)
tflint --format json
# รัน recursive ทุกไดเรกทอรี
tflint --recursive
# ระบุ config file
tflint --config=custom.tflint.hcl
การใช้งาน Plugin เฉพาะ Provider
TFLint มี plugin สำหรับผู้ให้บริการ cloud หลักทุกราย สร้างไฟล์ .tflint.hcl ที่ root ของ project:
plugin "aws" {
enabled = true
version = "0.30.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
plugin "terraform" {
enabled = true
preset = "recommended"
}
config {
call_module_type = "local"
force = false
}
# ปิดกฎบางข้อ
rule "terraform_naming_convention" {
enabled = true
format = "snake_case"
}
# ไม่สน warning บางตัว
rule "terraform_unused_declarations" {
enabled = false
}
หลังสร้าง config แล้วต้อง init plugin ก่อนใช้งาน:
tflint --init
tflint
กฎที่นิยมเปิดใน TFLint
terraform_deprecated_interpolation— เตือนเมื่อใช้ syntax${var.xxx}แบบเก่าterraform_unused_declarations— เตือนเมื่อมี variable หรือ local ที่ไม่ถูกใช้terraform_documented_variables— บังคับให้ทุก variable มี descriptionterraform_naming_convention— บังคับให้ใช้ snake_caseaws_instance_invalid_type— เตือนเมื่อใช้ instance type ที่ไม่มีaws_iam_policy_document_gov_friendly_arns— ตรวจสอบ ARN format
Checkov: Security Scanner
Checkov เป็นเครื่องมือ static analysis ที่ออกแบบมาเพื่อหาช่องโหว่ด้านความปลอดภัยและ compliance โดยตรง รองรับ Terraform, CloudFormation, Kubernetes, Dockerfile และอื่นๆ มีกฎมากกว่า 1,000 ข้อที่ครอบคลุม CIS Benchmark, HIPAA, PCI-DSS, SOC 2
การติดตั้ง Checkov
# ติดตั้งผ่าน pip
pip install checkov
# ตรวจสอบเวอร์ชัน
checkov --version
การใช้งาน
# สแกนไดเรกทอรี
checkov --directory .
# สแกนไฟล์เดียว
checkov --file main.tf
# JSON output
checkov --directory . --output json
# ข้ามกฎบางข้อ
checkov --directory . --skip-check CKV_AWS_20,CKV_AWS_23
# รันเฉพาะ framework
checkov --directory . --framework terraform
ตัวอย่างผลการสแกน
Check: CKV_AWS_20: "Ensure S3 bucket has block public ACLS enabled"
FAILED for resource: aws_s3_bucket.data
File: /main.tf:45-52
Guide: https://docs.bridgecrew.io/docs/s3_1-acl-read-permissions
45 | resource "aws_s3_bucket" "data" {
46 | bucket = "my-data-bucket"
47 | acl = "public-read"
48 | }
การ Skip Check แบบ Inline
บางกรณีที่ไม่สามารถ fix ได้ตอนนี้ สามารถ skip เฉพาะ resource ได้ด้วย comment:
resource "aws_s3_bucket" "public_assets" {
#checkov:skip=CKV_AWS_20: public access required for CDN origin
bucket = "my-cdn-origin"
acl = "public-read"
}
Conftest และ OPA สำหรับ Policy
Open Policy Agent (OPA) เป็น framework สำหรับเขียน policy แบบยืดหยุ่น ใช้ภาษา Rego ส่วน Conftest คือ CLI ที่ทำให้ OPA ใช้งานกับ configuration files ได้ง่ายขึ้น เหมาะกับการบังคับใช้ policy เฉพาะองค์กรที่ linter ทั่วไปไม่ครอบคลุม
ตัวอย่าง Policy ด้วย Rego
สร้างไฟล์ policy/deny.rego เพื่อบังคับว่าทุก EC2 instance ต้องมี tag “Environment”:
package main
deny[msg] {
resource := input.resource.aws_instance[name]
not resource.tags.Environment
msg := sprintf("EC2 instance '%s' ต้องมี Environment tag", [name])
}
deny[msg] {
resource := input.resource.aws_instance[name]
not resource.tags.Owner
msg := sprintf("EC2 instance '%s' ต้องมี Owner tag", [name])
}
การรัน Conftest กับ Terraform Plan
# สร้าง plan เป็น JSON
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
# รัน Conftest ทดสอบ policy
conftest test tfplan.json --policy ./policy
การผนวกเข้ากับ CI Pipeline
เพื่อให้การตรวจสอบเกิดขึ้นอัตโนมัติทุกครั้งที่มี commit หรือ pull request ควรนำเครื่องมือทั้งหมดไปรันใน CI pipeline ตัวอย่างต่อไปนี้คือ GitHub Actions workflow ที่ครอบคลุม validate, fmt, TFLint และ Checkov:
name: IaC Quality Checks
on:
pull_request:
paths:
- '**.tf'
- '**.tfvars'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.6.0
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validate
- name: Setup TFLint
uses: terraform-linters/setup-tflint@v4
- name: TFLint Init
run: tflint --init
- name: Run TFLint
run: tflint --recursive --format compact
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: cli
soft_fail: false
การใช้ Pre-commit Hook
อีกทางเลือกคือรันตรวจสอบที่เครื่อง developer ก่อน push ผ่าน pre-commit framework ไฟล์ .pre-commit-config.yaml มีรูปแบบดังนี้:
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.83.5
hooks:
- id: terraform_fmt
- id: terraform_validate
- id: terraform_tflint
args:
- --args=--recursive
- id: terraform_checkov
args:
- --args=--quiet
- --args=--framework=terraform
# ติดตั้ง hook
pre-commit install
# รัน manual
pre-commit run --all-files
ผลของ Finding จาก Linter ที่พบบ่อย
เมื่อรัน linter ครั้งแรกกับ project ที่มีอยู่แล้ว มักจะเจอข้อผิดพลาดที่เกิดซ้ำบ่อย ต่อไปนี้คือรายการ finding ที่พบบ่อยและวิธีแก้:
- S3 bucket ไม่เปิดใช้ encryption → เพิ่ม
server_side_encryption_configuration - S3 bucket ไม่เปิด versioning → เพิ่ม
versioning { enabled = true } - Security group เปิด 0.0.0.0/0 ที่ port 22 → จำกัดเฉพาะ IP ของทีม
- RDS ไม่เปิด encryption at rest → ตั้ง
storage_encrypted = true - EC2 ไม่บังคับใช้ IMDSv2 → ตั้ง
metadata_options { http_tokens = "required" } - IAM policy กว้างเกินไป (Action = “*”) → ระบุ action เฉพาะที่จำเป็น
- Variable ไม่มี description → เพิ่ม
descriptionให้ทุก variable - Deprecated interpolation syntax → เปลี่ยนจาก
"${var.x}"เป็นvar.x
การจัดการกับ False Positive
linter ทุกตัวจะมีบางกรณีที่แจ้ง warning ที่ไม่เกี่ยวข้องกับ context ของเรา (false positive) ควรมีแนวทางจัดการที่ชัดเจน:
- ปิด rule ระดับ project — ใช้
.tflint.hclหรือ--skip-checkของ Checkov เมื่อ rule ไม่เหมาะกับองค์กร - Skip ระดับ resource — ใช้ inline comment (
#checkov:skip=...) เมื่อเป็นข้อยกเว้นเฉพาะจุด ต้องมีเหตุผลอธิบาย - Review เป็นระยะ — ตรวจ skip-list ทุก 3–6 เดือนว่ายังจำเป็นหรือไม่ เพื่อไม่ให้กลายเป็น technical debt
- สร้าง custom rule — ถ้า business logic เฉพาะ เขียน policy ด้วย Conftest/OPA แทนที่จะ skip
Best Practices สำหรับการตรวจสอบ IaC
- รันตรวจสอบในหลายจุด: pre-commit ที่เครื่อง dev, CI ตอน PR, และก่อน deploy
- ตั้ง gate ให้ชัดเจน: fail build เมื่อเจอ critical finding, warn เมื่อเจอ medium
- จัดเรียงลำดับการรัน: fmt → validate → TFLint → Checkov → Conftest เพื่อให้ error ง่าย ๆ แจ้งก่อน
- เริ่มจาก baseline: บันทึก finding เดิมไว้ แล้ว fail เฉพาะ finding ใหม่ ช่วยค่อย ๆ clean up
- เขียน policy as code: policy ควรอยู่ใน repo เดียวกับโค้ด มี review process เหมือนโค้ดทั่วไป
- Document exception: ทุกกรณีที่ skip ต้องมี comment อธิบายและ ticket reference
- อัปเดต tool เป็นประจำ: rule ใหม่ ๆ เพิ่มเข้ามาตลอด ควรอัปเดต version อย่างน้อยทุก 2–3 เดือน
- สอนทีมใช้ tool: ให้ทุกคนเข้าใจความหมายของ finding ไม่ใช่แค่ ignore เพื่อให้ผ่าน CI
เปรียบเทียบเครื่องมือ
| เครื่องมือ | จุดเด่น | เหมาะกับ |
|---|---|---|
| terraform validate | built-in, เร็ว, ตรวจ syntax | Baseline check ทุก project |
| TFLint | ตรวจ IaC best practices, plugin เฉพาะ cloud | คุณภาพโค้ด + catch provider-specific issues |
| Checkov | Security + compliance, rule พร้อมใช้ 1000+ | Security scanning, compliance audit |
| Conftest/OPA | ยืดหยุ่นสูง, เขียน policy เฉพาะองค์กรได้ | Custom business rule |
ไม่จำเป็นต้องใช้ทุกตัว แต่การใช้ร่วมกันจะทำให้ครอบคลุมทั้ง syntax, best practice, security และ policy ได้สมบูรณ์ที่สุด
สรุป
การ validate และ linting เป็นขั้นตอนสำคัญที่ทำให้โค้ด IaC มีคุณภาพ ปลอดภัย และสอดคล้องกับมาตรฐานของทีม เครื่องมือ terraform validate เป็นจุดเริ่มต้นสำหรับตรวจ syntax, TFLint เสริมด้วยการตรวจ best practice ระดับ provider, Checkov ช่วยค้นหาช่องโหว่ด้านความปลอดภัย ส่วน Conftest/OPA ใช้บังคับ policy เฉพาะองค์กร
การนำเครื่องมือเหล่านี้ไปรันอัตโนมัติใน pre-commit hook และ CI pipeline จะช่วยจับปัญหาได้ตั้งแต่เนิ่น ๆ ก่อนที่โค้ดจะถูกนำไป deploy บน infrastructure จริง ทำให้ทีมมีคุณภาพงานที่สม่ำเสมอและลดความเสี่ยงด้านความปลอดภัยได้อย่างมีประสิทธิภาพ

