TL;DR
- OPA is your go-to for multi-platform policy enforcement (Kubernetes, Terraform, APIs); Sentinel excels within the HashiCorp ecosystem
- Policy tests are code — write them before the policy itself using OPA’s built-in test framework
- The #1 mistake: treating policies as documentation instead of tested, versioned code
Best for: Teams managing infrastructure across multiple clouds or platforms needing unified governance Skip if: You’re 100% in Terraform Cloud/Enterprise and never touch Kubernetes Read time: 11 minutes
Your Terraform plan passed all tests. CI is green. You merge and deploy. Three hours later, someone provisions a publicly accessible S3 bucket with no encryption. Your security team is furious.
The problem isn’t your tests — it’s that you’re testing the wrong layer. Infrastructure tests verify what gets deployed. Policy tests verify what’s allowed to be deployed. In 2026, with AI tools generating Terraform configs faster than ever, this distinction is critical.
The Real Problem
Most teams approach policy as code backwards. They write policies after incidents, store them in a wiki, and hope developers read them. This is policy as documentation, not policy as code.
Real policy as code means:
- Policies are versioned in Git alongside infrastructure
- Policies have unit tests that run in CI
- Policies block deployments automatically, not via code review comments
- Policy violations produce actionable error messages, not cryptic failures
The shift from “guidelines” to “guardrails” requires a mental model change. You’re not writing rules for humans to follow — you’re writing executable constraints that machines enforce.
OPA vs Sentinel: The 2026 Reality
Open Policy Agent (OPA) v1.9.0 and HashiCorp Sentinel both solve policy enforcement, but their scopes differ dramatically.
| Aspect | OPA | Sentinel |
|---|---|---|
| Ecosystem | Platform-agnostic (K8s, Terraform, APIs, CI/CD) | HashiCorp stack (Terraform, Vault, Nomad, Consul) |
| Language | Rego (declarative, JSON-native) | HSL (HashiCorp Sentinel Language) |
| Testing | Built-in opa test command | CLI with sentinel test |
| Data Input | Any JSON (requires terraform show -json) | Native Terraform plan imports |
| Governance | DIY enforcement via CI gates | Built-in advisory/soft/hard enforcement levels |
| Cost | Open source, free | Requires Terraform Cloud/Enterprise |
My recommendation: Start with OPA if you work across multiple platforms or value portability. Choose Sentinel if you’re deeply invested in HashiCorp Cloud Platform and want tight integration with zero transformation steps.
Writing Testable OPA Policies
The key insight is that policies should be written test-first. Before coding the policy, define what should pass and what should fail.
package terraform.aws.s3
import rego.v1
# Deny S3 buckets without encryption
deny contains msg if {
resource := input.planned_values.root_module.resources[_]
resource.type == "aws_s3_bucket"
not has_encryption(resource)
msg := sprintf("S3 bucket '%s' must have server-side encryption enabled", [resource.name])
}
has_encryption(bucket) if {
bucket.values.server_side_encryption_configuration[_].rule[_].apply_server_side_encryption_by_default[_].sse_algorithm
}
Notice the import rego.v1 — this is the modern syntax as of OPA v1.x. The contains keyword and if guards are Rego v1 conventions that replace the older implicit set generation.
Testing Your Policies
OPA’s testing framework lets you verify policy behavior before deployment. Create a test file alongside your policy:
package terraform.aws.s3_test
import data.terraform.aws.s3
# Test: Encrypted bucket should be allowed
test_encrypted_bucket_allowed if {
count(s3.deny) == 0 with input as {
"planned_values": {
"root_module": {
"resources": [{
"type": "aws_s3_bucket",
"name": "secure_bucket",
"values": {
"server_side_encryption_configuration": [{
"rule": [{
"apply_server_side_encryption_by_default": [{
"sse_algorithm": "AES256"
}]
}]
}]
}
}]
}
}
}
}
# Test: Unencrypted bucket should be denied
test_unencrypted_bucket_denied if {
count(s3.deny) > 0 with input as {
"planned_values": {
"root_module": {
"resources": [{
"type": "aws_s3_bucket",
"name": "insecure_bucket",
"values": {}
}]
}
}
}
}
Run tests with opa test . -v to see detailed output. The -v flag shows which tests passed and any that failed with reasons.
CI/CD Integration
The workflow for Terraform + OPA in your pipeline:
# 1. Generate plan
terraform plan -out=tfplan.binary
# 2. Convert to JSON for OPA
terraform show -json tfplan.binary > tfplan.json
# 3. Evaluate policies
opa exec --decision terraform/aws/s3/deny --bundle policies/ tfplan.json
# 4. Fail if violations exist
if [ $(opa eval -d policies/ -i tfplan.json 'data.terraform.aws.s3.deny' | jq '.result[0].expressions[0].value | length') -gt 0 ]; then
echo "Policy violations detected!"
exit 1
fi
For Conftest (OPA’s dedicated config file testing tool), the syntax is simpler:
conftest test tfplan.json --policy policies/
Conftest automatically looks for deny rules and exits non-zero if any fire.
Sentinel for Terraform Cloud
If you’re using Terraform Cloud or Enterprise, Sentinel offers tighter integration. The equivalent S3 encryption policy:
import "tfplan/v2" as tfplan
s3_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket" and
rc.mode is "managed" and
(rc.change.actions contains "create" or rc.change.actions contains "update")
}
encryption_required = rule {
all s3_buckets as _, bucket {
bucket.change.after.server_side_encryption_configuration is not null
}
}
main = rule {
encryption_required
}
Sentinel’s tfplan/v2 import gives you native access to Terraform’s plan structure without JSON conversion. The enforcement levels (advisory, soft-mandatory, hard-mandatory) let you roll out policies gradually.
AI-Assisted Approaches
Writing policies in Rego or Sentinel requires learning a new language. In 2026, AI tools significantly accelerate this process.
What AI does well:
- Generating initial policy drafts from plain English requirements
- Converting between OPA and Sentinel syntax
- Creating comprehensive test cases for edge conditions
- Explaining complex Rego comprehensions
What still needs humans:
- Deciding which resources require which constraints
- Setting appropriate severity levels for violations
- Reviewing AI-generated policies for logical correctness
- Understanding the business context behind compliance requirements
Useful prompt:
Write an OPA Rego policy that:
1. Denies AWS EC2 instances without the "Environment" tag
2. Allows exceptions for instances with "temporary: true" in metadata
3. Include comprehensive unit tests for both cases
4. Use Rego v1 syntax with explicit imports
When This Breaks Down
Policy as code has limitations:
Complexity explosion: As policies grow, interdependencies become hard to track. A policy allowing t3.micro instances conflicts with a policy requiring encrypted EBS volumes on certain instance types.
False positives: Overly strict policies block legitimate use cases. Teams start asking for exemptions, and you end up with a policy full of exceptions.
Performance at scale: Evaluating thousands of policies against large Terraform plans can slow CI significantly. OPA handles this well with partial evaluation, but you need to optimize.
Drift between policy and reality: Policies enforce what’s planned, not what’s running. A manually-created resource bypasses all policy checks.
Consider complementary tools:
- Terraform drift detection for existing resources
- Cloud-native tools (AWS Config, Azure Policy) for runtime enforcement
- Compliance testing for IaC for regulatory requirements
Decision Framework
Choose OPA when:
- You manage Kubernetes alongside Terraform
- You need policies for API authorization or CI/CD gates
- Your infrastructure spans multiple clouds
- Budget constraints rule out Terraform Enterprise
Choose Sentinel when:
- You’re all-in on HashiCorp Cloud Platform
- You want enforcement levels without building custom CI logic
- Your team already knows Sentinel from Vault or Nomad
- You need first-class support and SLAs
Use both when:
- OPA for Kubernetes admission control (Gatekeeper)
- Sentinel for Terraform-specific governance in TFC
Measuring Success
| Metric | Before | After | How to Track |
|---|---|---|---|
| Policy violations in prod | Unknown | 0 | Cloud audit logs |
| Mean time to violation detection | Days/weeks | <5 minutes | CI pipeline timestamps |
| Policy coverage | 0% | 80%+ resources | opa test –coverage |
| False positive rate | N/A | <5% | Exemption request count |
Warning signs it’s not working:
- Teams bypassing CI to avoid policy checks
- Growing list of permanent exemptions
- Policies that never fire (too permissive)
- Policies blocking every PR (too strict)
What’s Next
Start small. Pick one high-impact policy — maybe S3 encryption or instance tagging — and implement it end-to-end:
- Write the policy with tests
- Add to CI as a warning (advisory mode)
- Monitor for false positives for one sprint
- Promote to blocking (mandatory mode)
- Expand to the next policy
The goal isn’t 100% policy coverage on day one. It’s building muscle memory for treating policies like production code.
Related articles:
- Terraform Testing and Validation Strategies
- Infrastructure as Code Testing
- GitOps Workflows for QA and Testing
- Compliance Testing for IaC
External resources: