| 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:
Which users can access which resources
Which subnets egress traffic is allowed to
Which clusters a workload can be deployed to
Which registries software binaries can be downloaded from
Which OS capabilities a container can execute
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:
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:
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:
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:
{"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:
{
"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:
{
"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:
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:
{
"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:
{
"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:
{
"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:
{
"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:
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:
{
"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:
{
"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:
{
"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:
{
"result": {
"allow": true,
"limit": 500.00,
"role": "employee"
}
}
With this, we conclude our hands-on demonstration of using OPA to enforce enterprise policies !!!
References