Skip to content
advanced Phase 11 · Policy Enforcement, Security Scanning, and Secrets

OPA and Conftest Policy Gates

Writing Rego policies that evaluate plan JSON for organizational rules.

1h 5m
0 problems
Topic Progress 0%

Turn a reviewed plan into input for an OPA policy

A policy should evaluate what Terraform intends to change

Terraform configuration can express defaults, modules, variables, and computed values. A source-only rule may not know the final instance type, region, or network range. A plan contains Terraform's proposed actions and resolved values. Running terraform show with its JSON output option converts a saved plan into structured data that a policy engine can inspect. Open Policy Agent evaluates structured input against rules written in Rego. Conftest wraps OPA for testing configuration and plan-like documents from the command line, making it practical to run a policy check in CI.

A plan policy can reject a new public database, require encryption on storage resources, limit instance families, or enforce tags on resources that will be created or changed. It can also distinguish actions: a policy may require review for a destructive replacement while allowing an in-place tag update. The policy should inspect the planned resource changes, their address, type, actions, and relevant values. Checking only the final planned object may miss whether an operation deletes or replaces a resource; checking only resource types may miss a risky attribute. Choose the fields that express the control's intent.

The workflow begins by initializing the correct modules and providers, validating configuration, and creating a plan. Convert the exact saved plan artifact to JSON, then pass that document to Conftest. If any deny rule returns a violation, mark the required CI status failed so the pull request cannot merge or apply. Apply should use the reviewed plan artifact when the execution platform supports that workflow; regenerating a plan later may produce a different set of changes. Keep the plan, JSON representation, and policy result tied to the same commit.

Plan JSON has a structured schema with resource changes and planned values. Some attributes may be unknown until apply, especially values generated by cloud APIs. A robust policy distinguishes known values from unknown values rather than treating missing data as a safe default. A security rule should fail closed when a required control cannot be evaluated, or explicitly route the case for review. Document that behavior. Blindly comparing an unknown value to a required string can produce a false pass or a policy runtime error.

Sensitive values may appear in plan JSON even when command-line output redacts them. Restrict access to the saved plan and generated JSON, avoid publishing them as public artifacts, and delete them according to a short retention policy. Run policy code on trusted input in an isolated CI job. Avoid logging the entire document when a test fails; print the resource address and a safe explanation. If the policy only needs a few fields, transform or select those fields before sharing data across jobs.

OPA policies commonly define deny results with explanatory messages. A good message identifies the resource, violated requirement, and remediation path. For example, ?module.database.aws_db_instance.primary must enable storage encryption? is more useful than ?policy failed.? Keep policy decisions separate from explanatory metadata so the gate can block reliably and authors can understand it. Add unit fixtures for allowed and denied cases, including a planned deletion or an unknown attribute where relevant.

Conftest makes local feedback possible. Developers can run the same policy bundle against a local plan before opening a pull request. Pin the Conftest/OPA version and policy bundle revision in CI so local and remote results are comparable. If policy language or plan schema changes, upgrade deliberately and run fixtures before enabling the new version as a required check.

In an interview, explain why plan JSON is more informative than raw HCL for evaluated values, and name the limits: unknown values, sensitive artifacts, and provider-specific structure. Then describe how a deny result becomes a merge gate. This demonstrates a complete control path rather than just knowing that Rego exists.

The policy should model change intent. A plan's change action can be create, update, delete, or a replacement sequence containing multiple actions. A rule that blocks every delete may prevent legitimate decommissioning; a rule that allows all deletes may permit loss of critical data. Inspect whether the resource is protected by a backup or replacement process, and define the intended exception path. Preserve the resource address in violation messages so the reviewer can find it in the plan.

Write a policy that fails safely on planned resources

Iterate over resource changes and state the invariant directly

A Rego policy for a Terraform plan typically locates the resource change objects in the plan's resource_changes collection, filters for relevant resource types, and checks the proposed values. The control should read like an invariant: every managed storage bucket created or updated must have public access blocked; every database resource must have encryption enabled; or every production resource must include an owner tag. Keep the condition narrow enough to match the requirement and broad enough to cover module addresses and all relevant resource types.

Plan values can be nested. Provider schemas expose attributes in resource-specific shapes, and some settings are represented as lists or nested blocks. Read a known plan fixture to understand the shape before writing the rule. Normalize only what the policy needs, and account for absent or unknown data. A field omitted from the plan is not necessarily equivalent to false. Where a required security value is unknown, report that the policy cannot establish compliance rather than silently accepting it.

Policies should consider action types. A planned deletion does not need the same configuration checks as a create, but it may need a data-protection guard. An in-place update can make a previously compliant resource unsafe. A replacement can combine delete and create actions, so decide whether both sides should be checked. Test create, update, delete, and replacement examples. Also test a module-qualified resource address; policy code should not assume root-level resources only.

Conftest supports policy bundles and tests. Keep policy files with infrastructure code or in a versioned central policy repository. A fixture test can feed sample plan JSON to a rule and assert that compliant input passes while insecure input produces a violation. Negative tests matter: a policy that always returns an empty result passes every plan but enforces nothing. Include a deliberately insecure fixture in automated tests and verify the expected resource address appears in the failure.

Organize rules by domain or resource family. A single large policy can be hard to review and risky to change. Give each invariant a stable identifier and message. Add a short policy document that names the owner, rationale, severity, and exception path. When multiple teams share a policy bundle, use versioned releases and a deprecation process so a new rule does not unexpectedly block unrelated repositories.

Use input data only for the decision. Plan JSON can be large and may contain sensitive resource attributes. Do not include raw values in failure output unless they are known safe. A violation can name the address and attribute without printing credentials or user data. Store policy decision logs with appropriate access. If the gate runs on untrusted pull request code, do not provide cloud credentials to the policy job; plan creation and policy evaluation can be separated behind trust boundaries.

Avoid embedding provider assumptions in general rules without tests. A tag may be represented differently across resource types or provider versions. A module may omit a setting because it relies on an organizational default. The policy should either account for that supported pattern or require the module to expose an explicit value. Test policy changes against representative plans from each supported provider and module version.

Policy exceptions should remain visible. A narrow exception can identify a resource address, justification, owner, and expiration. For long-term exceptions, document the compensating control and schedule review. Do not add a broad ignore for an entire module because one resource needs unusual behavior. A policy is only as trustworthy as its change process; require review from the security or platform owner for modifications that weaken controls.

In an interview, walk through a specific invariant and its test cases. Explain how you handle an unknown field and how a violation blocks merge. Mention that policy code itself needs versioning and tests. This shows you know that a policy gate is executable production logic, not a one-time script.

Plan schema evolves with Terraform and providers. Keep fixtures generated by supported Terraform versions and validate them when upgrading. A parser that assumes an obsolete path can fail open if the CI wrapper ignores errors. Configure the pipeline so invalid JSON, missing plan files, policy runtime errors, and denied rules all produce a non-success status. Distinguish them in output so the team can repair the right failure.

Enforce plan policy without leaking plan data or hiding failures

Make the policy result a required part of deployment review

A secure CI design has a clear sequence: formatting and validation, plan generation with scoped credentials, plan JSON conversion, OPA/Conftest evaluation, review and approval, then apply. Source scanning can run earlier without cloud credentials. The policy stage must consume the plan created for that pull request revision. If the branch changes, regenerate plan and policy results. A stale green check is not evidence for the current commit.

Do not equate a successful command invocation with compliance. CI must fail when the policy denies a change, when plan JSON is invalid, when a required field is missing, or when the policy engine errors. Check exit codes and publish a concise summary. A job that catches every error and returns zero turns the gate into theater. Test the failure path with a known violating fixture and confirm branch protection treats the check as required.

Protect plan artifacts as sensitive. Binary plans and JSON may contain values derived from secrets, even if terminal logs redact them. Restrict artifact read access, use short retention, and avoid attaching the full plan to a public pull request. Policy output should state the resource and reason without exposing secret values. If the organization must preserve evidence, store it in an access-controlled audit system rather than open CI logs.

Plan creation itself can require provider credentials. Run untrusted pull request code in a context that does not expose production credentials; a contributor can change configuration or CI scripts. Use a trusted workflow, protected environment, read-only identity where possible, and explicit approval before any apply privilege is granted. Policy evaluation can be isolated from apply credentials because it needs the plan document and policy bundle, not permission to modify a cloud account.

Policy gates are not cost gates, drift checks, or runtime controls. A policy can require a resource tag or encryption setting in the proposed plan. Cost estimation evaluates price implications, while drift detection compares state with live infrastructure. Runtime monitoring checks actual behavior and exposure. Connect each check to a clear owner so teams do not mistake one successful gate for comprehensive assurance.

A practical pull request exercise should cover a compliant plan, an insecure plan, a plan with an unknown security attribute, an invalid JSON artifact, and an engine failure. Verify each result has an actionable message and correct exit status. Then test an approved exception and confirm its scope is narrow. Review logs and artifacts as a security user: can a public contributor access secret values or production credentials? This tests both policy correctness and workflow safety.

Measure the policy gate's noise. If it frequently flags irrelevant resources, improve the rule, module interface, or documented exception rather than asking teams to ignore the check. Track policy coverage across repositories and keep a central owner for shared rules. Introduce breaking policy changes with a rollout plan: report-only first, remediation window, then required enforcement. Emergency rules can block immediately when exposure warrants it, but communicate the reason and affected resources.

In an interview, explain the full mechanism: the plan is converted to JSON, Rego evaluates planned changes, Conftest runs in CI, and a deny result blocks merge. Cover unknown values, artifact sensitivity, and how the job fails closed. Those details distinguish a policy design from merely invoking a scanner.

Use Static Security Scanning for fast source checks before a plan exists, and Pull Request Workflow Gates for branch protection and approval mechanics. A strong pipeline assigns each check a specific input and guarantee, then makes its result visible to reviewers.

Quiz

1. Why evaluate Terraform plan JSON instead of only scanning HCL?

Question 1 options

2. How should a policy handle a required attribute that is unknown until apply?

Question 2 options

3. What should CI do when Conftest cannot parse the plan JSON?

Question 3 options

4. Why restrict access to plan JSON artifacts?

Question 4 options

Flashcards

Question

What does OPA evaluate in a Terraform policy workflow?

Answer

Structured plan data, commonly JSON produced from a saved plan, against Rego rules.

Question

How should a required unknown plan value be handled?

Answer

Do not silently treat it as compliant; fail closed or require an explicit review path.

Question

What should happen when plan JSON is invalid?

Answer

The policy job should fail clearly rather than report a pass.

Question

Why protect Terraform plan JSON?

Answer

It can contain sensitive values even when terminal output redacts them.

Question

What proves a policy gate actually blocks?

Answer

A negative fixture with a known violation that returns a failing CI status and actionable message.

Revision Notes

Key Takeaways

  • 1. Plan JSON gives OPA proposed actions and resolved values for policy evaluation.
  • 2. Rego rules should inspect relevant resource types, attributes, and action sets while handling unknowns deliberately.
  • 3. Invalid inputs and policy runtime errors must fail closed rather than appear compliant.
  • 4. Plan artifacts can contain sensitive data and need access control and short retention.
  • 5. Test policy rules with compliant, violating, unknown, and malformed inputs before making them required.

Interview Tips

  • • Describe the plan-to-JSON-to-Rego-to-Conftest path and how a deny result blocks merge.
  • • Explain how you treat unknown attributes and planned replacements or deletions.
  • • Mention plan artifact sensitivity and separation from apply credentials.
  • • Give a negative policy test that proves the CI check cannot pass silently.

Cheat Sheet

OPA and Conftest Policy Gates Cheat Sheet

Phase: Policy Enforcement, Security Scanning, and Secrets | Level: advanced

Key points:

  • terraform show -json converts a plan into a document OPA policies can query.
  • Rego policies inspect resource types and arguments to allow or deny proposed changes.
  • Conftest wraps OPA for a simple command-line test workflow over JSON input.
  • Policies belong in CI gates so violations block merges before credentials are ever used.

Tags: terraform, policy, opa

Continue Learning

Before You Start

  • No prerequisites listed.

Related Reading