Terraform + Open Policy Agent: How to Industrialize Infrastructure-as-Code Compliance
In a world where cloud deployment speed has become a competitive edge, companies are multiplying Terraform templates to automate infrastructure creation. But this massive automation creates a blind spot: how can we ensure that deployed resources comply with internal and regulatory policies?
This is where Open Policy Agent (OPA) comes in—specifically its Terraform-focused modules: OPA with Conftest, or OPA via Terraform Cloud/Enterprise using Sentinel or native integration. In this article, we outline a practical approach to industrialize IaC compliance by integrating OPA into a Terraform CI/CD pipeline.
Why use Open Policy Agent (OPA) for Terraform?
OPA is a declarative policy engine based on the Rego language. It allows you to define governance rules as code—rules that are reusable, version-controlled, testable, and auditable.
Key benefits:
- Clear separation between infrastructure code (Terraform) and compliance rules (OPA)
- Automated deployment in CI/CD pipelines
- Fine-grained controls (e.g., block public S3 buckets except for specific projects)
- Broad ecosystem compatibility (GitHub Actions, GitLab CI, Jenkins, Atlantis, etc.)
Implementation architecture
Let’s take a real-world example: a GitHub Actions workflow where each terraform plan
and terraform apply
is validated through an OPA phase, preventing any compliance violations.
graph TD; dev[Developer pushes Terraform code] --> pr[Pull Request on GitHub]; pr --> ci[GitHub Action - Terraform Plan]; ci --> opa[OPA Validation Phase with Conftest]; opa -->|OK| appro[Manual or Automatic Approval]; opa -->|KO| reject[PR or Apply Rejected];
Example repository structure:
├── main.tf
├── modules/
├── policies/
│ ├── aws/
│ │ ├── s3_no_public.rego
│ │ ├── ec2_no_public_ip.rego
│ └── rules_test/
│ └── s3_no_public_test.rego
├── .github/
│ └── workflows/
│ └── terraform-ci.yml
Example of a Rego policy
Here’s a policy that blocks public S3 buckets unless they are explicitly tagged with AllowPublic=true
:
package policies.aws.s3
deny[msg] {
input.resource_type == "aws_s3_bucket"
input.resource_name == bucket
input.config.acl == "public-read"
not input.config.tags["AllowPublic"] == "true"
msg := sprintf("Bucket %s must not be public without explicit exception", [bucket])
}
Associated test:
package policies.aws.s3
test_deny_public_bucket {
deny with input as {
"resource_type": "aws_s3_bucket",
"resource_name": "bucket_infra_test",
"config": {
"acl": "public-read",
"tags": {}
}
}
contains deny, "Bucket bucket_infra_test must not be public"
}
Local execution of conftest to test OPA policies
Before integrating OPA into a CI/CD pipeline, developers should be able to validate their Terraform configurations locally against compliance rules. This helps reduce back-and-forth in pull requests.
Local testing steps:
- Install Conftest:
brew install conftest # macOS
# or via Go:
go install github.com/open-policy-agent/conftest@latest
- Generate Terraform plan in JSON:
terraform init
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
- Run Conftest with your rules:
conftest test tfplan.json --policy policies/aws
Expected result:
If the s3_no_public.rego
rule is violated:
FAIL - tfplan.json - Bucket my-bucket-prod must not be public without explicit exception
1 test, 0 passed, 1 failed, 0 warnings
Recommended structure:
Version your policies in a policies/
folder at the root of each Terraform project, or centralize them in a shared Git repo (e.g., terraform-policies/
).
This empowers developers to shift security left and validate compliance before pushing code, making the CI/CD pipeline smoother and more autonomous.
Integration in GitHub Actions
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init & Plan
run: terraform init && terraform plan -out=tfplan.binary
- name: Terraform JSON Plan
run: terraform show -json tfplan.binary > tfplan.json
- name: Run OPA with Conftest
run: |
conftest test tfplan.json --policy policies/aws --output table
🚨 Tip: Use
terraform show -json
to generate a plan that OPA can interpret.
Policy Governance and Management
To industrialize OPA usage in large organizations:
- Centralize rules in a versioned policy repo with unit testing pipelines (
rego test
) - Allow exceptions through tags, environment variables, or contextual input sources (project, env, team)
- Integrate OPA in Terraform Cloud or Enterprise for hard-blocking or soft-fail policies
Field Feedback
Best Practices:
- Build a modular OPA rule framework (by cloud provider, resource type)
- Document each rule with a
README.md
explaining its business and security rationale - Include automatic tests for the policies themselves
Pitfalls to Avoid:
- Don’t block everything upfront—start with an “audit-only” mode
- Avoid overly complex business logic in Rego; keep rules simple and focused
- Plan for scale: What happens when you grow from 30 to 300 Terraform modules?
Conclusion
OPA enables compliance to become a native, automated part of your IaC deployments—without slowing teams down. Coupled with Terraform, it acts as a true infrastructure-as-code firewall, aligning DevOps agility with security requirements.
Industrializing this approach ensures every terraform apply
complies with your internal policies—without human intervention. A must-have in any scaled cloud environment.