ข้ามไปยังเนื้อหา

Policy as Code and Static Analysis

static analysis tool จับ pattern ที่รู้อยู่แล้วว่าไม่ดีใน HCL ก่อนที่ plan จะรันด้วยซ้ำ ส่วน policy-as-code tool ประเมิน plan ที่คำนวณจริงเทียบกับ rule ขององค์กร แล้วบล็อกไม่ให้ apply ได้แบบอัตโนมัติถ้า plan นั้นผิด rule

terraform validate เช็คแค่ว่า HCL syntax ถูกต้องและ consistent กันภายใน — type ตรงกัน argument ที่ required มีครบ reference resolve ได้ แต่ไม่รู้หรอกว่า instance type t2.micro เป็นตัวเลือกที่สมเหตุสมผลไหม หรือ S3 bucket ควรเปิดให้ public read จริง ๆ หรือเปล่า tool สองประเภทนี้เข้ามาเติมช่องว่างนั้น และทั้งคู่รันเร็ว ก่อนที่จะมีการ plan หรือ apply อะไรเลย

  • tflint provider-aware มาพร้อม plugin rule set (plugin ของ AWS, ของ Azure และอื่น ๆ) ที่จับ mistake ที่ validate มองไม่เห็น เพราะต้องรู้ว่า AWS API จริง ๆ รับอะไรได้บ้าง เช่น instance type ที่ deprecated ไปแล้ว, ARN pattern ที่ผิด, argument ที่ provider เลิก support ไปเงียบ ๆ
  • security และ misconfiguration scanner ในตระกูล checkov pattern-match ตัว HCL เองเทียบกับ configuration ที่รู้อยู่แล้วว่าไม่ดี เช่น S3 bucket ที่เปิด public read access, security group ที่เปิด 0.0.0.0/0 บน port ที่ sensitive, EBS volume ที่ไม่ได้ encrypt tfsec เคยครอบคลุมเรื่องคล้าย ๆ กันนี้และ check เหล่านั้นถูกรวมเข้าไปใน Trivy แล้ว ส่วน checkov ยังเป็นตัวเลือกที่ใช้กันแพร่หลายและ maintain อยู่ต่อเนื่องสำหรับ layer นี้
Terminal window
tflint --init
tflint
# 1 issue(s) found:
#
# Warning: "t1.micro" is an invalid value as instance_type (aws_instance_invalid_type)
#
# on main.tf line 12:
# 12: instance_type = "t1.micro"
checkov -d .
# Check: CKV_AWS_20: "S3 Bucket has an ACL defined which allows public READ access."
# FAILED for resource: aws_s3_bucket.reports
# File: /main.tf:5-8

tool ทั้งสองรันกับ configuration ดิบ ๆ เลย ไม่ต้องมี AWS credential ไม่ต้อง plan ไม่ต้องมี state นี่แหละคือเหตุผลที่ควรเป็น gate แรกสุดใน pipeline เพราะเร็ว ถูก และจับ mistake ทั้ง class ได้ก่อนที่ Terraform จะคุยกับ AWS ด้วยซ้ำ

static analysis หยุดอยู่แค่ที่ code จึงตอบคำถามแบบ “plan นี้สร้าง m5.xlarge instance เกินห้าตัวใน production account ไหม” ไม่ได้ เพราะตัวเลขนั้นยังไม่มีอยู่จนกว่าจะคำนวณ plan จริง policy-as-code tool ปิด gap ตรงนี้ด้วยการประเมิน machine-readable output ของ plan ไม่ใช่ตัว HCL

  • Sentinel เป็น policy engine ที่ built-in มากับ HCP Terraform integrate แน่นกับ HCP Terraform และ Terraform Enterprise run — policy ผูกไว้กับ workspace หรือ organization แล้วถูกประเมินอัตโนมัติเป็นส่วนหนึ่งของทุก run ที่นั่น
  • Open Policy Agent (OPA) ใช้ผ่าน CLI ชื่อ conftest เป็น alternative แบบ open-source ที่ provider-agnostic กว่า ใช้ได้กับ CI system ไหนก็ได้ ไม่ว่าจะมี HCP Terraform หรือไม่ ด้วยการประเมิน JSON document ที่คุณส่งเข้าไป

ไอเดียหลักของทั้งสองตัวเหมือนกัน คือมีคนเขียน policy ไว้ครั้งเดียว — “security group ห้ามเปิด ingress จาก 0.0.0.0/0 บน port 22” หรือ “ทุก resource ต้องมี tag cost-center” — แล้วจะประเมินอัตโนมัติกับทุก plan ตั้งแต่นั้นมา บล็อก plan ที่ผิด rule ไม่ให้ apply ได้เลย เป็น version แบบ mechanical และบังคับใช้เสมอของสิ่งที่ human reviewer เดิมต้องคอยจำไปเช็คด้วยตาทุกครั้ง โดยไม่มีวันพลาด

Terminal window
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
conftest test plan.json
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group_rule"
resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
resource.change.after.from_port <= 22
resource.change.after.to_port >= 22
msg := sprintf("%s allows ingress from 0.0.0.0/0 on port 22", [resource.address])
}

conftest อ่าน plan.json ตัวเดียวกับที่คนอ่านเองก็ได้ แล้ว reject run ทันทีที่ deny ให้ message ออกมา — ไม่มี plan ตัวไหนที่ตรง rule นี้ไปถึง apply ได้เลย

ชวนให้คิดว่า tflint/checkov กับ Sentinel/OPA ทำงานเดียวกันซ้ำสองรอบ แต่จริง ๆ ทั้งสองเช็คคนละเรื่องกันเลย

  • static analysis เช็ค ตัว code เอง ไม่ขึ้นกับว่า change จริง ๆ คืออะไร จึง flag S3 bucket ที่ public เหมือนกันหมด ไม่ว่า run นี้จะสร้าง bucket เดียวหรือร้อยตัว
  • policy-as-code เช็ค change ที่ plan ไว้จริง ๆ และ reference ค่าที่คำนวณจริงได้ ซึ่งมีอยู่ก็ต่อเมื่อ plan ถูกสร้างขึ้นแล้ว เช่น instance count จริง, ค่า tag จริง, CIDR block จริงที่ resolve ได้หลังจาก variable interpolation กับ module composition รันเสร็จ rule แบบ “บล็อก plan ที่จะเหลือ instance หลัง load balancer น้อยกว่าสองตัว” ไม่มี equivalent ที่ระดับ HCL ดิบเลย เพราะไม่มีทางรู้ instance count ได้โดยไม่คำนวณ plan ก่อน

ทั้งสอง stage อยู่ใน pipeline เดียวกันได้ เรียงต่อกัน static check ที่ถูกและเร็วก่อน จับ mistake ทั้ง class ได้ทันที ตามด้วย policy evaluation กับ plan ที่คำนวณแล้ว จับ violation ที่เฉพาะเจาะจงกับสถานการณ์จริง ซึ่งจะเห็นได้ก็ต่อเมื่อ Terraform รู้แน่ชัดแล้วว่ากำลังจะทำอะไร

flowchart LR
  hcl["HCL code"] --> lint["tflint / checkov: static checks"]
  lint -->|passes| plan["terraform plan"]
  plan --> policy["Sentinel / OPA + conftest: policy check"]
  policy -->|passes| apply["apply"]
  policy -->|violates a rule| blocked["Blocked, never applied"]
HCL ผ่าน static analysis ก่อน แล้วคำนวณ plan และเช็คกับ policy ก่อนอนุญาตให้ apply
tflint กับ checkov จับอะไรได้ที่ terraform validate ธรรมดาจับไม่ได้
ความต่างหลักระหว่างสิ่งที่ static analysis tool เช็ค กับสิ่งที่ policy-as-code tool อย่าง Sentinel หรือ OPA เช็ค คืออะไร
policy ข้อไหนที่บังคับใช้ได้แค่กับ plan ที่คำนวณแล้ว ไม่ใช่กับ HCL ดิบอย่างเดียว
Sentinel ต่างจาก Open Policy Agent พร้อม conftest ยังไง