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:

  1. Install Conftest:
brew install conftest  # macOS
# or via Go:
go install github.com/open-policy-agent/conftest@latest
  1. Generate Terraform plan in JSON:
terraform init
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
  1. 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.