Terraform

Operating OPA/Rego Policies for Terraform: A Pragmatic Alternative to Sentinel

2026-04-19
NicheeLab Editorial Team

Terraform Cloud/Enterprise Sentinel is not the only answer for Terraform policy review. Pre-apply review with OPA (Rego) delivers consistent governance across multiple tools and clouds.

This guide centers on OPA/Rego validation that takes the plan JSON produced by terraform show -json as input, and covers comparisons, implementation patterns, exception management, and the points exams tend to focus on.

Why Use OPA/Rego for Terraform Policy Review

Terraform's plan output can be obtained as machine-readable JSON via terraform show -json. OPA (Rego) can take that JSON as input and produce allow/deny decisions, which makes it a great fit for CI gating. It is a strong choice when you are not using Terraform Cloud/Enterprise, or when you want a single policy language across multiple clouds.

In practice you generate the plan in CI, validate the JSON with conftest (a CLI that wraps OPA), and stop the merge or deploy on failure. Because the check runs consistently before apply, governance becomes preventive rather than after-the-fact.

  • Input should generally be plan JSON (the planned changes). State JSON is better for inspecting post-apply state.
  • Rego is declarative. Even complex conditions can be expressed as a composition of small rules.
  • Exam angle: nailing the plan JSON structure and the validation order in a CI pipeline is a reliable point of focus.
Developer PRterraform plan -out tfplanterraform show-json tfplanconftest test(OPA/Rego policy)Block (Fail)deny>0Proceed CIdeny==0Conceptual Terraform x OPA (Rego) review flow

Alternative Policy Approaches: When to Pick OPA vs Something Else

Policy review does not collapse to a single right answer. The best choice depends on team size, platforms in use, existing CI/CD, and operational cost. Below is a summary of the main options and where each fits.

Sentinel on Terraform Cloud/Enterprise is the quickest and most powerful path, but it ties you to the platform. OPA/Rego with Conftest avoids vendor lock-in and adapts to a wide range of use cases. Static HCL analysis (tfsec, Trivy, etc.) catches issues early but does not understand the actual plan diff.

  • Selection criteria: operational cost, expressive power, enforcement point (pre or post apply), and platform lock-in.
  • A hybrid approach is the most resilient: static analysis for early detection plus plan-based OPA as the final gate.
ApproachTarget Layer/ObjectEnforcement PointStrengths
OPA/Rego + ConftestTerraform plan (JSON)Right after plan in CILow vendor lock-in, expressive, reusable across tools
Terraform Cloud/Enterprise SentinelPlan/apply between runsJust before or after plan/applyTight platform integration; simple to operate
Static HCL analysis (tfsec, Trivy, etc.)HCL / static codeFrom PR creation to pre-planEarly detection; easy to adopt
Manual review (review conventions)PRs / design documentsAnywhereFlexible; supports context-aware judgement

Rego Patterns: Implementation Tips Using Plan JSON

At a minimum, reference the resource_changes array and each element's change.after. The targets are typically create, update, and replace (= ["create","delete"]). Exclude pure deletes and no-ops.

Guardrails such as required tags, naming conventions, region restrictions, and size limits become much easier to test when implemented as a pattern that returns a single deny set.

  • Input: assume terraform show -json tfplan > plan.json
  • Target actions: ["create"], ["update"], ["create","delete"]
  • Normalize deny messages (resource type.name.reason) so they are easy to grep in CI logs.

Rego example: require an owner tag policy (input is plan JSON)

package tfplan.tags

deny[msg] {
  some i
  rc := input.resource_changes[i]
  should_check(rc.change.actions)
  after := rc.change.after
  after != null
  required := "owner"
  not has_required_tag(after, required)
  msg := sprintf("%s.%s is missing tag %q", [rc.type, rc.name, required])
}

should_check(actions) {
  actions == ["create"]
} or {
  actions == ["update"]
} or {
  actions == ["create", "delete"]  # replace
}

has_required_tag(after, key) {
  tags := get_tags(after)
  tags[key]
}

get_tags(obj) = tags {
  tags := obj.tags
} else = tags {
  tags := obj.tags_all
} else = tags {
  tags := {}
}

Shape Your Input Data: Plan JSON or State JSON

If the policy's primary goal is to verify the safety of what is about to be created or changed, plan JSON is the first choice. You evaluate the diff directly and keep false positives low. If the goal is inventory work like compliance audits or reconciling an asset register, state JSON is the useful one.

On the Rego side, abstract attribute lookups into helper functions to handle input variation (provider-specific attribute names). Push common concepts like tags and names into wrapper functions so the rule body can focus on business intent.

  • Plan focuses on the diff (intent); state focuses on the actual outcome.
  • Absorb provider differences with helpers (e.g., tags vs tags_all).
  • At scale, split the input across multiple workspaces or directories so each run stays lightweight.

CI/CD Integration: Operate Reliably with Minimal Steps

The basic shape has three steps: 1) terraform init/plan, 2) convert to JSON with terraform show -json, 3) apply Rego with conftest test. Conftest exits 0 on pass and non-zero on deny, so its exit code can serve directly as a job gate.

For multi-module setups, the realistic design is to run plan → conftest in parallel for each directory and fail the whole job if any directory fails. Use caching appropriately to keep plan generation cost down.

  • Recommended: turn the plan file (tfplan) into an artifact to guarantee reproducibility.
  • Supply policies pinned to a version (tag plus checksum verification).
  • Include a ticket number in the deny message so investigations stay traceable.

Exception Management and the Policy Lifecycle

Zero exceptions is unrealistic in real environments. Build an exception mechanism into Rego (annotation files or metadata) and explicitly manage the expiration, scope, and rationale. Exceptions must always have an expiration, a reason, and a limited scope.

Policy changes can be breaking. Roll out new rules in stages — warn mode first, then enforce mode — and run them in a CI sandbox period of roughly two weeks to stay safe.

  • Track exceptions in source control (e.g., exceptions.yaml) so they remain auditable.
  • Introduce in stages from warn to deny to understand impact.
  • Operate rule IDs and change history through a CHANGELOG.

Check Your Understanding

Pro

問題 1

An organization wants to automatically validate required tags and naming conventions against Terraform plans. Terraform Cloud/Enterprise is not in use. Which approach implements a pre-apply gate with the lowest operational cost?

  1. Convert terraform plan output to JSON via terraform show -json, apply OPA/Rego policies with conftest, and gate CI on the result.
  2. Strengthen terraform validate only, and block apply when validation fails.
  3. Prepare a manual review checklist and only run apply after the review passes.
  4. Audit only the state file on a regular cadence and roll back if issues are found.

正解: A

An automated pre-apply gate requires evaluating the plan diff. Converting the plan to JSON and validating with OPA/Rego lets you make strict decisions based on the diff and gate CI through the exit code. terraform validate is limited to syntax and some local checks, and manual review or post-hoc audits do not satisfy the automated gate requirement.

Frequently Asked Questions

Should I feed plan JSON or state JSON into my policy?

Plan JSON is the first choice for pre-apply gates because it evaluates the diff (what is about to change). State JSON is better suited to inventory, post-hoc audits, and asset visibility. A two-tier setup that uses both is also effective.

Provider attribute names differ in Rego. How should I structure this?

Abstract attribute lookups into helper functions so they are decoupled from the rule body. For example, define get_tags that handles both tags and tags_all, and have downstream logic depend on the return value of get_tags.

What should I implement first to start small?

Start with a single high-impact rule in Rego (e.g., require an owner tag on critical resources) and wire the minimal CI path: plan → show -json → conftest. Set up observability for pass/fail (clear deny messages, artifacts) first so the system is easy to extend.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.