Sentinel vs. OPA Policies for IaC

Introduction

Infrastructure as Code (IaC) lets teams define and provision infrastructure using code, bringing version control, repeatability, and automation to ops. As IaC adoption grows, policy-as-code becomes essential to enforce security, compliance, cost, and architectural standards early in the delivery pipeline. Two popular approaches are HashiCorp Sentinel and Open Policy Agent (OPA).

This post explains both, shows their syntax, compares them, walks through use cases, and wraps up with practical guidance.

Introduction to Sentinel

Sentinel is a policy-as-code framework created by HashiCorp. It’s tightly integrated with HashiCorp tools (notably Terraform Cloud/Enterprise, Vault, and Consul). With Sentinel, you write policies to evaluate runtime data (e.g., a Terraform plan) and decide whether to pass, warn/soft-mandate, or fail a run.

Key traits:

  • Tight integration with Terraform Cloud/Enterprise via policy sets.

  • Imports expose structured data like Terraform plans (tfplan/v2), state (tfstate), and configuration (tfconfig).

  • Enforcement levels: advisory, soft-mandatory, hard-mandatory.

  • Designed for enterprise governance with centralized policy management, versioning, and run logs.

Introduction to OPA Policies

Open Policy Agent (OPA) is a general-purpose, open-source policy engine. OPA policies are written in Rego, a declarative language. OPA is tool-agnostic and widely used across cloud-native systems (Kubernetes admission control with Gatekeeper, API authZ, CI checks, and IaC validation with tools like Conftest).

Key traits:

  • Open source and platform-agnostic; runs as a sidecar, CLI, daemon, or admission controller.

  • Strong ecosystem: Gatekeeper (Kubernetes), Conftest (file/IaC checks), bundles, tooling and libraries.

  • Policies written in Rego evaluate arbitrary JSON input (e.g., Terraform plans converted to JSON, Kubernetes manifests, CloudFormation templates).

Syntax

In this section, we’ll cover the syntax for both options and compare how they express policies.

Sentinel Syntax

To understand Sentinel syntax, let’s look at the example below.

# Sentinel policy: require_tags.sentinel
import "tfplan/v2" as tfplan

# Required tags
required = ["Owner", "Environment", "CostCenter"]

# Helper: get all planned resources from the AWS provider
aws_resources = func() {
  resources = []
  for tfplan.resource_changes as rc {
    if rc.provider_name is "registry.terraform.io/hashicorp/aws" {
      resources.append(rc)
    }
  }
  return resources
}

# Check that after changes, each resource has required tags
all_tagged = func() {
  for aws_resources() as rc {
    # "after" represents planned values after apply
    tags = rc.change.after.tags else {}
    for required as key {
      if not (key in keys(tags)) {
        return false
      }
    }
  }
  return true
}

# Enforce
main = rule {
  all_tagged()
}

At the top of the file, the statement import "tfplan/v2" as tfplan tells Sentinel that this policy reads the Terraform plan data interface. Below it, we define the required tag keys that must be present on any created or updated resource. Next, a helper function gathers the AWS resources from the plan, appends them to the resources list, and returns that list. The following function iterates over the resources returned by the helper and checks whether each required tag key is present; it returns false as soon as a tag is missing and true only if all resources include all required tags.

In Terraform Cloud/Enterprise, you attach this policy to a policy set. During a plan run, the engine evaluates the tfplan/v2 data and fails the run if any AWS resource is missing required tags. You can select enforcement levels (e.g., hard-mandatory to block the run).

OPA/Rego Syntax (Terraform via Conftest)

Now let’s express the same policy—enforcing the presence of mandatory tags for all AWS resources—in Rego.

package terraform.required_tags_policy

required_tags := {"Owner", "Environment", "CostCenter"}

deny[msg] if {
	# Iterate over all planned resources
	some rc in input.resource_changes
	rc.change.after != null
	startswith(rc.type, "aws_")

	# Fetch tags if present
	tags := rc.change.after.tags
	missing := {t | some t in required_tags; not tags[t]}
	count(missing) > 0

	msg := sprintf(
		"Resource %s.%s missing tags: %v",
		[rc.type, rc.name, concat(", ", missing)],
	)
}

At the top, we declare the package name and the required tags. The deny[msg] rule produces a message for each violation found. Inside that rule, some i introduces an index variable used to iterate over input.resource_changes. The line rc.change.after != null ensures we only evaluate resources that will exist after the apply (i.e., creates/updates/replacements) and implicitly skips pure deletions. We then extract the tags from the planned values, compute the set of missing required tags, and trigger a denial when count(missing) > 0. Finally, sprintf constructs a clear message that Conftest/OPA returns as output for the failing resources (it doesn’t “print” to a console by itself; the message is part of the rule’s result).

Comparison Between Sentinel and OPA Policies

To compare the policies let’s take a look at the table below:

Criterion Sentinel OPA/Rego
Where it runs Terraform Cloud/Enterprise during runs Anywhere (local dev, CI, pre-commit)
Language Sentinel Rego
Data sources tfconfig, tfplan, tfstate, tfrun terraform show -json input (generic JSON)
Governance Centralized policy sets, enforcement levels Flexible; you enforce via CI gates and pipelines
Best for Org-wide guardrails in TFC/TFE Developer shift-left checks; platform-agnostic
Cost Requires paid HashiCorp subscription Open source

What the differences mean in practice

  • Execution point: Sentinel evaluates inside Terraform Cloud/Enterprise runs, giving a single, centralized gate for apply decisions. OPA runs anywhere—great for catching issues earlier (pre-commit/CI) and for non-Terraform targets (e.g., Kubernetes).

  • Language & model: Sentinel is purpose-built for HashiCorp workflows with first-class imports like tfplan/v2. OPA uses Rego, a general policy language that evaluates arbitrary JSON—useful when you want one language across Terraform, Kubernetes, APIs, and more.

  • Data handling: Sentinel consumes rich, provider-aware Terraform data via imports, so you don’t need to transform anything. OPA typically consumes the JSON output of terraform show -json (or other JSON sources), which adds a conversion step but increases portability.

  • Governance style: Sentinel shines for centralized governance—policy sets, advisory/soft/hard enforcement levels, and clear audit trails within TFC/TFE. OPA emphasizes flexible enforcement, wired into your CI/CD gates or admission controllers; you design how/where policies are applied.

  • Best-fit scenarios: Choose Sentinel when your organization primarily runs Terraform in TFC/TFE and wants strong, centralized controls at apply time. Choose OPA when you want shift-left checks for developers and a single policy engine that spans multiple platforms.

Common pattern: Use OPA locally and in CI to block bad changes early, then use Sentinel in TFC/TFE for the final, centralized enforcement gate.

Use Case Scenarios

Here are some use case scenarios to consider:

1) Terraform Governance in Terraform Cloud/Enterprise

  • Problem: Enforce cost, security, and tagging standards across many workspaces.
  • Sentinel fit: Excellent. Attach policy sets to workspaces. Use tfplan/v2 and tfconfig to block risky patterns (e.g., open security groups, public S3 buckets, oversized instance types).
  • OPA fit: Viable but less native. You’d run plan-to-JSON and OPA in CI before TFC/TFE, or call out to an OPA service during your pipeline.

2) Pre-Commit / Local Developer Experience

  • Problem: Shift-left checks so developers catch issues before pushing.
  • Sentinel: Typically evaluated during TFC/TFE runs, not local by default (you can approximate with the Sentinel CLI in specific workflows, but it’s uncommon).
  • OPA: Great. Use Conftest in pre-commit hooks to test Terraform plan JSON, Helm charts, or Kubernetes YAML before pushing.

3) Multi-Cloud, Multi-Tool Policy Unification

  • Problem: One policy language across Terraform, Kubernetes, and custom services.
  • Sentinel: Best within HashiCorp ecosystem.
  • OPA: Purpose-built for this unification. Rego rules can be reused everywhere JSON is available.

4) Regulatory Compliance & Auditing

  • Problem: Demonstrate consistent controls with evidence.
  • Sentinel: Strong audit trail inside TFC/TFE; enforcement levels support gradual adoption (advisory → mandatory).
  • OPA: Strong too, with decision logging and policy bundles—requires you to set up the operational pieces yourself (or use Gatekeeper’s audit).

Conclusion: The Right Guardrails in the Right Place

The Good: Sentinel gives you a centralized, auditable checkpoint exactly where Terraform decisions are made. Policy sets, enforcement levels, and first-class access to tfplan/v2 make it a strong last gate before infrastructure changes go live.

The Bad: That centralization can come with limits. Sentinel shines in TFC/TFE, but it’s not a universal engine for Kubernetes, APIs, or bespoke JSON. Local-dev and pre-commit workflows are possible but not its sweet spot.

The Ugly: Relying only on a single, late-stage gate means you’ll catch issues after code review and CI. By then, developer feedback loops are longer, and remediation is more expensive.

Where OPA/Rego fits: OPA excels at shift-left. It runs anywhere, evaluates any JSON, and covers more than Terraform—think Kubernetes admission, CI checks, and config linting. The trade-off is you’ll do the plumbing (e.g., terraform show -json, CI wiring, policy bundles) and own the operational model.

Pragmatic takeaway: Don’t pick a winner; pick a layer. Use OPA early (pre-commit/CI and cluster admission) to prevent bad patterns from entering version control or your cluster. Use Sentinel in TFC/TFE as the final, centralized control that speaks Terraform natively and enforces organization-wide guardrails.

If you can only start with one, start where your risk concentrates:

  • Heavy Terraform in TFC/TFE? Start with Sentinel.

  • Mixed stacks or strong developer autonomy? Start with OPA.

Either way, measure violations, iterate on policies, and tighten enforcement over time. The goal isn’t policies for their own sake; it’s safer, cheaper, and more reliable infrastructure with every commit and every apply.

Thanks for reading!

Armen