Every Terraform pull request gets security findings, organization-policy violations and the monthly cost change before merge.
The problem
The cheapest place to catch a public bucket, an open SSH port or a $1,000/month instance is the pull request, before it exists. Human reviewers miss things and tire of repeating themselves. A bot doesn’t, and with OIDC it can run a real terraform plan without a single stored AWS key.
What you’ll build
- A CloudFormation stack with the GitHub OIDC provider and a read-only plan role trusted only by one repo’s pull requests
- Explicit denies on secrets, parameters, KMS decrypt and data-plane reads, so even “read-only” can’t leak data
- A GitHub Actions workflow running Checkov, OPA/Conftest and Infracost on the plan
- Organization rules in Rego: required tags, no public S3, no open SSH/RDP, instance-size limits, encrypted private databases
- One sticky PR comment and a required status check that blocks merge
Architecture
Pull request โโถ GitHub Actions โโOIDCโโโถ AWS STS โโถ PlanRole (ReadOnly, no secrets, this repo's PRs only)
โ
โโ terraform plan โ plan.json
โโ Checkov 500+ built-in checks โ ๏ธ
โโ Conftest (OPA) policy/aws.rego (your rules) ๐ blocks merge
โโ Infracost "+$1,107/month" ๐ฐ
โโ one sticky PR comment + required status check
AWS services used
How it works
- AuthenticateThe workflow exchanges its GitHub OIDC token for a 1-hour read-only AWS session.
- Plan
terraform planruns against real state and becomesplan.json. - EvaluateCheckov (built-in checks), Conftest (your rules) and Infracost (cost diff) review the plan in parallel.
- DecideThe bot comments once and updates it in place. Violations fail the required check.
Key design decisions
- Trust is pinned to
repo:ORG/REPO:pull_request, so other repos and branches can’t assume the role. - Policies evaluate the plan, not the code, so modules and variables are fully resolved.
- Cost appears next to security, and FinOps shifts left into code review.
Deploy it
aws cloudformation deploy --stack-name policy-bot \
--template-file template.yaml --capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides GitHubOrg=my-org GitHubRepo=infrastructure StateBucketName=my-tf-state
Full prerequisites, parameters and clean-up steps are in the project README on the code page.
See it work
Open a PR with the included flawed Terraform. The bot flags missing tags, a disabled public-access block, SSH open to the world and an m7i.8xlarge, with the monthly cost right there in the comment.
Skills this demonstrates
Get the code
Every file needed to build this project, written as a deployable CloudFormation template and validated with the CloudFormation schema.
Leave a Reply