Worked review 3: CI pipeline และ policy check
Feature ที่ต้องทำ
หัวข้อที่มีชื่อว่า “Feature ที่ต้องทำ”Prompt คือ: “เขียน GitHub Actions pipeline ที่รัน Terraform plan บน pull request และ apply บน merge เข้า main โดยใช้ OIDC แทน credential AWS ที่เก็บไว้ พร้อม policy check” สิ่งที่ตามมาคือ draft แรกแบบที่ AI assistant ส่งกลับมาให้กับ prompt แบบนี้พอดี — YAML ที่ดูสมเหตุสมผล, action OIDC จริง, Rego policy ที่อ่านแล้วเหมือน OPA tutorial ทุกอัน มี bug จริง 10 ข้อ; ดู AI bug catalog สำหรับ pattern ทั่วไปเบื้องหลังหลายข้อ (entry 4, 18, 19, 20, 22) บทนี้เสริม — และลิงก์ไปหา — CI/CD for Infrastructure ที่แสดง pipeline ที่แก้แล้วเต็มรูปแบบ บทนี้เน้นการจับ mistake ใน draft ก่อนที่จะไปถึงจุดนั้น ทุกข้อที่กล่าวถึงด้านล่างพิสูจน์ด้วย actionlint, tflint, checkov, และ conftest จริง (ผ่าน docker run --rm กับ image rhysd/actionlint, ghcr.io/terraform-linters/tflint, bridgecrew/checkov, และ openpolicyagent/conftest ที่ pin ไว้) บวก exit code จริงของ Terraform และ terragrunt run --all plan --out-dir จริง
โค้ดที่ AI สร้าง
หัวข้อที่มีชื่อว่า “โค้ดที่ AI สร้าง”# .github/workflows/terraform.yml, as generated -- fragment, not a collected fencename: terraformon: pull_request: push: branches: [main]
jobs: plan: runs-on: ubuntu-latest steps: - uses: actions/checkout@master - uses: hashicorp/setup-terraform@v3 - name: Assume AWS role via OIDC uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::111122223333:role/github-actions-terraform aws-region: us-east-1 - run: terraform init - name: plan run: | echo "TF_VAR_db_password=$TF_VAR_db_password" terraform plan -detailed-exitcode -out=$PLAN_FILE env: PLAN_FILE: tfplan TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
apply: needs: plan if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@master - uses: hashicorp/setup-terraform@v3 - run: terraform init - run: terraform apply -auto-approve# ci/gitignore, as generated -- fragment, not a collected fence.terraform/.terraform.lock.hcl*.tfstate*.tfstate.backup# terraform.tfvars, as generated -- fragment, committed alongside the workflow abovedb_password = "Sup3rSecret!2024"api_token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz"# policy/deny_no_tags.rego, as generated -- fragment, not a collected fencepackage main
deny[msg] { resource := input.resource_changes[_] resource.type == "aws_instance" msg := sprintf("%s must set tags", [resource.address])}# .tflint.hcl, as generated -- fragment, not a collected fenceplugin "aws" { enabled = true version = "0.40.0" source = "github.com/terraform-linters/tflint-ruleset-aws"}เดินรีวิวทีละ bug
หัวข้อที่มีชื่อว่า “เดินรีวิวทีละ bug”Workflow: actions/checkout@master — branch ที่เปลี่ยนแปลงได้ ถูกใช้ราวกับ pin ไว้แล้ว
หัวข้อที่มีชื่อว่า “Workflow: actions/checkout@master — branch ที่เปลี่ยนแปลงได้ ถูกใช้ราวกับ pin ไว้แล้ว”อาการ — actionlint จริง, rhysd/actionlint, container แบบ one-shot:
$ docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest -color(no output -- 0 exit code)🐛 หา bug ในโค้ดนี้
- uses: actions/checkout@masterเฉลย
bug เดียวกับ AI bug catalog entry 19 — และพิสูจน์สดใหม่ที่นี่ด้วยความต่างที่ชัดเจนกว่า actionlint ต้องการแค่มี token @ref อยู่จริงตาม syntax เท่านั้น การลบมันออกไปเลย (uses: actions/checkout ไม่มี @ เลย) คือ error จริงที่มันจับได้ — ยืนยันแยกต่างหาก: specifying action "actions/checkout" in invalid format because ref is missing แต่ @master ให้ ref ที่ valid ตาม syntax actionlint เลยยอมรับเหมือนกับ commit SHA ที่ pin ไว้ทุกประการ — มันไม่มีความเห็นเลยว่า ref นั้น เปลี่ยนแปลงได้ หรือไม่ ตาม คู่มือ hardening ของ GitHub เอง มีแค่ commit SHA เต็มความยาวเท่านั้นที่ immutable วิธีแก้: uses: actions/checkout@<40-char-sha> # v5.0.0
Workflow: ปัญหา shellcheck ที่ actionlint จับได้จริง
หัวข้อที่มีชื่อว่า “Workflow: ปัญหา shellcheck ที่ actionlint จับได้จริง”อาการ — actionlint จริง กับ run: terraform plan -detailed-exitcode -out=$PLAN_FILE:
$ docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest -color.github/workflows/terraform.yml:14:9: shellcheck reported issue in this script: SC2086:info:1:40: Double quote to prevent globbing and word splitting [shellcheck] |14 | run: | | ^~~~🐛 หา bug ในโค้ดนี้
run: |terraform plan -detailed-exitcode -out=$PLAN_FILEเฉลย
actionlint ฝัง shellcheck เข้าไปกับทุก multi-line run: script — นี่เป็นการจับที่จริงและมีประโยชน์ ต่างจาก entry @master ด้านบน $PLAN_FILE ที่ไม่ quote เจอ word-splitting และ glob expansion จาก shell ได้ ถ้า plan filename มี space หรือ glob character -out=$PLAN_FILE จะทำผิดแบบเงียบๆ วิธีแก้: -out="$PLAN_FILE"
Workflow: env: TF_VAR_db_password ถูก echo ในบรรทัด debug
หัวข้อที่มีชื่อว่า “Workflow: env: TF_VAR_db_password ถูก echo ในบรรทัด debug”อาการ — จริง env ทำซ้ำสิ่งที่ environment ของ CI runner หน้าตาเป็นเมื่อ env: ตั้งตัวแปรที่มาจาก secret:
$ export TF_VAR_db_password="SuperSecret123"$ env | grep TF_VAR_db_passwordTF_VAR_db_password=SuperSecret123🐛 หา bug ในโค้ดนี้
- name: planrun: | echo "TF_VAR_db_password=$TF_VAR_db_password" terraform plan -detailed-exitcode -out=$PLAN_FILEenv: TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}เฉลย
GitHub Actions มาสก์ ค่า secret ที่รู้จักแล้ว ใน raw log output ก็จริง แต่การมาสก์นั้นเป็นแค่ best-effort string match — พิสูจน์แล้วด้านบน ข้อเท็จจริงทาง shell ที่แท้จริงคือ TF_VAR_db_password เป็น environment variable ธรรมดาสามัญทันทีที่ env: ตั้งมัน อ่านได้ด้วย env, printenv, crash dump, debug step, หรือเครื่องมือไหนก็ตามที่รันในงานเดียวกันที่ dump environment ของมันเพื่อ diagnostic บรรทัด echo ที่นี่เอาชนะการมาสก์ได้เลย (สร้าง string ใหม่ "TF_VAR_db_password=$TF_VAR_db_password") ซึ่งการมาสก์ของ GitHub ไม่ได้จับเสมอไป วิธีแก้: อย่า echo ตัวแปรที่มาจาก secret เลย แม้จะเพื่อ debug ก็ตาม ถ้า terraform เองต้องการมัน ก็ให้มันอ่าน env var โดยตรงโดยไม่ print ที่ไหนเลย
apply: ไม่มี plan ที่บันทึกไว้ — terraform apply -auto-approve plan ใหม่ตั้งแต่ต้น
หัวข้อที่มีชื่อว่า “apply: ไม่มี plan ที่บันทึกไว้ — terraform apply -auto-approve plan ใหม่ตั้งแต่ต้น”อาการ — จริง เทียบกันตรงๆ ก่อนอื่น apply ที่ไม่มี plan file ที่บันทึกไว้:
$ terraform apply -auto-approveterraform_data.x: Refreshing state...Plan: 1 to add, 0 to change, 0 to destroy.Apply complete! Resources: 1 added, 0 changed, 0 destroyed.แล้ว plan ที่บันทึกจริงถูก apply ผ่าน path ของไฟล์แทน:
$ terraform plan -out=tfplan$ terraform apply tfplanApply complete! Resources: 1 added, 0 changed, 0 destroyed.🐛 หา bug ในโค้ดนี้
- run: terraform init- run: terraform apply -auto-approveเฉลย
job apply ไม่เคย download plan ที่ job plan คำนวณไว้เลย และ (ใน draft นี้) ไม่เคยแม้แต่จะตั้งชื่อ plan file ให้บันทึกตั้งแต่แรก พิสูจน์แล้วด้านบน: apply -auto-approve ที่ไม่มี plan file พิมพ์ Refreshing state... / Plan: ของตัวเองสดใหม่ก่อน apply — มัน derive plan ใหม่ตั้งแต่ต้น แล้ว apply มันทันที โดยไม่มีมนุษย์รีวิว plan ตัวนั้นเจาะจงเลย การ apply plan file จริงที่บันทึกไว้แทน (apply tfplan) ไม่พิมพ์บรรทัดไหนเลย — แค่ apply เพราะไม่มีอะไรเหลือให้คำนวณ ระหว่างที่ job plan รีวิวเสร็จกับตอนที่ job apply รันจริง infrastructure หรือ configuration จริงอาจ drift ได้ และ -auto-approve บน plan ที่เพิ่ง recompute ใหม่แปลว่าไม่มีใครเห็นเลยว่าจริงๆ แล้ว apply อะไรไป วิธีแก้: terraform plan -out=tfplan เป็น build artifact ใน job plan, download แล้ว apply ตรงตัว (terraform apply tfplan ไม่ต้อง -auto-approve เพราะ plan file เองก็เป็น target ที่ชัดเจนอยู่แล้ว) ใน job apply
plan: คำนวณ -detailed-exitcode แต่ exit code สามแบบไม่เคยถูกอ่านจริง
หัวข้อที่มีชื่อว่า “plan: คำนวณ -detailed-exitcode แต่ exit code สามแบบไม่เคยถูกอ่านจริง”อาการ — จริง command เดียวกันเป๊ะ exit code จริงต่างกันในสองสถานะ:
$ terraform plan -detailed-exitcode # a resource actually needs to change$ echo $?2
$ terraform plan -detailed-exitcode # re-run immediately after, nothing left to change$ echo $?0🐛 หา bug ในโค้ดนี้
terraform plan -detailed-exitcode -out=$PLAN_FILE# (workflow never checks $? here at all)เฉลย
-detailed-exitcode มีไว้เจาะจงเพื่อให้ script แยกได้ว่า “มี change จริงพร้อมให้รีวิว” (2) กับ “เกิด error จริง” (1) กับ “ไม่มีอะไรต้องทำ” (0) — พิสูจน์แล้วด้านบน command เดียวกันเป๊ะคืน code ต่างกันจริงขึ้นอยู่กับว่ามี diff หรือไม่ run: block ของ draft นี้ไม่เคยจับ $? เลย: เพราะ block ของ shell ในขั้นนี้ไม่มีบรรทัดถัดไปหลัง terraform plan เลย non-zero exit ตรงนี้เลยไม่ถูกกลืนแบบเงียบๆ (พฤติกรรม default ของ run: คือ exit non-zero ทำให้ step พัง) แต่ก็ยังไม่มีอะไรใน workflow แยกแยะ exit 2 (diff จริง — คาดหวังไว้ ควร post plan ให้รีวิว) จาก exit 1 (error จริง — ควร alert ดังๆ ต่างออกไป) เลย วิธีแก้: จับ $? ชัดเจน แยกตามมัน (0 → ไม่มีอะไรเปลี่ยน ข้ามการ comment; 1 → ทำให้ job พัง; 2 → change จริง post plan แล้วไปต่อ) แบบที่บท CI/CD for Infrastructure ของคอร์สนี้ทำใน pipeline ของมัน
.gitignore: .terraform.lock.hcl ถูก gitignore ไปพร้อมกับ .terraform/
หัวข้อที่มีชื่อว่า “.gitignore: .terraform.lock.hcl ถูก gitignore ไปพร้อมกับ .terraform/”# .gitignore, as generated.terraform/.terraform.lock.hcl*.tfstate*.tfstate.backupbug เดียวกับ AI bug catalog entry 4 — .gitignore แบบ Terraform ทั่วไป ignore ทั้ง .terraform/ (ถูกแล้ว — เป็น cache download ขนาดใหญ่ที่ regenerate ในเครื่องได้จริง) และ .terraform.lock.hcl (ผิด — ไฟล์นี้มีไว้เจาะจงเพื่อให้การ resolve provider reproducible ข้ามทุกเครื่องและทุก CI run) วิธีแก้:
# .gitignore, fixed.terraform/*.tfstate*.tfstate.backupterraform.tfvars: secret จริง commit ไปพร้อมกับ workflow
หัวข้อที่มีชื่อว่า “terraform.tfvars: secret จริง commit ไปพร้อมกับ workflow”# terraform.tfvars, as generateddb_password = "Sup3rSecret!2024"api_token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz"bug เดียวกับ AI bug catalog entry 22 — แย่กว่าเดิมในบริบท CI เจาะจง เพราะ .tfvars ที่ commit แล้วแปลว่า secret ไปถึงทุก checkout, ทุก pull request จาก fork ที่อ่าน repository ได้, และทุก CI log ที่บังเอิญ cat ไฟล์เพื่อ debug workflow ที่ generate มาแสดง pattern ที่ถูกต้องอยู่ห่างออกไปแค่ขั้นเดียว (env: TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}) — ไฟล์ .tfvars เป็นของเหลือจาก local development ที่ไม่เคยถูกลบก่อน commit วิธีแก้: ลบไฟล์ออกจาก working tree และจาก git history; ดึง db_password/api_token จาก environment variable แบบ TF_VAR_* ที่มาจาก CI secret เท่านั้น ไม่เคยจากไฟล์ที่ commit ไว้เลย
policy/deny_no_tags.rego: syntax Rego ก่อน v1 load ไม่ผ่าน
หัวข้อที่มีชื่อว่า “policy/deny_no_tags.rego: syntax Rego ก่อน v1 load ไม่ผ่าน”# policy/deny_no_tags.rego, as generated -- @expect-error rego_parse_error: `if` keyword is required before rule bodypackage main
deny[msg] { resource := input.resource_changes[_] resource.type == "aws_instance" msg := sprintf("%s must set tags", [resource.address])}อาการ — conftest test จริง, openpolicyagent/conftest เวอร์ชันปัจจุบัน, กับ terraform show -json plan จริง:
$ docker run --rm -v "$(pwd)":/project openpolicyagent/conftest test --policy policy plan.jsonError: running test: load: loading policies: load: 2 errors occurred during loading:policy/deny_no_tags.rego:3: rego_parse_error: `if` keyword is required before rule bodypolicy/deny_no_tags.rego:3: rego_parse_error: `contains` keyword is required for partial set rules🐛 หา bug ในโค้ดนี้
deny[msg] {resource := input.resource_changes[_]resource.type == "aws_instance"msg := sprintf("%s must set tags", [resource.address])}เฉลย
bug เดียวกับ AI bug catalog entry 20 — รูปทรง rule แบบ bracket-set ที่ Rego tutorial หลายปีใช้กันคือสิ่งที่ OPA v1 (default ของ conftest ปัจจุบัน) ปฏิเสธ เป็น load error ไม่ใช่ rule ที่เงียบๆ ไม่เคยทำงาน วิธีแก้:
# policy/deny_no_tags.rego, fixedpackage main
import rego.v1
deny contains msg if { resource := input.resource_changes[_] resource.type == "aws_instance" msg := sprintf("%s must set tags", [resource.address])}จริง กับ plan เดียวกันเป๊ะ:
$ docker run --rm -v "$(pwd)":/project openpolicyagent/conftest test --policy policy plan.jsonFAIL - plan.json - main - aws_instance.app must set tags
1 test, 0 passed, 0 warnings, 1 failure, 0 exceptions.tflint.hcl: ไม่มี required_version ใน Terraform config ที่ pipeline ใช้จริง
หัวข้อที่มีชื่อว่า “.tflint.hcl: ไม่มี required_version ใน Terraform config ที่ pipeline ใช้จริง”อาการ — tflint จริง, ghcr.io/terraform-linters/tflint, container แบบ one-shot กับ main.tf ของ pipeline เอง:
$ docker run --rm -v "$(pwd)":/data -w /data ghcr.io/terraform-linters/tflint1 issue(s) found:
Warning: terraform "required_version" attribute is required (terraform_required_version)
on main.tf line 1: 1: terraform {มี policy config อยู่ แต่ไม่มี step checkov ต่อเข้า pipeline เลยเหมือนกัน
หัวข้อที่มีชื่อว่า “มี policy config อยู่ แต่ไม่มี step checkov ต่อเข้า pipeline เลยเหมือนกัน”อาการ — checkov จริง, bridgecrew/checkov, container แบบ one-shot กับ main.tf ของ pipeline เอง (aws_instance ที่ไม่มี encryption, ไม่บังคับ IMDSv2, ไม่มี IAM role ผูกไว้):
$ docker run --rm -v "$(pwd)":/tf bridgecrew/checkov -d /tf --compact --quietPassed checks: 2, Failed checks: 5, Skipped checks: 0
Check: CKV_AWS_135: "Ensure that EC2 is EBS optimized"FAILED for resource: aws_instance.appCheck: CKV_AWS_126: "Ensure that detailed monitoring is enabled for EC2 instances"FAILED for resource: aws_instance.appCheck: CKV_AWS_79: "Ensure Instance Metadata Service Version 1 is not enabled"FAILED for resource: aws_instance.appช่องว่างประเภทเดียวกับ entry .tflint.hcl ด้านบน — check ที่ทำงานจริง แต่ไม่เคยถูก workflow ที่ generate เรียกใช้เลย วิธีแก้: step checkov เข้า job plan ก่อน terraform plan ทำให้ job พังบน check ไหนก็ตามที่ FAILED (หรือ skip list ที่ระบุชัดเจนและรีวิวแล้ว — ไม่เคยเงียบด้วยการละเลย)
run --all plan ไม่มี --out-dir ที่ชั้น Terragrunt
หัวข้อที่มีชื่อว่า “run --all plan ไม่มี --out-dir ที่ชั้น Terragrunt”อาการ — terragrunt run --all plan --out-dir จริง plan file จริงต่อ unit สำหรับทุก unit ใน tree และ command ถัดไปที่ต้องรันพิมพ์ออกมาตรงๆ:
$ terragrunt run --all --non-interactive --no-color --out-dir /tmp/tg-plans -- plan[live/prod/app] terraform apply "/tmp/tg-plans/live/prod/app/tfplan.tfplan"
❯❯ Run Summary 4 units Succeeded 4$ find /tmp/tg-plans -maxdepth 3/tmp/tg-plans/live/dev/network/tmp/tg-plans/live/dev/app/tmp/tg-plans/live/prod/network/tmp/tg-plans/live/prod/app🐛 หา bug ในโค้ดนี้
terragrunt run --all -- plan# ... later, in a separate job/step ...terragrunt run --all -- apply -auto-approveเฉลย
bug เดียวกันเป๊ะกับ entry “ไม่มี -out” ของ plain Terraform ด้านบน แค่ขึ้นไปอีกชั้น — run --all plan ที่ไม่มี --out-dir คำนวณแล้วทิ้ง plan ต่อ unit; job apply plan ใหม่ทุก unit ตั้งแต่ต้น พิสูจน์แล้วด้านบน: --out-dir <dir> เขียนไฟล์ .tfplan จริงต่อ unit หนึ่งไฟล์ ใน directory structure ที่ mirror live tree และยังบอก command terraform apply "<path>" ที่แน่นอนที่จะ apply มันโดยไม่เปลี่ยนแปลงด้วย วิธีแก้: run --all plan --out-dir <dir> ใน job plan (upload เป็น CI artifact), run --all apply --out-dir <dir> (directory เดียวกัน) ใน job apply apply plan ที่บันทึกไว้จริงตรงตัว
Trust policy ของ OIDC scope กว้างเกินไป
หัวข้อที่มีชื่อว่า “Trust policy ของ OIDC scope กว้างเกินไป”workflow ที่ generate มามี role-to-assume: arn:aws:iam::111122223333:role/github-actions-terraform ที่จริงและรูปทรงถูกต้อง แต่ไม่มีอะไรในโค้ดที่ generate มาของบทนี้แสดง trust policy ของ IAM role เองเลย — ส่วนที่ตัดสินใจจริงว่า identity ของ GitHub ตัวไหน assume มันได้ trust policy ที่เงื่อนไข sub หายไปเลย หรือ scope แค่ repository โดยไม่มี ref (repo:my-org/my-infra:*) เปิดให้ branch ไหนก็ได้ — รวมถึง pull request จาก fork ถ้า workflow เคยถูก trigger จาก fork เลย mint token ที่ assume role นี้ได้ วิธีแก้: scope เงื่อนไข sub ของ trust policy ไปที่ branch ที่รัน apply จริงเจาะจง (repo:my-org/my-infra:ref:refs/heads/main) แล้วใช้ role แยกที่ scope แคบกว่า (หรือไม่ใช้ role เลย) สำหรับ job plan ที่รันบน pull request จาก fork ที่ไม่น่าเชื่อถือ
อะไรเปลี่ยนและทำไม
หัวข้อที่มีชื่อว่า “อะไรเปลี่ยนและทำไม”| # | Bug | วิธีแก้ |
|---|---|---|
| 1 | actions/checkout@master — ref เปลี่ยนแปลงได้ actionlint ยอมรับเหมือน SHA | Pin เป็น commit SHA เต็ม — actionlint จริงพิสูจน์ว่ามันไม่แยกแยะ branch ref จาก SHA |
| 2 | -out=$PLAN_FILE ไม่ quote ใน run: script | -out="$PLAN_FILE" — actionlint/shellcheck SC2086 จริง |
| 3 | TF_VAR_db_password ที่มาจาก secret ถูก echo ในบรรทัด debug | อย่า echo เลย — env output จริงพิสูจน์ว่ามันเป็นตัวแปรธรรมดาที่อ่านได้ทันทีที่ตั้งแล้ว |
| 4 | apply -auto-approve ไม่มี plan file บันทึกไว้ | plan -out=tfplan เป็น artifact, apply tfplan ใน apply job — บรรทัด Refreshing state.../Plan: จริงพิสูจน์ว่า re-plan สดใหม่เกิดขึ้นถ้าไม่มี |
| 5 | คำนวณ -detailed-exitcode แล้วไม่เคยแยก exit code 3 แบบ | จับ $? แยกตาม 0/1/2 ชัดเจน — exit code จริง 2 (มี change) เทียบ 0 (ไม่มี) พิสูจน์แล้ว |
| 6 | .terraform.lock.hcl ถูก gitignore ไปพร้อม .terraform/ | ignore แค่ .terraform/ — mechanics เดียวกับ AI bug catalog entry 4 |
| 7 | terraform.tfvars มี secret จริง commit ไว้ | ลบทิ้ง; secret มาจาก TF_VAR_* CI secret เท่านั้น — mechanics เดียวกับ AI bug catalog entry 22 |
| 8 | Rego ก่อน v1 (deny[msg] { ... }) | import rego.v1 + deny contains msg if { ... } — conftest load error จริงก่อน, FAIL จริงหลัง |
| 9 | มี config .tflint.hcl/checkov แต่ไม่มี CI step ไหนรันเลยสักตัว | เพิ่ม step tflint/checkov จริงเข้า job plan — ทั้งสองเครื่องมือพิสูจน์แล้วว่าเจอ issue จริงกับ main.tf ตัวนี้เมื่อรันจริง |
| 10 | ไม่มี --out-dir บน terragrunt run --all plan | --out-dir <dir> ใน plan, dir เดียวกันใน apply — ไฟล์ .tfplan ต่อ unit จริงและ command apply ที่แน่นอนพิสูจน์แล้ว |
| — | เงื่อนไข sub ของ OIDC trust policy หายไปหรือกว้างเกินไป | Scope เป็น repo:org/repo:ref:refs/heads/main — อ้างอิงจาก docs ไม่มี AWS account ในคอร์สนี้ |
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”สาม bug นี้ (load error ของ 8, การจับของ shellcheck, และ uses: ที่ไม่ pin ไม่มี ref เลย) เป็น hard, tool-level rejection — conftest/actionlint ปฏิเสธไม่ไปต่อ bug config-file สองข้อ (9) คือรูปทรงที่อันตรายที่สุดในคอร์สนี้ทั้งหมด: เครื่องมือจริงที่ทำงานได้จริงที่ควรจับปัญหาจริง นั่งอยู่ใน repository แยกขาดจาก pipeline ที่ควรจะรันมันโดยสิ้นเชิง — พิสูจน์ด้วยการรันตรงแล้วเจอ finding จริงที่ CI ไม่เคยเห็นเลย ที่เหลือ — action ที่ไม่ pin, plan ที่ re-derive ใหม่, exit code ที่ไม่แยก, secret ที่รั่ว, lockfile ที่ gitignore, .tfvars ที่ commit, trust policy ที่ scope ไม่พอ — ไม่เคย error ที่ไหนเลยในทั้ง toolchain มันล้วน schema ถูกต้อง สมเหตุสมผลรายตัว และทุกข้อพิสูจน์ที่นี่ด้วยการรันเครื่องมือ จริง กับ artifact ที่ generate จริง แทนที่จะเชื่อว่าแค่มี policy file อยู่ก็แปลว่ามันถูก enforce
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “ถ้ามีไฟล์
.tflint.hcl/checkov config อยู่ใน repo แปลว่ามันถูก enforce” พิสูจน์แล้วด้านบน: ทั้งสองเครื่องมือ รันตรงกับmain.tfของ pipeline เอง เจอ issue จริง (required_versionหายไป, checkov พัง 5 check) ที่ CI run ซึ่งอิงแค่ว่ามี config file อยู่ — ไม่มี steptflint/checkovจริง — จะไม่เห็นเลย - “
actionlintจับ third-party action ที่ไม่ pin ได้” พิสูจน์แล้วด้านบน อย่างเจาะจง: มันต้องการแค่มี@refอยู่ตาม syntax (uses: actions/checkoutเปล่าไม่มี ref เลยคือ error จริงที่มันจับได้) แต่ไม่แยกแยะ — และแยกแยะไม่ได้จาก YAML อย่างเดียว — branch name เปลี่ยนแปลงได้แบบ@masterจาก commit SHA แบบ immutable - “การมาสก์ secret ของ GitHub แปลว่า secret แบบ
TF_VAR_*รั่วผ่าน CI log ไม่ได้” พิสูจน์แล้วด้านบน: ทันทีที่ secret กลายเป็น environment variable ธรรมดา มันอ่านได้ด้วยเครื่องมือไหนก็ตามที่ dump environment ของมัน การมาสก์เป็นแค่ best-effort string match กับค่า secret ที่รู้จักอยู่แล้ว ไม่ใช่การรับประกัน
flowchart TD Y["workflow YAML"] --> AL["actionlint"] AL -->|"1: @master accepted, ref exists"| AL AL -->|"2: unquoted $PLAN_FILE, real SC2086"| AL Y --> R["terraform plan -detailed-exitcode"] R -->|"3: TF_VAR_* echoed"| R R -->|"5: exit code 0/1/2 never branched"| R R --> A["terraform apply"] A -->|"4: no -out, re-plans from scratch"| A G[".gitignore / terraform.tfvars"] -->|"6: lockfile ignored"| G G -->|"7: secret committed"| G P["policy/*.rego"] --> CF["conftest"] CF -->|"8: pre-v1 Rego, load error"| CF L[".tflint.hcl / checkov config"] -->|"9: never wired into CI"| L TG["terragrunt run --all plan"] -->|"10: no --out-dir"| TG IAM["IAM role trust policy"] -->|"OIDC sub too broad -- docs"| IAM