Automation & Code
AnalystCode / policy lab

Policy as Code with OPA/Rego

Write Rego policies that evaluate cloud configuration against your control catalog.

60 min 250 XPSkills: Rego, Policy as code

Rego evaluates documents, not systems

Open Policy Agent does one thing: it takes an input document, evaluates rules against it, and returns a result. It never inspects your cloud account. Whatever you feed it is the entire universe the policy can see, which means the quality of your policy is capped by the quality of your input.

This is why policy-as-code is a data problem before it is a language problem. Terraform plan JSON, a Kubernetes admission review, a cloud config snapshot: each has a different shape, and a rule written against one shape returns nothing at all against another.

Diagram of Rego evaluation: two input JSON documents flowing into a deny rule, producing either an empty deny set that passes or a populated deny set that fails.
The rule is a function of the input. Change the input shape and the rule goes quiet.

deny sets, not booleans

The conventional shape is a partial set called `deny` (or `violation`), where each element is a message describing one failure. An empty set means the evaluation found nothing to complain about; a populated set means block and explain.

Write messages the engineer receiving them can act on: name the resource, name the rule, and say what would make it pass. A policy that returns "denied" and nothing else generates a ticket to you rather than a fix by them.

Every deny message should contain the resource identifier and the remediation. Anything less exports the work back to the compliance team.

Default allow is a decision you are already making

If your gate only fails when `deny` is non-empty, you are running default-allow: anything your rules do not cover ships. That is a reasonable posture for a young program and a dangerous one for a regulated workload, and either way it should be a written choice rather than an accident of how the pipeline was wired.

Default-deny inverts it: nothing passes unless an `allow` rule affirms it. It catches the unknown-unknowns and it will stop deployments on day one, so it is introduced per resource class, not across the estate at once.

The vacuous pass

The dominant failure of policy-as-code is not a wrong rule. It is a rule that never ran: the package path does not match what the caller queries, the input key is `request.object.metadata` and the rule reads `metadata`, the file was never bundled. In every case the deny set is empty and the pipeline reports success.

The defence is a negative test. For each rule, keep an input document that is unambiguously non-compliant and assert that the rule produces at least one violation. A rule with only passing tests is a rule you have never proven fires.

Zero violations is not a result. It is either compliance or silence, and only a known-bad input tells you which.

Comparison of a rule that never evaluated due to a wrong package path and input key mismatch against a rule that evaluated with no violation, both producing zero violations.
Two very different situations, one identical green result.

Policies belong in the same lifecycle as controls

A Rego file is the executable expression of a catalog control, so it carries the same metadata: which control id it enforces, which framework references that control satisfies, who owns it. Store the linkage in the policy's own annotations and you can answer "which controls are enforced in code" as a query rather than an opinion.

And version them together. A control whose definition changed in March and whose policy changed in September has six months where the code enforced something the catalog no longer says.

Tasks

Answers are graded on the server: the answer key never reaches your browser.

Task 1: The rule that never fired

Scenario sim+85 XP

Name the failure the green result represents, then the mismatch that produced it.

Incident brief

A required-labels policy has gated the cluster for four months. The dashboard shows zero violations for the entire period, and the platform lead cites it as evidence that admission control is operating effectively.

An engineer deploys a workload with no team label at all. It admits successfully. The policy file is present in the bundle, its syntax is valid, and its unit tests, three of them, all asserting compliant inputs produce no violation, pass.

The rule reads `input.metadata.labels`. Admission review payloads nest the object under `input.request.object.metadata`.

Term bank, not all of these are right

vacuous passinput pathdefault denypackage namenegative testpartial evaluation

1.1Name the failure the four-month green streak represents. (two words)

1.2Name the mismatch that caused it. (two words)

Task 2: Whatever we did not think of, shipped

Scenario sim+85 XP

Name the posture the gate is running, then the posture the finding demands.

Incident brief

The gate blocks on public S3 buckets, unencrypted volumes and overly broad IAM policies: eleven rules, all well tested, all firing correctly on known-bad inputs.

An incident review finds that a new team shipped a managed message queue with public network access, in a service the eleven rules say nothing about. The policy repository behaved exactly as written.

The platform lead's summary: "the policy engine failed to catch it." The engine returned precisely what the rules described: an empty deny set.

Term bank, not all of these are right

default allowdefault denyadvisory modevacuous passexceptioncoverage gap

2.1Name the posture the gate is running today. (two words)

2.2Name the posture that would have blocked the queue. (two words)

Task 3: Enforced in code, allegedly

Scenario sim+80 XP

Name the artifact a rule must be tested against, then what the policy must carry to link it to the catalog.

Incident brief

The catalog says 22 controls are enforced in code. Asked which Rego rule enforces CC6.6, the team greps the policy repo for "CC6.6" and finds nothing: the rules are named after resources, not controls.

Worse: the encryption control was rewritten in March to require customer-managed keys. The Rego rule still asserts only that encryption is enabled. Both changes were reviewed; neither reviewer saw the other artifact.

Every rule in the repo has tests. All of them supply compliant inputs.

Term bank, not all of these are right

input documentcontrol iddeny messageframework refpackage pathowner

3.1Name the artifact a Rego rule is tested against. (two words)

3.2Name what each policy must carry to link it back to the catalog. (two words)

Labs

85 bonus XP

Hands-on work. You edit the artefact, the server runs the checks: the expectations and the reference solution never reach your browser.

Lab 1 · Policy lab

Deny the buckets that are actually public

Briefing 45 XP

A platform engineer swears the storage policy is enforced: the rule denies any bucket with `acl == "public-read"`. Last week a bucket went public anyway: it had a private ACL and a bucket policy granting `*` read access.

Write deny rules over `input` that catch both shapes: the public ACL, and a private-ACL bucket whose policy principal is a wildcard. Encryption-off buckets should not be denied by this rule: that is a different control, and denying it here would hide the finding you were asked for.

Inputs the rule is evaluated against

# should be DENIED
{ "name": "logs",    "acl": "public-read", "policy_principal": "arn:aws:iam::1:root", "encrypted": true }
{ "name": "exports", "acl": "private",     "policy_principal": "*",                   "encrypted": true }

# should be ALLOWED
{ "name": "backups", "acl": "private",     "policy_principal": "arn:aws:iam::1:root", "encrypted": true }
{ "name": "scratch", "acl": "private",     "policy_principal": "arn:aws:iam::1:root", "encrypted": false }

Checks

  • Rule parses and declares at least one deny block
  • Denies the public-ACL bucket
  • Denies the wildcard-principal bucket
  • Allows the properly locked-down bucket
  • Does not deny on encryption: wrong control
Lab 2 · Triage lab

Triage the policy decision log

Briefing 40 XP

The OPA decision log for the last hour holds eight entries. Production is being deployed to continuously and the policy has just been widened.

Raise the decisions that mean the policy failed to protect production. Leave the ones that are working as intended.

List only the lines that qualify, worst first. Severity is one of: critical | high | medium | low. Add or delete rows as needed.

Decision log extract

D-1  deny bypassed: request carried break_glass=true; no incident id, no expiry, allowed.
D-2  allow: request matched an exemption rule whose expiry field is set to the year 2099.
D-3  deny: public storage bucket blocked, developer notified.
D-4  allow: policy evaluated against an empty input document; default allow returned.
D-5  deny: untagged resource blocked.
D-6  allow: rule uses "not deny" with a typo'd rule name, so the guard never fires.
D-7  allow: compliant deployment, all rules evaluated, decision logged with policy hash.
D-8  deny: two rules disagreed; conflict resolved to deny and logged for review.

Checks

  • The right findings are raised
  • Benign lines are left out
  • Severity matches the actual exposure
  • Ranked worst first

Next on your path · Automation & Code

Control Checks in CI/CD

Fail a pipeline on a control violation, and design the exception path that keeps engineers moving.

Analyst 50 min 210 XP
Start next room

Community

Discussion & writeups

Sign in to post

Share how you approached the room or ask where you got stuck. Never paste task answers or full lab solutions: write the reasoning, not the key.

Loading discussion…