Scanning Terraform with Checkov before you apply
Catch public buckets and open security groups in the plan — in CI, with suppressions that expire and a clean baseline.
Most Terraform incidents are boring in hindsight: an S3 bucket policy with Principal: *, a security group with 0.0.0.0/0 on port 22, or an RDS instance without encryption. Checkov scans IaC statically — no cloud credentials required for the scan itself — and fails the pipeline when policy-as-code rules match your .tf files. The goal is not zero findings forever; it is catching regressions before terraform apply publishes them to the internet.
This note wires Checkov into a local pre-commit hook and a CI job, shows how to baseline known debt without silencing new violations, and sets suppressions that expire. Pair it with Terraform for the modules being scanned and the dedicated Checkov course for custom policies and OPA integration.
Scan runs on every pull request against the plan directory. Fail on new CRITICAL/HIGH unless suppressed with owner and expiry.
Run Checkov locally and in CI
Start with the built-in AWS, Azure, and Kubernetes policies — hundreds of checks maintained by Bridgecrew/Palo Alto. Output JSON in CI so you can diff findings between branches. Soft-fail locally (--soft-fail) while onboarding; hard-fail in main once the team trusts the signal.
Scope scans to directories that actually deploy — terraform/, modules/, Helm charts — and skip .terraform/ provider caches. In monorepos, path filters on pull requests keep CI fast. When a finding is a false positive, fix the policy upstream or document the exception with an owner; silent --skip-check flags in root modules become permanent blind spots within two quarters.
- name: Checkov scanrun: |pip install checkov==3.2.40checkov -d terraform/ \--framework terraform \--output sarif \--output-file results.sarif \--soft-fail-on LOW \--hard-fail-on HIGH
Suppressions that expire
Permanent # checkov:skip= comments rot into permanent blind spots. Prefer a .checkov.baseline for legacy stacks you are chipping away at, and inline skips only with a ticket id and expiry metadata where your policy allows. Review suppressions in PRs the same way you review security group changes.
Custom policies in YAML or Python extend Checkov to org rules — mandatory cost-center tags, approved AMIs, required VPC endpoints. Export SARIF to GitHub Advanced Security so findings appear inline on the diff the author is already looking at. The scan should fail before terraform plan posts to the PR comment, not after merge when apply is one click away.
# Legacy public CloudFront origin — ticket INFRA-442, remove by Q3# checkov:skip=CKV_AWS_86:Origin is behind WAF + OACresource "aws_s3_bucket" "assets" {bucket = "acme-cdn-origin"}
checkov -d terraform/prod --framework terraform --compactCheck: CKV_AWS_20 — S3 bucket ACL allows public readCheck: CKV_AWS_23 — Security group allows 0.0.0.0/0:22checkov -d terraform/prod --check CKV_AWS_20 --quietPassed checks: 1, Failed checks: 0Where this goes next
Wire Checkov output into SARIF so GitHub or GitLab surfaces findings on the diff. Add custom policies for org-specific requirements — mandatory Environment tags, approved instance types, required KMS keys. Checkov covers custom YAML policies and OPA; pair with Terraform so modules ship secure defaults instead of suppressions.