RSS

What is Open Policy Agent (OPA) and how to implement it with Terraform

Open Policy Agent Terraform Kubernetes Security

What you'll learn: This article walks through Open Policy Agent from first principles to when you're actually running an OPA policy against a Terraform plan, enforcing concrete security rules like S3 encryption and security group hardening, then shows how the same ideas extend into Terrateam so that policy enforcement becomes part of your normal pull request workflow.

Read our guide on how Open Policy Agent works as a general-purpose policy engine, how Rego expresses policy logic in a high-level declarative language, how Terraform produces the structured data that OPA evaluates, and how to wire everything together in modern infrastructure as code pipelines that need real cloud security and compliance policies – not just best-effort code review.

What is Open Policy Agent

Open Policy Agent is an open source, general-purpose policy engine that unifies policy enforcement across the stack, from Kubernetes resources and container images to Terraform plans, API gateways and custom application code. It lives as a separate policy agent that you query with an input document – usually JSON data that describes the user, resources and context – and it returns a policy decision that your systems can enforce.

The project is a graduated project in the CNCF landscape, originally created at Styra, which over time has become the default answer when people want OPA for cloud native technologies.

Most organizations reach for it when they need one place to define and manage policies that span cloud infrastructure, security policies for microservices, and compliance policies that auditors actually care about, instead of scattering one-off checks through application logic and bash scripts.

OPA provides a "high-level declarative language" and simple APIs so you can write policy definitions as code and offload policy decision making from your systems into a common policy engine.

How does Open Policy Agent work?

Conceptually, Open Policy Agent's Terraform users, Kubernetes operators and application engineers all interact with OPA the same way. There are always three pieces moving:

  1. The policy language Rego, where you write policy rules and deny rule logic
  2. The data that represents the world right now, or the proposed change you want to check
  3. A query that asks OPA for a policy decision, such as allow or deny

In practice, OPA evaluates policies over structured JSON data that represents things like a Terraform plan, a Kubernetes manifest, an HTTP request, salary information in an internal HR system, or API requests passing through API gateways.

Your application or tool sends that policy input into OPA along with the Rego policy, and OPA returns a result that you inspect – maybe a boolean allow flag, maybe a list of policy violations that explain which rules failed and why. This pattern of decoupling policy from application code is the core of how OPA works and why it scales across so many use cases.

OPA runs either as a long-lived policy agent sidecar, receiving queries over HTTP, or as a command line tool in CI, where you run OPA against files on disk. In both cases, you integrate OPA by pushing in an input document, usually JSON, and asking it to evaluate some package and rule, like data.terraform.deny or data.kubernetes.admission.allow.

The same policy library can be shared between CI jobs, admission controllers and runtime services, which is where OPA really enables unified policy enforcement across cloud native environments.

What are some use cases for OPA?

Once you understand that OPA evaluates Rego policies over JSON, you start seeing small, specific examples everywhere.

A team responsible for cloud security can write security policies that check Terraform plans to ensure resource limits on production databases, block public S3 buckets, enforce encryption and stop overly permissive security groups before they ever reach AWS.

Another group might focus on organizational policies around tagging, cost centers and environment labels, using an OPA policy to enforce that every resource has the right tags so chargeback and cost reports stop falling apart.

In application stacks, OPA manages fine-grained access control, handling which specific users can see salary information, which services can perform sensitive API requests, where compliance rules require additional checks or approvals, and which teams are allowed to deploy to specific production environments.

Kubernetes teams use OPA to validate Kubernetes resources before they are admitted to the cluster, enforcing rules on container images, network policies and pod security standards. API gateways rely on OPA to evaluate security rules for each call, so business logic can stay cleaner and policy logic can evolve independently as regulations, compliance requirements and threat models change.

Because the policy engine is general-purpose and domain-agnostic, you can integrate OPA anywhere you can provide JSON input – from CI pipelines that validate infrastructure as code to custom internal tools that need consistent decision-making based on shared organizational policies, instead of each team reinventing its own set of policy checks.

What are the benefits of using Open Policy Agent?

Centralized, reusable policy definitions

The obvious benefit is that you can enforce policies consistently across multiple systems without copying the same checks everywhere.

You define policies once in a central policy library, store them in a code repository next to the systems they protect, and let different tools query OPA for decisions during their workflows. That decoupling of policy decision-making from application logic reduces human error because developers stop embedding ad-hoc checks in random places and instead rely on a single, reviewable source of truth expressed in code.

Context-aware policy enforcement

Another benefit is context-aware policy enforcement.

OPA can combine Terraform plan data with external structured data such as inventories of approved regions, lists of sensitive resources or user group memberships, and make decisions that depend on the full context, not just the resource being changed.

That is where open policy shines, since you're no longer limited to simple boolean flags inside Terraform alone but can fold in everything your organization knows about environments, projects, and risk.

Policies that are easier to reason about and test

Because policies are written in a high-level declarative language, they are easier to reason about and test than imperative scripts.

You can run opa test against Rego files, version your policy changes, add unit tests around specific scenarios such as a security group with 0.0.0.0/0 ingress, and review policy changes in pull requests just like any other code.

Combined with infrastructure as code, this gives you an end-to-end workflow where security, compliance and cost controls are codified, tested and reviewed instead of living in spreadsheets.

Consistency across cloud native technologies

Finally, Open Policy Agent brings consistency across cloud native technologies.

The same engine that validates Terraform plans can also validate Kubernetes manifests, filter api gateway calls, and constrain CI pipelines, meaning most organizations can consolidate scattered rules into a single system for policy management, enforcement and audit.

What is Rego?

Rego is the policy language used by Open Policy Agent. It's designed as a high-level declarative language for expressing policy rules over immutable data. Instead of writing imperative code that loops and mutates state, you describe the conditions under which something should be allowed or denied, and OPA figures out whether those conditions hold for the given input.

In Rego, you typically define a package, some helper rules, and one or more top-level rules, such as allow or deny.

A deny rule often builds up a list of messages describing each violation, which is a natural fit for security policies and compliance policies where you want to explain to the user why something is blocked.

Rego rules can traverse nested JSON data, perform set and array operations, and combine simple predicates into more complex policy logic without ever dropping down into application code.

A minimal Rego file for Terraform might look like this, with a simple example that blocks any security group that exposes SSH to the internet. This is a direct answer to the question many OPA users start with, "What is opa going to do for my security groups?" and it shows how succinct writing policies can be in Rego.

package terraform.security_groups

default deny = []

deny[msg] {
  sg := input.resource_changes[_]
  sg.type == "aws_security_group"

  ingress := sg.change.after.ingress[_]
  cidr := ingress.cidr_blocks[_]

  cidr == "0.0.0.0/0"
  ingress.from_port <= 22
  ingress.to_port >= 22

  msg := sprintf("security group %s exposes SSH to the internet", [sg.address])
}

Here, the input document is the Terraform plan JSON, the deny rule collects any policy violations it finds, and the message becomes part of the policy decision that OPA returns to the caller.

How to implement OPA policies with Terraform

Using Open Policy Agent in Terraform workflows is mostly about connecting two existing tools.

Terraform already knows how to turn HCL into a plan and then into JSON, and OPA already knows how to read that JSON as policy input and evaluate Rego policies against it.

Terraform describes the desired cloud infrastructure, OPA checks that description before it is applied and enforces policy rules around security, cost, compliance or anything else you care about.

At a high level, you ask Terraform to create a binary plan, convert that plan to JSON, and then run OPA on that JSON with your policies loaded. The rest is just details around how you manage policies, which queries you run, and how you wire this into CI or Terrateam.

Step 1: Write a Rego policy for Terraform configuration

Start with a concrete requirement – for example, that every S3 bucket must have server-side encryption enabled. You can translate that into Rego by looking at input.resource_changes, filtering for aws_s3_bucket resources, and checking whether the encryption configuration is set in the planned state.

package terraform.s3_encryption

default deny = []

deny[msg] {
  r := input.resource_changes[_]
  r.type == "aws_s3_bucket"
  r.change.after.bucket != ""
  not has_encryption(r)

  msg := sprintf("S3 bucket %s has no server side encryption configured", [r.address])
}

# Inline bucket encryption configuration
has_encryption(r) {
  r.change.after.server_side_encryption_configuration.rule[_].
    apply_server_side_encryption_by_default.sse_algorithm != ""
}

# Separate aws_s3_bucket_server_side_encryption_configuration resource
has_encryption(r) {
  some enc
  enc := input.resource_changes[_]
  enc.type == "aws_s3_bucket_server_side_encryption_configuration"
  enc.change.after.bucket == r.change.after.bucket
}

This policy definition is just Rego; there's nothing Terraform-specific beyond the structure of the JSON that Terraform produces.

You could add more rules for lifecycle configuration, block public access or policy changes, all in the same Rego file.

If you want to enforce multiple organizational policies at once, you can group them under a package such as terraform.policies and have one top-level deny rule aggregate results from several specialized rules.

Step 2: Integrate OPA with Terraform

Once you have a Rego file, you need Terraform to generate the JSON data that OPA will read. The official Open Policy Agent documentation shows the standard approach, which works across tools. First, Terraform builds a binary plan, then terraform show converts it to JSON.

terraform init

terraform plan -out=tfplan.binary

terraform show -json tfplan.binary > tfplan.json

That tfplan.json file is the policy input you will feed into OPA. To run OPA on this input with the S3 encryption policy from above in a policy directory, you can use the CLI.

opa eval \
  --input tfplan.json \
  --data policy \
  "data.terraform.s3_encryption.deny"

If the deny rule returns an empty array, there are no policy violations. If it returns messages, you treat that as a failed policy decision and stop the pipeline.

Most organizations wrap these commands in a small script or CI step so every plan goes through the same policy enforcement process, and they run OPA tests against their Rego files to ensure policy changes behave as expected before rolling them out.

You can build more sophisticated flows using tools from the OPA ecosystem, but the basics remain the same. Terraform turns plans into JSON, OPA evaluates policies over that JSON, and your pipeline enforces the result.

Step 3: Enforce S3 encryption policy

To see the S3 encryption policy in action, consider a Terraform configuration that creates a bucket without any encryption.

provider "aws" {
  region = "us-west-1"
}

resource "aws_s3_bucket" "logs" {
  bucket = "example-logs-bucket"
}

If you run the plan and convert it to JSON, then run OPA as shown earlier, the deny rule will fire and return a message telling you that aws_s3_bucket.logs has no server-side encryption configured. That is a clean example of a policy decision where OPA evaluates a simple rule over cloud infrastructure described as code and prevents a problem before it ever reaches AWS.

If you then update the configuration to include encryption, OPA will pass the plan.

resource "aws_s3_bucket" "logs" {
  bucket = "example-logs-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}

Run the same sequence of terraform plan, terraform show -json and opa eval, and the deny rule returns an empty list.

That's how Open Policy Agent and Terraform together can enforce cloud security requirements automatically: in a way that is visible, testable and easy to evolve as your policy definitions grow.

Using OPA with Terrateam

Terrateam is a GitOps orchestration engine for infrastructure as code that integrates directly with Open Policy Agent to provide policy enforcement on Terraform plans and applies.

In practice, that means every pull request can trigger Terraform planning, convert the plan into JSON, run OPA with your Rego policies, and report any policy violations back into the code review before anything is merged.​​

Because policy runs happen next to the code in the same pull request, you can keep your policy library in the same code repository as your modules, track policy changes along with application code, and enforce policies as early as possible.

Terrateam treats OPA as a native integration rather than an afterthought, so it becomes natural to define policies that are aware of team, environment and repository context in addition to the raw Terraform plan data, giving you genuinely context-aware policy enforcement in your cloud native environments.

In typical workflows, Terrateam uses tags and configuration files to decide which stacks and workspaces run which checks. You can configure some stacks to run only lightweight best practice rules, while critical production stacks run a richer set of OPA policies that cover security, compliance and resource limits.

Developers get fast feedback directly in their pull requests when they violate organizational policies, rather than discovering problems during a manual review or, worse, after apply.

Setting cost limits with OPA and Terrateam

One pattern that shows open policy at its best is cost control.

You can define policies that inspect Terraform plans for instance types, database sizes or autoscaling group settings and enforce resource limits or monthly cost estimates before changes land.

With Terrateam orchestrating the runs, OPA policies can look at both the plan JSON and metadata, such as which team owns the workspace or whether the stack is tagged as production, then apply different thresholds for different environments.

For example, a policy might deny any change where a non-production stack creates a database larger than a certain size, while allowing the same change in production but requiring explicit approval from a reviewer group.

Rego makes it straightforward to encode that business logic, using input data from the Terraform plan and additional context passed by Terrateam. OPA evaluates the rules, Terrateam surfaces the result in the pull request, and teams can adjust policy definitions over time without touching the underlying application code or Terraform modules.

Enforcing secure security group settings with OPA and Terrateam

Security groups are another place where human error is common, and policy enforcement is essential.

A single ingress rule with 0.0.0.0/0 for SSH can open an environment to the internet, and those mistakes are easy to miss in large diffs. With Open Policy Agent (OPA) wired into Terrateam, you can define policies that scan every planned change to aws_security_group resources and block any rule that exposes sensitive ports or protocols to the wrong CIDR ranges.

In this setup, Terrateam runs OPA during the plan stage and treats any non-empty deny rule as a failed check.

The policy might examine JSON data under input.resource_changes, filter for the relevant resource types, and produce clear messages when rules violate security standards.

Developers see those messages directly in the pull request, along with suggestions for how to fix them, and once they push a corrected commit, Terrateam re-runs the plan, and the same OPA policy passes.

The combination of infrastructure as code, Open Policy Agent, Terraform integration and Terrateam's GitOps workflow lets you enforce security rules continuously without slowing teams down with manual gatekeeping.

Conclusion

Open Policy Agent gives you a single, consistent way to define policies, manage policies and enforce policies across cloud infrastructure, applications and everything in between, using Rego as a high-level declarative language over structured data.

Terraform turns your desired cloud infrastructure into JSON, OPA evaluates that JSON according to your policy rules, and tools like Terrateam connect those pieces into a practical workflow where every pull request is checked automatically for security, compliance and cost issues.

If you want to go deeper, the Open Policy Agent documentation and OPA website include extensive guides, specific examples and reference material on integrating OPA with Terraform and other cloud native technologies, while there is a growing ecosystem of projects that build on the same foundations.

The important part is that you start with one or two clear policies (maybe a deny rule for public S3 buckets and a rule for SSH exposure), wire them into your plan workflow using Open Policy Agent terraform integration, and then grow your policy library as you discover more organizational policies that should be enforced as code.

Over time, OPA becomes the place where policy decision-making lives, Terrateam becomes the place where those decisions are executed in your GitOps workflows, and the result is modern infrastructure that is easier to reason about, easier to audit and much harder to misconfigure by accident.