Policy as Code and Static Analysis
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”static analysis tool จับ pattern ที่รู้อยู่แล้วว่าแย่ใน HCL ก่อนที่ plan จะรันด้วยซ้ำ ส่วน policy-as-code tool ประเมิน plan ที่คำนวณออกมาจริงเทียบกับกฎขององค์กร แล้วบล็อกไม่ให้ apply ได้แบบอัตโนมัติถ้าละเมิดกฎ
Static analysis ก่อน plan จะรันด้วยซ้ำ
หัวข้อที่มีชื่อว่า “Static analysis ก่อน plan จะรันด้วยซ้ำ”terraform validate เช็คแค่ว่า HCL ของคุณ syntax ถูกต้องและสอดคล้องกันภายใน — type ตรงกัน, required argument ครบ, reference resolve ได้ แต่ไม่รู้เลยว่า machine type แบบ f1-micro เป็นตัวเลือกที่สมเหตุสมผลไหม หรือ Cloud Storage bucket ควร public read ได้จริงเหรอ tool สองประเภทนี้มาเติมช่องว่างตรงนั้น และทั้งคู่รันเร็ว ก่อนที่จะมีการ plan หรือ apply อะไรเลย
tflintรู้จัก provider มี plugin rule set ให้ใช้ รวมถึงtflint-ruleset-googleที่จับ mistake ที่validateมองไม่เห็น เพราะต้องรู้ว่า Google Cloud API จริง ๆ รับอะไรได้บ้าง เช่น machine type ที่ deprecated ไปแล้ว, ชื่อ region ที่ไม่ถูกต้อง, argument ที่ provider เลิกรองรับไปเงียบ ๆ- security และ misconfiguration scanner ในตระกูล
checkov/ผู้สืบทอดของtfsecจะ pattern-match ตัว HCL เทียบกับ configuration แย่ ๆ ที่รู้จักอยู่แล้ว เช่น Cloud Storage bucket ที่เปิด public read access, persistent disk ที่ไม่เข้ารหัส, firewall rule ที่เปิดให้0.0.0.0/0เข้าถึง port ที่ sensitivetfsecเคยครอบคลุมเรื่องคล้าย ๆ กัน แล้ว check เหล่านั้นก็ถูกรวมเข้าไปใน Trivy ตั้งแต่นั้นมา ส่วนcheckovยังเป็นตัวเลือกที่ใช้กันแพร่หลายและ maintain อยู่ต่อเนื่องสำหรับ layer นี้
plugin "google" { enabled = true version = "0.29.0" source = "github.com/terraform-linters/tflint-ruleset-google"}tflint --inittflint
# 1 issue(s) found:## Warning: "us-central1-z" is an invalid value as zone (google_compute_instance_invalid_zone)## on main.tf line 12:# 12: zone = "us-central1-z"
checkov -d .
# Check: CKV_GCP_28: "Ensure that Cloud Storage bucket is not anonymously or publicly accessible"# FAILED for resource: google_storage_bucket.reports# File: /main.tf:5-8tool ทั้งสองตัวรันกับ raw configuration เท่านั้น ไม่ต้องมี GCP credential, ไม่ต้อง plan, ไม่ต้อง state นี่แหละคือเหตุผลที่ควรอยู่เป็น gate แรกสุดใน pipeline เพราะถูก, เร็ว, และจับ mistake ทั้ง class ได้ก่อนที่ Terraform จะคุยกับ Google Cloud ด้วยซ้ำ
Policy-as-code ตอน plan time: Sentinel กับ OPA คู่กับ conftest
หัวข้อที่มีชื่อว่า “Policy-as-code ตอน plan time: Sentinel กับ OPA คู่กับ conftest”static analysis หยุดอยู่แค่ที่ตัวโค้ด จึงตอบคำถามแบบ “plan นี้สร้าง instance n2-standard-4 เกินห้าตัวใน production project ไหม” ไม่ได้ เพราะตัวเลขนั้นยังไม่มีอยู่จนกว่าจะคำนวณ plan ออกมาจริง ๆ policy-as-code tool ปิดช่องว่างตรงนี้ด้วยการประเมิน machine-readable output ของ plan แทนที่จะดูที่ HCL
- Sentinel คือ policy engine ในตัวของ HCP Terraform ผูกแน่นกับ HCP Terraform และ Terraform Enterprise run — policy ถูกผูกไว้กับ workspace หรือ organization แล้วถูกประเมินอัตโนมัติเป็นส่วนหนึ่งของทุก run ที่นั่น
- Open Policy Agent (OPA) ที่ใช้ผ่าน
conftestCLI คือทางเลือกโอเพนซอร์สที่ไม่ผูกกับ provider ไหนเป็นพิเศษ ทำงานกับ CI system ไหนก็ได้ ไม่ว่าจะใช้ HCP Terraform อยู่หรือไม่ ด้วยการประเมิน JSON document ที่คุณส่งเข้าไป
ไอเดียร่วมของทั้งสองแบบคือเหมือนกัน มีคนเขียน policy ไว้ครั้งเดียว — “ห้าม Cloud Storage bucket ไหนอนุญาต public access”, “ทุก resource ต้องมี label cost-center” — แล้วจะประเมินอัตโนมัติกับทุก plan ตั้งแต่นั้นเป็นต้นไป บล็อก plan ที่ละเมิดไม่ให้ apply ได้เลย นี่คือเวอร์ชันเชิงกลไกที่บังคับใช้เสมอของสิ่งที่ human reviewer ต้องคอยจำไปเช็คด้วยตาทุกครั้งไม่พลาดสักครั้ง
terraform plan -out=plan.tfplanterraform show -json plan.tfplan > plan.jsonconftest test plan.jsonpackage main
deny[msg] { resource := input.resource_changes[_] resource.type == "google_storage_bucket_iam_binding" resource.change.after.members[_] == "allUsers" msg := sprintf("%s grants public access via allUsers", [resource.address])}conftest อ่าน plan.json ตัวเดียวกับที่คนอ่านเองด้วยมือได้ แล้วปฏิเสธ run ทันทีที่ deny สร้าง message ออกมา ไม่มี plan ที่เข้าเงื่อนไขนั้นไปถึง apply เลยสักตัว
สองชั้นที่เสริมกัน ไม่ใช่ทำงานซ้ำกัน
หัวข้อที่มีชื่อว่า “สองชั้นที่เสริมกัน ไม่ใช่ทำงานซ้ำกัน”น่าจะดูเหมือน tflint/checkov กับ Sentinel/OPA ทำงานเดียวกันซ้ำสองรอบ แต่จริง ๆ แล้วเช็คคนละอย่างกันโดยพื้นฐาน
- static analysis เช็ค ตัวโค้ดเอง ไม่ขึ้นกับว่าตอนนี้กำลังเปลี่ยนแปลงอะไรอยู่ จึง flag Cloud Storage bucket ที่ public เหมือนกันไม่ว่า run นี้จะสร้าง bucket เดียวหรือร้อยตัว
- policy-as-code เช็ค การเปลี่ยนแปลงที่ plan ไว้จริง ๆ และ reference ค่าที่คำนวณจริงซึ่งมีอยู่แค่ตอนที่ plan มีแล้วเท่านั้น เช่น instance count จริง, label value จริง, member list จริงที่ resolve ได้หลังจาก variable interpolation กับ module composition รันไปแล้ว กฎแบบ “บล็อก plan ไหนที่จะสร้าง instance
n2-standard-4เกินห้าตัวใน production project” ไม่มีทางเทียบเท่าได้ที่ระดับ raw-HCL เลย เพราะไม่มีทางรู้จำนวน instance ได้โดยไม่คำนวณ 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"]