PolarSPARC

Primer on Open Policy Agent (OPA)


Bhaskar S 07/05/2026


Overview

Open Policy Agent (or OPA for short) is a general-purpose policy decision engine that unifies enterprise policy enforcements across the stack. OPA allows one to implement policies in a high-level declarative language that lets one specify policies for a wide range of use cases, which can be enforced in application proxies, API gateways, Kubernetes, CI/CD pipelines, and more.

In short, OPA is domain agnostic so that one can describe almost any kind of invariant in the OPA policies, such as the following use-cases:

OPA policies are expressed in a high-level declarative language called Rego. Rego is purpose built for expressing policies over complex hierarchical data structures.

When a software component needs to enforce policy decisions, it makes a request to the OPA policy server, supplying input as a structured data (JSON). Upon accepting an OPA request, the OPA policy server evaluates the policy file(s) and responds with a decision.

In this hands-on primer, we will introduce and demonstrate how one can implement enterprise policies using OPA Rego.


Installation and Setup

The installation will be on a Ubuntu 24.04 LTS based Linux desktop.

Ensure Docker is installed on the system. Else, follow the instructions provided in the article Introduction to Docker to complete the installation.

Check for the latest stable version of OPA docker image. Version v1.18.2 was the latest at the time of this article.

To download the latest docker image for OPA, execute the following command:


$ docker pull openpolicyagent/opa:1.18.2


The following would be a typical output:


Output.1

1.18.2: Pulling from openpolicyagent/opa
5c7f633cfc46: Pull complete 
998036137dfb: Pull complete 
9201e51fe8c1: Pull complete 
be42b23d0328: Pull complete 
bc7dfa40f5aa: Pull complete 
d6687ecb78a1: Pull complete 
Digest: sha256:cba27d3c6af2feba1e4d6e6b5e24df5b53db332420d4148a90acccd12efae6ed
Status: Downloaded newer image for openpolicyagent/opa:1.18.2
docker.io/openpolicyagent/opa:1.18.2

We will store the OPA policy files in the directory $HOME/.opa on the host.

To setup the required directories, execute the following command in a terminal window:


$ mkdir -p $HOME/.opa{/data,/policies}


This completes all the necessary system installation and setup for the OPA hands-on demonstration.


Hands-on with OPA


Before we get started, we need to determine all the available OPA command(s). To determine the supported command(s), execute the following command:


$ docker run --rm --name opa openpolicyagent/opa:1.18.2 --help


The following should be the typical output:


Output.2

An open source project to policy-enable your service.

Usage:
  opa [command]

Available Commands:
  bench        Benchmark a Rego query
  build        Build an OPA bundle
  capabilities Print the capabilities of OPA
  check        Check Rego source files
  completion   Generate the autocompletion script for the specified shell
  deps         Analyze Rego query dependencies
  eval         Evaluate a Rego query
  exec         Execute against input files
  fmt          Format Rego source files
  help         Help about any command
  inspect      Inspect OPA bundle(s)
  parse        Parse Rego source file
  run          Start OPA in interactive or server mode
  sign         Generate an OPA bundle signature
  test         Execute Rego test cases
  version      Print the version of OPA

Flags:
  -h, --help   help for opa

Use "opa [command] --help" for more information about a command.

Let us start with a very simple OPA Rego policy file.

Create a file called $HOME/.opa/policies/simple.rego with the following contents:


#
# Name:   simple.rego
# Author: Bhaskar S
# Date:   07/05/2026
#

package simple

import rego.v1

# Default is deny
default allow := false

# Dev environment
env := "dev"

# Only allow alice
allow := true if input.name == "alice"

The OPA rego policy defaults to a DENY, .i.e., allow := false.

The variables in rego (allow and env) can be assigned values using the := operator.

All rules in rego have the following format:

HEAD if {
    BODY
}

The HEAD consists of variable and a value.

The BODY consists of one or more conditions which are all MUST be true (conditions are AND'd) for the HEAD to be true. In other words, a rule is true only when all the conditions in the body are satistied.

Multiple rules with the same HEAD are OR'd.

Variables within a body have local scope.

OPA accepts JSON structured data as input. Upon accepting an input request (as JSON), OPA binds data provided in the query to a global variable called input. One can refer to specific parts of the input data using the . (dot) notation.

When referring to a value that does not exist, OPA returns undefined.

Now, it is time to test the simple.rego policy !

To make sure that the simple.rego policy file is valid, execute the following command:


$ docker run --rm --name opa -v $HOME/.opa/policies:/policies openpolicyagent/opa:1.18.2 inspect /policies/simple.rego


The following would be the typical output if the rego policy file is valid:


Output.3

NAMESPACES:
+-------------------------------------+
|  NAMESPACE  |         FILE          |
+-------------------------------------+
| data.simple | /policies/simple.rego |
+-------------------------------------+

To start the OPA policy decisioning server, execute the following command:


$ docker run --rm --name opa --network host -v $HOME/.opa/data:/data -v $HOME/.opa/policies:/policies openpolicyagent/opa:1.18.2 run --addr 192.168.1.25:8181 --server /data /policies


The following would be a typical output:


Output.4

{"addrs":["192.168.1.25:8181"],"diagnostic-addrs":[],"fields.time":"2026-07-05T16:41:54.159773451Z","level":"info","msg":"Initializing server.","time":"2026-07-05T16:41:54Z"}

Note that the package in the simple.rego policy is called simple. The API endpoint for this policy package therefore can be accessed at /v1/data/simple.

To test the simple.rego policy via the API endpoint /v1/data/simple, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/simple -H "Content-Type: application/json" -d '{"input": {"name": "alice"}}' | jq


The following would be the typical output:


Output.5

{
  "result": {
    "allow": true,
    "env": "dev"
  }
}

To test the simple.rego policy for another case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/simple -H "Content-Type: application/json" -d '{"input": {"name": "bob"}}' | jq


The following would be the typical output:


Output.6

{
  "result": {
    "allow": false,
    "env": "dev"
  }
}

Stop the OPA policy decisioning server.

Create the next policy file called $HOME/.opa/policies/permissions.rego with the following contents:


#
# Name:   permissions.rego
# Author: Bhaskar S
# Date:   07/05/2026
#

package permissions

import rego.v1

# Default is deny
default allow := false

# Only allow alice
allow if input.name == "alice"

# Default permissions
perms contains "read"

# Only allow alice with execute permissions
perms contains "execute" if input.name == "alice"

# Only allow write permissions to alice with a role of admin
perms contains "write" if {
    input.name == "alice"
    input.role == "admin"
}

In this example, we are returning a collection of values from the OPA decisioning server rather than atomic values.

To make sure that the permissions.rego policy file is valid, execute the following command:


$ docker run --rm --name opa -v $HOME/.opa/policies:/policies openpolicyagent/opa:1.18.2 inspect /policies/permissions.rego


The following would be the typical output if the rego policy file is valid:


Output.7

NAMESPACES:
+-----------------------------------------------+
|    NAMESPACE     |            FILE            |
+-----------------------------------------------+
| data.permissions | /policies/permissions.rego |
+-----------------------------------------------+

Restart the OPA policy decisioning server by executing the following command:


$ docker run --rm --name opa --network host -v $HOME/.opa/data:/data -v $HOME/.opa/policies:/policies openpolicyagent/opa:1.18.2 run --addr 192.168.1.25:8181 --server /data /policies


Note that the package in the permissions.rego policy is called permissions. The API endpoint for this policy package therefore can be accessed at /v1/data/permissions.

To test the permissions.rego policy via the API endpoint /v1/data/permissions , execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/permissions -H "Content-Type: application/json" -d '{"input": {"name": "alice"}}' | jq


The following would be the typical output:


Output.8

{
  "result": {
    "allow": true,
    "perms": [
      "execute",
      "read"
    ]
  }
}

To test the next case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/permissions -H "Content-Type: application/json" -d '{"input": {"name": "bob"}}' | jq


The following would be the typical output:


Output.9

{
  "result": {
    "allow": false,
    "perms": [
      "read"
    ]
  }
}

To test another case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/permissions -H "Content-Type: application/json" -d '{"input": {"name": "alice", "role": "admin"}}' | jq


The following would be the typical output:


Output.10

{
  "result": {
    "allow": true,
    "perms": [
      "execute",
      "read",
      "write"
    ]
  }
}

To test the final case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/permissions -H "Content-Type: application/json" -d '{"input": {"name": "bob", "role": "admin"}}' | jq


The following would be the typical output:


Output.11

{
  "result": {
    "allow": false,
    "perms": [
      "read"
    ]
  }
}

Stop the OPA policy decisioning server to move on to the next example.

Often times the OPA policy decisioning server needs to lookup external data from the policy file. The external static data is usually stored in a data JSON file. The OPA policy decisioning server loads this file into memory and binds its contents under the global variable referred to as data.

Create the JSON data file called $HOME/.opa/data/data.json with the following contents:


{
  "limits_by_roles": {
    "employee": 500.00,
    "manager": 1000.00,
    "owner": 5000.00
  },
  
  "user_roles": {
    "alice": {"role": "owner", "active": true},
    "bob": {"role": "manager", "active": true},
    "charlie": {"role": "employee", "active": false},
    "dave": {"role": "employee", "active": true}
  }
}

Now, create the policy file called $HOME/.opa/policies/limits.rego with the following contents:


#
# Name:   limits.rego
# Author: Bhaskar S
# Date:   07/05/2026
#

package limits

import rego.v1

# Default is deny
default allow := false

# Default limit
default limit := 0.00

allow if data.user_roles[input.name].active == true

# Identify role
role := role if role := data.user_roles[input.name].role

# Fetch user limits
limit := data.limits_by_roles[role] if allow

To make sure that the limits.rego policy file is valid, execute the following command:


$ docker run --rm --name opa -v $HOME/.opa/data:/data -v $HOME/.opa/policies:/policies openpolicyagent/opa:1.18.2 inspect /policies/limits.rego


The following would be the typical output if the rego policy file is valid:


Output.12

NAMESPACES:
+-------------------------------------+
|  NAMESPACE  |          FILE         |
+-------------------------------------+
| data.limits | /policies/limits.rego |
+-------------------------------------+

Restart the OPA policy decisioning server by executing the following command:


$ docker run --rm --name opa --network host -v $HOME/.opa/data:/data -v $HOME/.opa/policies:/policies openpolicyagent/opa:1.18.2 run --addr 192.168.1.25:8181 --server /data /policies


Note that the package in the limits.rego policy is called limits. The API endpoint for this policy package therefore can be accessed at /v1/data/limits.

To test the limits.rego policy via the API endpoint /v1/data/limits, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/limits -H "Content-Type: application/json" -d '{"input": {"name": "alice"}}' | jq


The following would be the typical output:


Output.13

{
  "result": {
    "allow": true,
    "limit": 5000.00,
    "role": "owner"
  }
}

To test the next case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/limits -H "Content-Type: application/json" -d '{"input": {"name": "bob"}}' | jq


The following would be the typical output:


Output.14

{
  "result": {
    "allow": true,
    "limit": 1000.00,
    "role": "manager"
  }
}

To test another case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/limits -H "Content-Type: application/json" -d '{"input": {"name": "charlie"}}' | jq


The following would be the typical output:


Output.15

{
  "result": {
    "allow": false,
    "limit": 0.00,
    "role": "employee"
  }
}

To test the final case, execute the following command in a terminal window:


$ curl -s -X POST http://192.168.1.25:8181/v1/data/limits -H "Content-Type: application/json" -d '{"input": {"name": "dave"}}' | jq


The following would be the typical output:


Output.16

{
  "result": {
    "allow": true,
    "limit": 500.00,
    "role": "employee"
  }
}

With this, we conclude our hands-on demonstration of using OPA to enforce enterprise policies !!!


References

Open Policy Agent



© PolarSPARC