Free — no signup required

Learning Rego Syntax

3 min read

The "Deny" Pattern

The most common pattern in Terraform policy enforcement is a set of deny rules. If any deny rule evaluates to true for a given input, the policy fails. If no deny rules match, the policy passes.

Example 1: Simple Condition — No Friday Deploys

Input data sent to OPA: {"day_of_week": "Friday"}

package main

# The 'deny' set collects all violation messages.
# If it's empty, the policy passes.
deny[msg] {
    input.day_of_week == "Friday"
    msg := "Deployments are not allowed on Fridays."
}

If input.day_of_week equals "Friday", the rule body is true, and the message is added to the deny set. The CI pipeline checks: is deny empty? If not, it fails.

Example 2: Iterating Over Resources — Require Tags

Checking a Terraform plan is more involved because you must iterate over all resources in the plan. The [_] syntax is Rego's way of saying "for each element in this array."

package terraform.tags

deny[msg] {
    # Step 1: Iterate over every resource change in the plan
    resource := input.resource_changes[_]

    # Step 2: Filter — only apply this rule to S3 buckets
    resource.type == "aws_s3_bucket"

    # Step 3: Check — does the 'after' state lack an 'Env' tag?
    not resource.change.after.tags.Env

    # Step 4: Build a descriptive error message
    msg := sprintf("S3 bucket '%v' is missing the required 'Env' tag.", [resource.name])
}

Read this rule as: "For every S3 bucket that will exist after this plan is applied, if it does not have an Env tag, add a violation message to the deny set."

Example 3: Allowlisting — Restrict Instance Types

package terraform.compute

# Define the set of allowed instance types
allowed_instance_types := {"t3.micro", "t3.small", "t3.medium"}

deny[msg] {
    resource := input.resource_changes[_]
    resource.type == "aws_instance"

    # Get the instance type from the planned state
    instance_type := resource.change.after.instance_type

    # Check if it's NOT in the allowed set
    not allowed_instance_types[instance_type]

    msg := sprintf(
        "EC2 instance '%v' uses disallowed instance type '%v'. Allowed types: %v",
        [resource.name, instance_type, allowed_instance_types]
    )
}

This pattern — defining an explicit allowlist and checking membership — is far more maintainable than a blocklist. When a new instance type is approved, you add it to the set. You don't need to update a long list of != conditions.

Interview Tip

A common interview question is: "How would you enforce that production resources always have a specific tag, but allow dev resources to skip it?" The answer demonstrates understanding of Rego's conditional logic. You'd add a condition to the rule body: resource.change.after.tags.Environment == "production" — so the rule only fires for production resources. This shows you understand that Rego rules are conjunctions (AND logic): every line in the rule body must be true for the rule to trigger.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free