CI/CD Cho Terraform: GitHub Actions, OIDC, và Quét Chất Lượng
Tới giờ ta chạy Terraform bằng tay trên máy mình. Lên production thì không ổn: ai cũng có credential mạnh trên laptop, không có review trước khi apply, và "máy tôi chạy được" thành câu cửa miệng. Part VII chuyển Terraform vào pipeline. Bài này dựng một pipeline GitHub Actions thực dụng: plan tự động trên pull request để review, apply khi merge, xác thực AWS bằng OIDC không cần lưu khóa, và một loạt cổng kiểm chất lượng chặn lỗi trước khi nó tới được prod.
Mục tiêu
Hiểu cấu trúc một pipeline Terraform (plan trên PR, apply khi merge), xác thực bằng OIDC thay vì access key, và gắn các công cụ quét fmt/validate/tflint/Trivy/Checkov với hiểu biết về cái gì bắt được gì.
Cổng chất lượng: chạy được tại chỗ trước
Mọi bước trong pipeline đều là lệnh bạn chạy được ngay trên máy. Bắt đầu từ hai cái rẻ nhất:
$ terraform fmt -check -recursive
(OK)
$ terraform validate
Success! The configuration is valid.
fmt -check bắt định dạng lệch chuẩn, validate bắt lỗi cú pháp và tham chiếu sai mà không gọi API. Tiếp theo là tflint — linter chuyên cho Terraform, bắt lỗi mà validate bỏ qua (giá trị enum sai, tên instance type không tồn tại, quy ước đặt tên):
$ tflint
(không có cảnh báo — code sạch ở mức tflint)
Quét bảo mật: Trivy và Checkov
validate và tflint kiểm tính đúng, không kiểm tính an toàn. Một security group mở cổng 80 ra toàn Internet là HCL hợp lệ hoàn hảo, nhưng có thể là lỗ hổng. Đây là việc của công cụ quét misconfiguration.
Một lưu ý quan trọng về lựa chọn công cụ: trước đây tfsec là lựa chọn phổ biến, nhưng nó đã deprecated và được hợp nhất vào Trivy. Tài liệu cũ trên mạng vẫn dẫn tfsec; cách hiện hành là dùng trivy config. Quét chính cấu hình bài 14 (module VPC + EC2):
$ trivy config ./
Tests: 5 (SUCCESSES: 0, FAILURES: 5)
Failures: 5 (UNKNOWN: 0, LOW: 2, MEDIUM: 0, HIGH: 2, CRITICAL: 1)
AWS-0104 (CRITICAL): Security group rule allows unrestricted egress to any IP address.
AWS-0131 (HIGH): Root block device is not encrypted.
AWS-0028 (HIGH): Instance does not require IMDS access to require a token.
AWS-0178 (MEDIUM): VPC does not have VPC Flow Logs enabled.
AWS-0124 (LOW): Security group rule does not have a description.
Trivy chấm ra năm vấn đề trên đoạn code ta tưởng "chạy tốt" ở bài 14: egress mở toàn bộ, root volume chưa mã hóa, IMDS chưa bắt buộc token, VPC chưa bật flow log. Đây đều là những thứ thật, mỗi cái kèm mã AVD để tra cách sửa.
Checkov (của Bridgecrew) là công cụ thứ hai, bộ luật khác, nên thường bắt thêm hoặc khác Trivy — chạy cả hai cho phủ rộng:
$ checkov -d .
Passed checks: 7, Failed checks: 8, Skipped checks: 0
CKV_AWS_260: "Ensure no security groups allow ingress from 0.0.0.0:0 to port 80"
CKV_AWS_23: "Ensure every security group and rule has a description"
CKV_AWS_79: "Ensure Instance Metadata Service Version 1 is not enabled"
CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
Bộ chất lượng đầy đủ xếp theo độ "đắt": fmt → validate → tflint (tĩnh, nhanh) → Trivy + Checkov (bảo mật) → và ở mức cao hơn là policy-as-code (Sentinel/OPA) để chặn theo chính sách công ty. Không dùng tfsec nữa.
OIDC: xác thực không cần lưu khóa
Pipeline cần quyền gọi AWS. Cách cũ là lưu một cặp access key vào secret của repo — nhưng khóa tĩnh là rủi ro: nó tồn tại lâu dài, lộ là nguy, và phải xoay vòng thủ công. Cách hiện hành là OIDC: GitHub Actions phát một token định danh ngắn hạn cho mỗi lần chạy, AWS tin token đó và cấp credential tạm thời đổi lại. Không có khóa nào được lưu ở đâu.
Cơ chế cần dựng một lần phía AWS: tạo một IAM OIDC provider tin token.actions.githubusercontent.com, rồi một IAM role có trust policy chỉ cho repo cụ thể của bạn được nhận role đó. Trong workflow, hai mảnh làm OIDC hoạt động:
permissions:
id-token: write # cho phép job lấy OIDC token
contents: read
# ...
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }} # role tin GitHub OIDC
aws-region: ap-southeast-1
permissions: id-token: write là điều kiện bắt buộc — thiếu nó job không lấy được token. Action configure-aws-credentials đổi token lấy credential tạm cho role chỉ định. Secret duy nhất cần lưu là ARN của role (không phải bí mật thật sự), không còn access key.
Cấu trúc pipeline
Ghép lại thành luồng quen thuộc của Git: thay đổi đi qua pull request, được plan và review, merge thì apply.
Pull request ──► [quality] fmt/validate/tflint/Trivy/Checkov
│ pass
▼
[plan] OIDC -> terraform plan -> đính vào PR để review
│ (người review duyệt, merge)
▼
Push production ─► [quality] ──► [apply] OIDC -> terraform apply
│
GitHub Environment: cổng phê duyệt thủ công
Ba job: quality chạy mọi cổng kiểm trên mọi PR; plan chạy trên PR và đính kết quả plan để người review xem trước khi merge; apply chỉ chạy khi push vào production. Job apply gắn environment: production — GitHub Environments cho phép đặt một cổng phê duyệt thủ công, nên dù đã merge, apply vẫn chờ một người bấm duyệt trước khi đụng vào hạ tầng thật. Đây là chốt chặn cuối cho prod.
Một bổ sung đáng có là infracost: nó ước tính chi phí thay đổi và đính lên PR ("thay đổi này tốn thêm 47 USD/tháng"), giúp review nhìn thấy tác động tiền bạc trước khi merge. Gắn nó vào job plan như một bước nữa.
Lưu plan để apply đúng cái đã duyệt
Một chi tiết bài 2 từng nhắc giờ thành quan trọng: trong CI nên terraform plan -out=tfplan rồi terraform apply tfplan, để apply thực thi đúng kế hoạch đã review, không phải một plan mới có thể đã khác. Trên máy cá nhân bỏ qua được, trong pipeline thì đây là khác biệt giữa "apply cái đã duyệt" và "apply cái khác lúc nào không hay".
Tổng kết
Pipeline Terraform đưa thay đổi qua pull request: job quality chạy fmt/validate/tflint (tính đúng) rồi Trivy/Checkov (tính an toàn — Trivy thay cho tfsec đã deprecated), job plan đính kế hoạch lên PR để review, job apply chạy khi merge với cổng phê duyệt của GitHub Environments. Xác thực bằng OIDC (id-token: write + configure-aws-credentials với role-to-assume) loại bỏ access key tĩnh. Quét thật trên code bài 14 cho thấy năm vấn đề Trivy bắt và tám lỗi Checkov bắt — bằng chứng rằng cổng chất lượng không phải hình thức.
Pipeline lo việc chạy an toàn, nhưng làm sao biết code Terraform đúng trước khi nó chạy? Bài 19 đi vào kiểm thử: terraform test (GA từ 1.6) với file .tftest.hcl, cách mock provider để test không cần dựng hạ tầng thật, và giới thiệu Terratest cho kiểm thử tích hợp sâu hơn.