Terraform turns infrastructure changes into pull requests, but a pull request only gets as much scrutiny as the humans reading it — and a 40-file diff adding a new VPC peering rule or a public S3 bucket policy rarely gets the line-by-line attention a security review deserves. Policy-as-code testing closes that gap by running the same three checks every time, before terraform apply ever touches a cloud account. The tooling landscape here is more mature than most teams realize: Checkov, the open-source infrastructure-as-code scanner built by Bridgecrew, had already surpassed a million downloads by the time Palo Alto Networks announced Checkov 2.0 in April 2021, and Palo Alto Networks found the underlying project valuable enough to acquire Bridgecrew outright for roughly $156 million in cash, completing the deal on March 2, 2021. Open Policy Agent, the general-purpose engine that lets teams write custom Rego rules against a Terraform plan, has an even longer institutional pedigree — it entered the Cloud Native Computing Foundation in March 2018 and reached CNCF's Graduated tier, the foundation's highest maturity bar, in January 2021. Neither tool alone is sufficient. This post covers what each one actually checks, why plan-based evaluation beats scanning raw .tf files, and how to chain all three into a single CI gate.
What does tflint check that Checkov and OPA don't?
tflint is a linter, not a policy engine — it checks Terraform code for correctness and provider-specific best practices rather than security or compliance posture. Its rule set catches things like deprecated syntax, unused variables and declared-but-unreferenced modules, invalid instance types for a given AWS region, and type mismatches between a module's inputs and what the provider schema expects. None of that overlaps meaningfully with Checkov's security checks or a custom Rego policy — tflint won't flag a public S3 bucket or an open security group, because that isn't its job. Where it earns its place in a pipeline is catching plan-time failures before they ever reach terraform plan: a bad instance type or a malformed variable reference wastes a CI cycle and, in a slow pipeline, several minutes of a developer's attention. Running tflint first, as a fast pre-check, means the more expensive policy scans only run against code that's already syntactically and structurally sound.
What does Checkov add that a human review misses?
Checkov encodes cloud security and compliance benchmarks — including CIS Benchmarks for AWS, Azure, and GCP — as reusable checks that run against every plan, not just the ones a reviewer happens to remember. It ships with built-in rules covering common misconfiguration patterns: S3 buckets without encryption or versioning enabled, security groups with 0.0.0.0/0 ingress on sensitive ports, IAM policies with wildcard actions or resources, and RDS instances without automated backups configured. Because these are codified checks rather than institutional knowledge, they run identically whether the pull request is reviewed by a senior engineer who's seen the pattern before or a newer hire who hasn't. Checkov's growth — passing a million downloads within about a year and a half of its initial release — reflects a broader shift: teams increasingly treat "does this Terraform diff meet our compliance baseline" as a question a scanner should answer definitively, not one left to reviewer memory during a rushed Friday-afternoon PR.
Why evaluate a Terraform plan instead of just the source files?
Static .tf files describe intent, but they don't always describe outcome — variables can be interpolated, modules can override defaults, and provider-computed values (like an auto-generated ARN or a data-source lookup) simply don't exist until Terraform resolves them. Scanning raw HCL alone can miss a misconfiguration that only becomes visible once those values are resolved, or produce false positives when a variable defaults to something insecure that a caller always overrides. The standard fix is to generate a machine-readable plan and evaluate that instead: terraform plan -out=plan.tfplan followed by terraform show -json plan.tfplan produces a full JSON representation of every resource Terraform intends to create, modify, or destroy, with every value fully resolved. Both Checkov and OPA (via Conftest) support consuming this JSON directly, which is why plan-based scanning has become the recommended pattern over pure source-code scanning for any policy check that depends on final, resolved configuration rather than declared intent.
How does OPA let you enforce rules Checkov doesn't ship with?
Open Policy Agent is a general-purpose policy engine, not a Terraform-specific scanner — it evaluates any structured input, including the JSON from terraform show -json, against rules written in Rego, OPA's declarative policy language. That generality is the point: Checkov's built-in checks encode common industry benchmarks, but every organization has its own rules that no off-the-shelf scanner will ever ship — "no resource may be tagged without a cost-center label," "no new IAM role may attach AdministratorAccess," "no RDS instance may be created outside the us-east-1 and eu-west-1 regions approved for data residency." Conftest wraps OPA specifically for testing configuration files like Terraform plans, letting a platform team write these org-specific rules once, in version-controlled Rego files, and apply them uniformly to every plan across every repository. OPA's CNCF Graduated status — the same maturity tier held by Kubernetes and Prometheus — reflects how thoroughly this same engine is already relied on elsewhere in the stack, from Kubernetes admission control to API authorization, making it a natural fit for infrastructure policy too.
How do you chain these into one CI gate before apply?
The three tools address different layers, so the effective pattern is sequential, not redundant: run tflint first as a fast syntax and best-practice pass, then generate a resolved plan with terraform plan -out=plan.tfplan && terraform show -json plan.tfplan, then run Checkov against that plan JSON for benchmark-level checks, and finally run Conftest with your org's Rego policies for anything benchmark scanners don't cover. Each stage should fail the pipeline independently, with clear output identifying exactly which resource and which rule failed, so a developer can fix a misconfigured security group in the same PR rather than discovering it during a post-deploy audit. Because all three tools run against version-controlled code and machine-readable output, the entire gate is reproducible: the same Terraform diff produces the same pass or fail result whether it runs on a laptop or in CI, which is exactly the property that makes policy-as-code a meaningfully stronger control than a human checklist attached to a PR template.