Policy-as-code with Open Policy Agent (OPA) and Rego
Module 2 introduced OPA and Conftest as a general pipeline gate. This lesson goes deeper into applying that same engine specifically to infrastructure code, where policies must reason about entire Terraform plans rather than single files. Writing effective Rego for infrastructure means understanding the plan's JSON structure well enough to target exactly the resources and attributes that matter, so policies catch real risk without drowning teams in irrelevant noise.
Analogy🏏Cricket
💪 Think of it like fitness: a good coach doesn't judge your health from one photo of a single bicep — they read a full-body assessment covering posture, every joint, and movement across the whole frame, then target only the muscles that actually need work. Judging one file in isolation is the single-photo mistake; reasoning over the entire Terraform plan is the full-body scan. Writing effective Rego means knowing that assessment's layout well enough to zero in on exactly the resources and attributes that carry real risk. This reveals why understanding the whole plan's structure is what lets a policy flag genuine danger instead of noise.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
A Terraform plan, when exported as JSON, describes every resource change as structured data: what will be created, updated, or destroyed, and every attribute each resource will hold. Rego policies traverse this structure directly, so a rule can inspect, for example, every planned security group and flag any that opens port 22 to 0.0.0.0/0, or inspect every planned bucket and flag any missing server-side encryption, regardless of which module or file defined it.
Analogy🏏Cricket
♟️ Think of it like chess: a strong player doesn't read the board one square at a time — they take in the entire position as structured relationships and immediately scan every piece of a given type, asking which knights are forked or which files sit undefended. A Terraform plan exported as JSON gives Rego that same whole-board view: it can sweep every planned security group for a port open to 0.0.0.0/0, or every bucket for missing encryption, no matter which module placed the piece. This reveals why the structured plan lets a policy reason about all resources of a kind at once, not file by file.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
Under the hood, well-organised infrastructure policy separates concerns: one package for network rules, another for identity and access rules, another for encryption and data protection rules. Each package exposes deny messages that Conftest aggregates into a single pass or fail result. This modularity lets different teams own different policy domains, lets policies be tested independently with example inputs, and lets a new rule be added to one package without risking unintended effects on another.
Analogy🏏Cricket
🍳 Think of it like cooking: a professional kitchen runs separate stations — sauce, grill, pastry — each with its own chef, its own standards, and its own checks, and the pass simply aggregates their verdicts into one plate that either goes out or doesn't. A new pastry technique can be perfected without disturbing the grill. Organising Rego into focused packages for network, identity, and encryption rules works the same way: Conftest gathers each station's deny messages into a single pass-or-fail, while teams own and test their domain in isolation. This reveals why separating policy concerns lets rules evolve safely without stepping on each other.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
rego
packageterraform.networkdeny[msg]{resource:=input.resource_changes[_]resource.type=="aws_security_group_rule"resource.change.after.cidr_blocks[_]=="0.0.0.0/0"resource.change.after.from_port<=22resource.change.after.to_port>=22msg:=sprintf("SG rule '%s' opens SSH (22) to the internet",[resource.address])}# CI: terraform show -json plan.tfplan > plan.json# conftest test plan.json --policy ./policies
Analogy🏏Cricket
💰 Think of it like finance: an auditor doesn't judge a company from one receipt — they pull the full structured ledger, then run separate specialist checks over each account: cash controls, payroll, tax, each against its own standard. The structured books make it possible to sweep every transaction of a type at once, and splitting the review into focused areas means the payroll specialist never trips over the tax rules. Writing Rego against a Terraform plan's JSON, organised into distinct policy packages, mirrors this exactly. This reveals why structured data plus separated, focused checks is how you audit an entire system without missing a line.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
Best practice tests policies with representative sample plans before trusting them in production, so a typo in a Rego rule cannot silently let every violation through unnoticed. Keep deny messages specific enough that a developer can fix the issue without needing to read the policy source, version policy bundles alongside application code, and separate warn-level advisory rules from hard-blocking ones so early-stage policies can be observed before they start failing builds.
Analogy🏏Cricket
💼 Think of it like business: before a new expense-approval rule goes company-wide, a sensible manager pilots it against last quarter's real invoices to confirm it actually catches overspending and doesn't wave everything through because of a typo in the wording. The rejection notice names the exact policy breached so staff can fix a claim without calling finance, and advisory guidance is trialled softly before it becomes a hard block. Testing Rego against representative sample plans, with specific deny messages and versioned bundles, follows the same discipline. This reveals why you validate a policy on known cases before it silently guards production.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
In the real world, a security team might roll out a new Rego rule requiring encryption on every planned storage resource across dozens of repositories in a single commit to a shared policy bundle. Instead of emailing every team and hoping they comply, the very next Terraform pull request in any repository that omits encryption fails automatically with a message naming the exact resource, achieving organisation-wide enforcement without a single manual follow-up.
Analogy🏏Cricket
⚽ Think of it like sports: when a league changes a rule — say, tightening what counts as offside — it doesn't email every referee and hope they each remember. It updates the single official rulebook every match already runs against, and the very next game enforces the new line automatically, flagging the exact player who strayed. Committing a new encryption requirement to one shared Rego bundle behaves identically: the next Terraform pull request in any repository that omits encryption fails on its own, naming the offending resource. This reveals how one change to a shared rulebook delivers organisation-wide enforcement with no manual chasing.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
Rego policies can traverse an entire Terraform plan's JSON, not just single files.
Rules can target specific resource types and attributes regardless of which module defined them.
Organise policies into focused packages: network, identity, encryption, and so on.
Test policies against sample plans before trusting them in a blocking gate.
A shared policy bundle enforces new rules organisation-wide from a single commit.