Terraform Validate และ Linting: ตรวจสอบ Code ด้วย TFLint และ Checkov

การเขียน 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 มี description
  • terraform_naming_convention — บังคับให้ใช้ snake_case
  • aws_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 validatebuilt-in, เร็ว, ตรวจ syntaxBaseline check ทุก project
TFLintตรวจ IaC best practices, plugin เฉพาะ cloudคุณภาพโค้ด + catch provider-specific issues
CheckovSecurity + 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 จริง ทำให้ทีมมีคุณภาพงานที่สม่ำเสมอและลดความเสี่ยงด้านความปลอดภัยได้อย่างมีประสิทธิภาพ