As organizations scale their cloud infrastructure, the manual review of Terraform configurations becomes a bottleneck. Security vulnerabilities, cost overruns, and compliance violations can slip through automated CI/CD pipelines if strict governance is not applied. This is where "Policy as Code" (PaC) transforms the DevOps landscape, shifting security from a gatekeeping step to an integrated, automated enforcement mechanism.
In this post, we will explore how to integrate Open Policy Agent (OPA) and HashiCorp Sentinel into your Terraform workflows to ensure that every infrastructure change aligns with your enterprise's security and compliance standards.
Why Policy as Code Matters
Traditional infrastructure management relies on human reviewers checking pull requests for compliance. This approach is slow, subjective, and prone to error. Policy as Code allows you to define rules in a machine-readable language that automatically evaluates Terraform plans before they are applied. This shifts security left, preventing non-compliant resources from ever reaching production.
There are two primary engines for enforcing these policies in the Terraform ecosystem: Open Policy Agent (OPA) and HashiCorp Sentinel. While both serve similar purposes, they differ in architecture and integration depth.
Open Policy Agent (OPA): The Flexible Choice
OPA is an open-source, general-purpose policy engine that uses the Rego language. It is cloud-agnostic and can be integrated into various stages of the CI/CD pipeline. OPA works by evaluating a decision against a set of policies, returning a boolean result or a set of violations.
Example: Restricting AWS Instance Types with OPA
Suppose your company requires that no production servers use small instance types. You can write a simple Rego policy to enforce this.
package terraform.aws
# Deny if instance_type is not allowed
deny[msg] {
input.resource_changes[_].type == "aws_instance"
instance := input.resource_changes[_].change.after
not allowed_instance_types[instance.instance_type]
msg := sprintf("Instance type %s is not allowed in production.", [instance.instance_type])
}
# Define allowed instance types
allowed_instance_types := {"t3.medium", "t3.large", "m5.large"}
This policy can be evaluated using the tfplan data structure provided by the OPA Terraform provider. If a pull request attempts to create a t3.micro instance in production, the pipeline fails with a clear error message.
HashiCorp Sentinel: The Native Integration
Sentinel is HashiCorp's native policy engine, deeply integrated into Terraform Enterprise (now part of Terraform Cloud/Enterprise). It uses a language designed specifically for policy definition, making it more readable for Terraform users. Sentinel policies run during the plan phase and can enforce policies on both Terraform plans and Terraform state.
Example: Enforcing Tagging Standards with Sentinel
Compliance often requires resources to have specific tags. Here is a Sentinel policy that ensures all EC2 instances have a CostCenter tag.
import "tfplan/v2" as tfplan
# Main enforcement rule
main = rule all resource in tfplan.resource_changes {
resource.type == "aws_instance" and
resource.mode == "managed" and
resource.change.after.tags["CostCenter"] != null
}
If a resource lacks the required tag, Sentinel blocks the apply action. This is particularly useful in enterprise environments where Terraform Cloud is the central orchestrator, as Sentinel policies can be attached directly to workspace policies in the UI.
Choosing Between OPA and Sentinel
Choosing between OPA and Sentinel depends on your infrastructure maturity and existing tooling:
- Use Sentinel if: You are using Terraform Cloud/Enterprise and want a seamless, native experience. It requires less custom configuration for integration.
- Use OPA if: You need a cloud-agnostic solution that can be reused across different IaC tools (like CloudFormation or Kubernetes Helm charts). OPA also offers a more mature ecosystem for complex logic and decision-making.
Best Practices for Implementation
- Version Control Policies: Treat your policy files as code. Store them in your version control system alongside your Terraform modules to ensure history and auditability.
- Fail Fast: Run policy checks in the pull request stage. This provides immediate feedback to developers before code is merged.
- Granular Policies: Write small, focused policies rather than monolithic rulesets. This makes debugging easier and allows for modular reuse.
- Documentation: Clearly document what each policy checks and why. Developers need to understand the rationale behind blocked deployments.
Conclusion
Implementing Policy as Code is not just about automation; it is about embedding security and compliance into the DNA of your development process. Whether you choose OPA for its flexibility or Sentinel for its native integration, the result is the same: a more secure, compliant, and efficient infrastructure lifecycle. By adopting these practices, enterprise teams can move faster without sacrificing control, turning compliance from a blocker into an enabler of innovation.