Skip to content

How to convert Sentinel policies to OPA and Rego

Leaving Terraform Cloud usually means leaving Sentinel. Most Sentinel policies are short and translate cleanly to Rego once you know how the concepts map.

How Sentinel concepts map to Rego

SentinelRego on StackShip
import "tfplan/v2"input.resource_changes
filter and allsome x in and rule bodies, which are already "for each"
main = rule { ... } returns true or falsedecision returns a list of findings; empty means pass
print() to explain a failureEach finding's message and resource_address
Advisory, soft-mandatory, hard-mandatoryThe same three enforcement modes, set when you attach the policy
sentinel testopa test

The biggest shift is the result. A Sentinel rule says pass or fail. A Rego policy returns findings, one per problem, so the reviewer sees exactly which resources broke which rule.

A side-by-side rewrite

A common Sentinel policy limits EC2 instance types. In Sentinel:

import "tfplan/v2" as tfplan

allowed_types = ["t3.micro", "t3.small", "t3.medium"]

instances = filter tfplan.resource_changes as _, rc {
	rc.type is "aws_instance" and
	(rc.change.actions contains "create" or rc.change.actions contains "update")
}

main = rule {
	all instances as _, instance {
		instance.change.after.instance_type in allowed_types
	}
}

The same policy in Rego, as a StackShip policy set:

package stackship.v1

import rego.v1

decision := {"findings": findings}

allowed_types := {"t3.micro", "t3.small", "t3.medium"}

# Matches aws_instance resources at the root or inside modules.
instance_address := `^(module\.[^.]+(\[[^\]]*\])?\.)*aws_instance\.`

findings contains finding if {
	some change in input.resource_changes
	regex.match(instance_address, change.address)
	some action in change.change.actions
	action in {"create", "update"}
	not change.change.after.instance_type in allowed_types
	finding := {
		"id": sprintf("instance-type:%s", [change.address]),
		"title": "Instance type not allowed",
		"message": sprintf(
			"%s uses %s. Allowed: %s.",
			[change.address, change.change.after.instance_type, concat(", ", sort(allowed_types))],
		),
		"resource_address": change.address,
	}
}

Three differences to notice:

  • Rego has no all; you write the rule for the bad case and each match becomes a finding.
  • The finding names the resource and the value, so nobody has to dig through logs to see what failed.
  • StackShip's policy input identifies resources by address, so the type comes from a pattern on the address. The pattern skips data sources.

Test the rewrite

package stackship.v1_test

import rego.v1

import data.stackship.v1 as policy

instance(address, type) := {
	"address": address,
	"provider_name": "registry.opentofu.org/hashicorp/aws",
	"change": {"actions": ["create"], "before": null, "after": {"instance_type": type}},
}

test_large_instance_in_module_is_flagged if {
	result := policy.decision with input as {"resource_changes": [
		instance("module.app.aws_instance.web[0]", "m5.4xlarge"),
	]}
	count(result.findings) == 1
}

test_allowed_instance_passes if {
	result := policy.decision with input as {"resource_changes": [
		instance("aws_instance.web", "t3.small"),
	]}
	count(result.findings) == 0
}

Run opa check --strict . and opa test . locally. StackShip runs both again when you publish a version.

What doesn't translate directly

  • tfconfig and tfstate imports. StackShip policies see the plan's resource changes, not the raw configuration or full state. Rewrite configuration checks against the planned values instead. That is usually what you meant anyway.
  • tfrun and cost estimates. Policies get the run kind (tracked, speculative, or manual) and the commit, not workspace run metadata or cost data.
  • HTTP calls. Policies can't call out to the network, so every result is reproducible from the plan alone.

Roll it out safely

  1. Rewrite and test each policy.
  2. Attach it as advisory and watch a week of real plans.
  3. Compare findings with what Sentinel reported.
  4. Switch to the enforcement level the Sentinel policy had.

For more examples, read OPA policy for OpenTofu plans, or see the full policy input reference.

Get started

Run your first OpenTofu plan today.

Free for every organization. Sign up with email or Google.