05 November 2026

What Is Rego? How the OPA Policy Language Works

Reza Khosravi

Table of Contents

What Is Rego? How the OPA Policy Language Works

Rego is the declarative policy language used by Open Policy Agent (OPA) to decide whether a request, configuration, or change is allowed.

You pasted a policy into a pull request, the job stayed green, and a privileged container still shipped. The file looked like code, but nobody could say which condition fired. If you searched what is rego because a teammate dropped the word in Slack, you want that mental model before you touch syntax. One small rule has to hold in your editor, in CI, and in the running service. Same conditions, same payload shape, same yes or no.

Rego Defined and the Terms Behind It

Rego is the declarative policy language used by Open Policy Agent (OPA), an open source project in the Cloud Native Computing Foundation, to decide whether something is allowed. You state conditions rather than a sequence of steps. The engine returns a decision such as true, false, or a structured list of violations.

Picture a referee with a printed rulebook. The rulebook is the policy. The play on the field is the request, plan, or manifest under review. The call is the decision. You don't coach the referee's footwork. You say which facts must be true before the flag goes up.

The map below ties those pieces together before the vocabulary gets specific.

A comprehensive infographic explaining Rego policy language, including its definition, core concepts, use cases, and key terms.

Terms you will see in every file

TermWhat it holdsExample
inputThe data being checked, such as an API request or a Terraform planinput.method
dataReference data loaded alongside the policy, such as approved teams or allowed regionsdata.approved_teams
packageThe namespace that groups the rules in one filepackage httpapi.authz
ruleA named condition that produces a value or a set of resultsallow if { ... }
queryThe question the engine answers, usually a path to a ruledata.httpapi.authz.allow
decisionThe result returned to the caller, either a Boolean or a structured objectallow: false

Leave that table visible while you read the examples. The names stay fixed even when the payload changes.

If you meant the vehicle word

Searches for what is rego also hit paperwork for cars. In New Zealand, rego usually means the recurring vehicle licence, while vehicle registration is the separate one-time process that adds a vehicle to the Motor Vehicle Register, as the Waka Kotahi vehicle licence guide explains. In Australia the word generally means registration, and a payment can bundle a registration fee, a traffic-improvement fee, and compulsory third-party insurance, as Queensland does. If that's your question, use a state or territory transport authority guide instead.

How Rego Decides with Rules

A bouncer doesn't follow a choreography. They check each guest against conditions: on the list, old enough, not already inside. Rego works the same way. A rule states what must be true. Line order isn't an execution sequence, so moving a block doesn't change the outcome the way it would in Python or Go.

The Open Policy Agent documentation describes Rego as OPA's high-level, declarative policy language for decisions over structured, hierarchical data such as API requests, infrastructure-as-code documents, Kubernetes objects, and configuration records. Authors define rules as logical conditions that generate virtual documents. The same docs call rules if-then logic statements, and you can test policies apart from application code, which gives you deterministic regression tests for security requirements.

A verdict, then every reason

Complete rules produce a single value, such as an allow or deny decision. Partial rules construct sets or key-value objects from matching input. A complete rule is the authorization outcome. A partial rule can list every violating resource, required control, or permitted network identity for whatever enforces the result.

That's why deny contains msg earns its keep. One evaluation returns every violation, so a developer fixes the whole set in one pass instead of discovering the next failure only after the first is gone. Because rules query a shared input document and policy data, one definition can serve CI checks, admission control, service authorization, and configuration validation. A practical shape is to normalize pull-request metadata, repository facts, and changed resources into that input, then return both a decision and explainable violations.

Practical rule: Pair one complete rule for the verdict with partial rules that explain it. A bare true or false leaves the next person guessing what to edit.

Four shapes

Current files use v1 syntax. Start with import rego.v1, put if before each body, and use contains when a rule builds a set.

Rule typeProducesSyntax patternTypical use
Complete ruleOne value, such as true, false, or a stringallow if { ... }Single allow or deny verdict
Partial set ruleA set of matching itemsdeny contains msg if { ... }Collecting every violation
Partial object ruleKey-value pairsports[name] := port if { ... }Mapping resources to settings
FunctionA reusable calculation that takes argumentsis_admin(user) if { ... }Shared logic called from several rules

A function such as is_admin(user) holds a calculation you'd otherwise paste into several rules. Call it from the allow rule and from the message rule, and both stay aligned when the definition of admin changes. Most real policies stop at that pairing: one complete rule answers the question, and partial rules carry the reasons.

Anatomy of a Small Policy and Your First Test Run

The smallest useful policy is an HTTP allow list with a fail-closed default. Read it as six ideas, not as a program that runs top to bottom.

The sketch below is the shape of that file: a package line, a v1 import, and rules that follow.

A hand-drawn illustration explaining Open Policy Agent Rego code structure with package declaration and modern syntax imports.

What each line does

package httpapi.authz names the namespace. Callers ask data.httpapi.authz.allow and land on this file.

import rego.v1 turns on the modern keywords, including if and contains.

default allow := false is the fail-closed line. An unmatched request is denied. It isn't left undefined for each caller to interpret differently.

Two allow if blocks follow. Conditions inside one block combine with AND. Separate blocks for the same rule combine with OR.

The first block allows a GET whose path is /public. Both facts must hold.

The second block allows any method when input.user.role is admin. A viewer hitting /orders matches neither block, so the default stands.

An undefined allow is not a polite no. Callers treat it as an error in one service and as a pass in another. The default removes that argument.

Predict before you run

Write the answers down first.

  • Allowed case: method GET, path /public, role viewer. Expect true, because the first block matches.
  • Denied case: method POST, path /orders, role viewer. Expect false, because neither block matches and the default is false.

If the second case comes back true, you have an extra allow block or a missing default. Don't debug the CLI until the prediction is written.

First run on your machine

  1. Install the OPA CLI and print the version. V1 keywords need a current release. An old binary will reject if and send you hunting for a syntax bug that isn't in the file.
  2. Save the policy as authz.rego with the package, the import, the default, and the two allow if blocks. One complete rule, two bodies.
  3. Create two JSON files, allow.json and deny.json, using the cases you already predicted. Keep field names identical to the policy (method, path, user.role). A typo here looks like a logic bug.
  4. Run opa eval with the policy on -d, the input on -i, and the query data.httpapi.authz.allow. Compare the printed value with your prediction before you change a single line.
  5. Add authz_test.rego. Test names start with test_. One test asserts the public GET, one asserts the viewer on /orders. Run opa test -v so a failure names the broken rule instead of a stack you have to reconstruct.
  6. Commit the policy and the tests together. A later pipeline can treat a failing test, or a deny message, as a failed job. Without the tests in git, that gate has nothing stable to run.

A mistake that looks like a pass

Beginners often write input.user.role == "admin" and then send JSON with input.role. The missing field makes the expression undefined. The rule doesn't fire, and with a default of false you deny everyone, including admins. With no default, allow stays undefined and the bug hides behind your HTTP wrapper. Print the input you passed to opa eval before you rewrite the rule.

Policy Examples for Common Use Cases

The language doesn't change when the thing being judged changes. The input document does. Four places show that cleanly, and the policy as code examples collection is a useful next read once these shapes feel familiar.

Pods that must not be privileged

Input is an admission request. The rule walks input.request.object.spec.containers. Any container whose security context sets privileged to true is collected into a deny set, and the message names that container so the author knows which block to edit. OPA Gatekeeper wraps rules like this and rejects the object at admission, before the pod is scheduled. A Boolean alone would only say "no." The named message says which container.

Plans that must not go public

A CI step exports a Terraform plan as JSON. The rule denies an aws_s3_bucket (or the matching storage resource) when the plan marks it public or leaves encryption off. Nothing is applied. The developer sees the deny text on the pull request, fixes the plan, and pushes again. Conftest is the usual runner here because it already speaks Rego and fails a process when deny messages exist.

API authorization is the same idea with a thinner payload. A gateway or the service itself passes method, path, and the caller's role. The policy allows the call or denies it. There is no cluster and no plan file, only the request context you already log.

Pull request checks stretch the input again. The document lists changed services plus repository metadata, including whether an approved design decision is recorded for each service. The rule blocks the merge when a changed service has no such record. Platform teams use this when a design review is supposed to exist before code lands, and a green test suite isn't evidence that the review happened.

Use caseInput checkedOutcome enforcedWhere it runs
Kubernetes pod securityPod spec in an admission requestDeny privileged containersCluster admission, for example OPA Gatekeeper
Terraform plan reviewPlan exported as JSONDeny public or unencrypted storageCI pipeline step, for example conftest
API authorizationHTTP request with user role and resource pathAllow or deny the call by role and resourceGateway or service
Pull request checksChanged services and repository metadataBlock merge without an approved designCI on pull request

Skills carry across that table. You still write conditions over structured input, still default to deny, and still return messages a human can act on. What changes is the JSON you hand the engine, not the way you think about the decision.

Running Rego in CI/CD and Policy-as-Code Workflows

Rego doesn't have to live in a separate policy server. It can run on a laptop, in a pipeline, at admission, and inside a service binary. The picture is the path a single policy takes.

A diagram illustrating the workflow of running Rego policies from local development through CI/CD pipelines to Kubernetes admission controllers.

Embed the engine when calls are frequent

The OPA integration guide shows that the Go rego package can construct a policy object, load modules and data, prepare a query with PrepareForEval(), and evaluate that prepared query on each request. Preparation pulls compilation and optimization off the request path, which suits high-frequency authorization and pull-request checks. The API returns a result set of bindings and expression values. An empty result set means the query wasn't satisfied, so your code must treat that case on purpose instead of assuming a Boolean arrived.

Rego can also compile to WebAssembly and run outside the Go process. Built-in support varies, and some built-ins need a host SDK rather than running inside Wasm. Inventory the built-ins each policy calls before you pick that runtime. Pre-compile stable queries. Return structured fields such as allowed, violations, and control_id rather than a Boolean alone, so a pipeline can block a merge, an auditor can trace a decision, and a control can map back to a design decision. The same decision engine then sits in the IDE, in CI, and in the service.

A pipeline that fails on deny text

Policy-as-Code means the rules live in Git, get reviewed like application code, and are tested before anyone relies on them. A concrete job checks out the repo, runs opa test, exports the Terraform plan to JSON, and runs conftest against that JSON. Any deny message fails the job. Structured output lets the log print the violation and the control id, which is what a reviewer needs at 4 p.m. For a wider checklist of gates around that job, see this CI/CD pipeline security walkthrough. Rite NRG's security as code guide is a solid companion if you want the organizational side of storing controls next to the code they judge.

Integration pointInputTypical decisionCommon tools
Local developer checkSource files or sample JSONWarn before commitopa eval, editor plugins
CI pull request gateParsed config, plan, or pull request metadataFail the build or block mergeconftest, OPA CLI
Kubernetes admissionAdmission review requestReject non-compliant objectOPA Gatekeeper
Service authorizationHTTP or gRPC request contextAllow or deny the callEmbedded OPA SDK or sidecar

The sequence a team can operate is short. Commit the policy with its tests. Test it locally and again in CI. Evaluate it against the plan or the request. Merge when the result set is clean, and block when it isn't. Skipping the prepare step, or returning only true and false, is how a fast check becomes a check nobody can explain.

Misconceptions and Limits Before You Adopt Rego

Most failed rollouts aren't syntax problems. They're assumptions about what the language is for.

  • Rego is a general-purpose language. It isn't. It's built to decide over structured data. Don't use it for storage, user interfaces, or business workflows. Those belong in the service. The policy should stay a decision.
  • A missing default is harmless. allow with no default stays undefined when nothing matches. One caller treats that as a failure, another as a pass. default allow := false makes every unmatched request a deny.
  • False and undefined are the same. They aren't. A comparison against a field that isn't in the input is undefined, and the rule doesn't fire. That's the usual reason a policy seems to pass everything, or to deny everyone, with no obvious branch taken.
  • Speed comes for free. It doesn't. Prepare queries once, load reference data once, and keep inputs to the fields the rules read. Recompiling a bundle on every HTTP call will dominate the latency you hoped to save.
  • A Boolean is enough for developers. It isn't. Return violations that name the resource and the broken condition. A red build with no message just creates a ticket titled "OPA said no."
  • The policy is the only thing to test. Rules are only as reliable as input normalization. Test the data shape as well as the conditions, including a payload that's missing a nested field. The policy as code tools overview is a practical place to compare runners once you know which mistake you're trying to catch.

Rules that nobody tests become folklore within a quarter. Write one test for an allowed input and one for a denied input before you ask another team to depend on the file. That pair is the cheapest review you'll run, and it's the one that catches an undefined field before production does.


DevArmor keeps threat models, design decisions, and Policy-as-Code checks in the tools your engineers already use, so a Rego rule on a pull request can trace back to an approved design instead of a one-off script. Teams in regulated, fast-moving domains use it to block unsafe merges without adding another review meeting. Visit DevArmor to see how that enforcement sits on your existing pull requests.

Table of Contents

Subscribe