OPA & Terraform: The Definitive Guide to Policy-as-Code Guardrails (2025 Edition)
Master the integration of Open Policy Agent (OPA) with Terraform to enforce security, compliance, and operational best practices on your infrastructure as code.
π Prerequisites
- Basic to intermediate knowledge of Terraform (
plan,apply, providers). - Familiarity with command-line tools and JSON data structures.
- Docker installed for running OPA easily.
- A code editor like VS Code with a Rego extension is recommended.
π― What You'll Learn
- Why combine OPA and Terraform for infrastructure governance.
- The modern workflow for validating Terraform plans with OPA's native functions.
- How to write practical Rego policies for real-world security and compliance.
- How to write robust unit tests for your Rego policies.
- How to integrate OPA checks into a CI/CD pipeline like GitHub Actions.
π·οΈ Topics Covered
Why Use OPA with Terraform?
Terraform has revolutionized infrastructure management, allowing us to define entire datacenters in code. However, this power brings a new challenge: how do you ensure that the thousands of lines of IaC written by dozens of teams adhere to your organization's security, compliance, and cost-management rules?
You could rely on manual pull request reviews, but this is slow, error-prone, and doesn't scale. This is where Open Policy Agent (OPA) comes in. OPA is a general-purpose, open-source policy engine that decouples policy decision-making from your software. When combined with Terraform, it provides a powerful framework for creating automated, code-based guardrails for your infrastructure.
Terraform
Defines the desired state of your infrastructure. It answers the question, "What do we want to build?"
Open Policy Agent (OPA)
Evaluates that desired state against a set of policies. It answers the question, "Is what we want to build allowed?"
The Core Workflow: Validating a Terraform Plan
The modern way to use OPA with Terraform leverages OPA's native built-in functions to parse Terraform plan files directly. This simplifies the entire process into two main steps.
Generate a Terraform Plan File
The standard terraform plan command creates a binary plan file that contains the proposed infrastructure changes.
terraform plan -out=tfplan.binary Evaluate the Plan with OPA
The opa eval command can directly ingest the binary plan file, evaluate it against your Rego policies, and output any violations.
opa eval --input-file tfplan.binary -d policy.rego "data.terraform.validation.violation" Writing Production-Ready Policies
Let's write a few practical Rego policies that use the native terraform.parse_plan() built-in for common use cases.
Example 1: Enforce Mandatory Tags
This policy ensures every EC2 instance and S3 bucket has Owner and CostCenter tags.
package terraform.validation
import rego.v1
plan := terraform.parse_plan(input.raw_plan)
violation[msg] if {
resource := plan.resource_changes[_]
resource.type in ["aws_instance", "aws_s3_bucket"]
contains_element(resource.change.actions, "create")
provided_tags := object.keys(resource.change.after.tags)
required_tags := {"Owner", "CostCenter"}
missing_tags := required_tags - provided_tags
count(missing_tags) > 0
msg := sprintf(
"%s is missing required tags: %v",
[resource.address, missing_tags]
)
} Example 2: Disallow Public S3 Buckets
This policy checks for S3 buckets with the public-read or public-read-write canned ACLs.
package terraform.validation
import rego.v1
plan := terraform.parse_plan(input.raw_plan)
violation[msg] if {
resource := plan.resource_changes[_]
resource.type == "aws_s3_bucket"
contains_element(resource.change.actions, "create")
resource.change.after.acl in ["public-read", "public-read-write"]
msg := sprintf(
"%s has a public ACL, which is not allowed.",
[resource.address]
)
} Unit Testing Your OPA Policies
Ensuring your policies work as expected is critical. OPA includes a robust testing framework that allows you to write unit tests for your policies using Rego itself. Create a file named s3_test.rego to test our S3 policy.
Example Test: s3_test.rego
package terraform.validation
import rego.v1
# Mock data for a non-compliant S3 bucket
mock_noncompliant_plan := {
"resource_changes": [{
"address": "aws_s3_bucket.unsafe",
"type": "aws_s3_bucket",
"change": { "actions": ["create"], "after": {"acl": "public-read"} }
}]
}
# Test case: A non-compliant bucket should have exactly ONE violation.
test_noncompliant_bucket if {
# The 'with' keyword provides mock data for the test
violations := violation with terraform.plan as mock_noncompliant_plan
count(violations) == 1
contains(violations[_], "has a public ACL")
} You can run these tests with the simple command: opa test . --verbose. This allows you to validate your policy logic before deploying it into your CI/CD pipeline.
Automating Enforcement with CI/CD
The real power of OPA is realized when you automate it. Hereβs an example of a GitHub Actions workflow that runs your OPA policies on every pull request.
.github/workflows/opa-check.yml
name: Terraform Plan & OPA Policy Check
on:
pull_request:
paths:
- '**.tf'
- 'policies/**.rego'
jobs:
opa-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Setup OPA
uses: open-policy-agent/setup-opa@v2
with:
opa-version: latest
- name: Terraform Init
run: terraform init
- name: Terraform Plan
run: terraform plan -out=tfplan.binary
- name: Run OPA Policy Check
run: |
opa eval --fail-defined --input-file tfplan.binary --data policies/ "data.terraform.validation.violation" The key flag here is --fail-defined. It tells OPA to exit with a non-zero status code if the violation rule produces any results. This will automatically fail the pipeline step and block the pull request from being merged, effectively enforcing your guardrails.