Open Policy Agent
Language Introduction
openpolicyagent.org
OPA: Add fine-grained policy to other projects
OPA
Cloud
Orchestrator
Risk Management
Linux
Container Execution, SSH, sudo
OPA
Hashicorp
Terraform
OPA
Microservice APIs
Istio Linkerd
OPA
Placement &
Admission Control
openpolicyagent.org
Use OPA to policy-enable your project
Integrate
Offload policy decisions from your project to OPA
Author
Write OPA policies that make decisions
Manage
Deploy OPA, retrieve policy, audit decisions, monitor health
1
2
3
Agenda
● How Policies are Invoked
● Simple Policies
● Policies with Iteration
● Additional Topics
○ Modularity
○ Negation
○ Any/All
○ Non-boolean Decisions
How Policies are Invoked
● Overview
● Example:
○ HTTP API Authorization
openpolicyagent.org
How Policies are Invoked
DataLogic
openpolicyagent.org
How Policies are Invoked
DataLogic
POST v1/data/<policy-name>
{“input”: <JSON>}
1. Decision Request
Any JSON value:
● "alice"
● ["api", "v1", "cars"]
● {"headers": {...}}
openpolicyagent.org
How Policies are Invoked
DataLogic
200 OK
{“result”: <JSON>}
1. Decision Request 2. Decision Response
Any JSON value:
● true, false
● "bob"
● {"servers": ["server-001", …]}
POST v1/data/<policy-name>
{“input”: <JSON>}
Any JSON value:
● "alice"
● ["api", "v1", "cars"]
● {"headers": {...}}
openpolicyagent.org
How Policies are Invoked
DataLogic
200 OK
{“result”: <JSON>}
Input is JSON. Policy decision is JSON.
1. Decision Request 2. Decision Response
POST v1/data/<policy-name>
{“input”: <JSON>}
Any JSON value:
● true, false
● "bob"
● {"servers": ["server-001", …]}
Any JSON value:
● "alice"
● ["api", "v1", "cars"]
● {"headers": {...}}
openpolicyagent.org
Example: HTTP API Authorization
DataLogic
openpolicyagent.org
Example: HTTP API Authorization
DataLogic
1. Example Request to OPA
POST v1/data/http/authz/allow
{"input": {
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"}}
1
openpolicyagent.org
Example: HTTP API Authorization
DataLogic
1. Example Request to OPA
2. Example Policy in OPA
POST v1/data/http/authz/allow
{"input": {
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"}}
package http.authz
allow {
input.user == “bob”
}
1
2
openpolicyagent.org
Example: HTTP API Authorization
DataLogic
1. Example Request to OPA
2. Example Policy in OPA
3. Example Response from OPA
POST v1/data/http/authz/allow
{"input": {
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"}}
package http.authz
allow {
input.user == “bob”
}
{“result”: true}
1
2
3
Agenda
● How Policies are Invoked
● Simple Policies
● Policies with Iteration
● Additional Topics
○ Modularity
○ Negation
○ Non-boolean Decisions
Simple Policies
● Lookup values
● Compare values
● Assign values
● Create rules
● Create functions
● Use context (data)
openpolicyagent.org
Lookup and Compare Values
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Input
input.method
input.path[0]
Lookup values.
openpolicyagent.org
Lookup and Compare Values
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Input
input.method == “GET”
input.path[0] == “finance”
input.user != input.method
Lookup values. Compare values.
openpolicyagent.org
Lookup and Compare Values
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Input
input.method == “GET”
input.path[0] == “finance”
input.user != input.method
startswith(input.path[1], “sal”)
count(input.path) > 2
Lookup values. Compare values.
See 50+ operators documented at openpolicyagent.org/docs/language-reference.html
openpolicyagent.org
path := input.path
path[2] == "alice"
Assign Values to Variables
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Input Assign variables.
Use variables like input.
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow = true {
input.method == "GET"
input.user == "bob"
}
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow = true {
input.method == "GET"
input.user == "bob"
}
Rule Head
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow = true {
input.method == "GET"
input.user == "bob"
}
Rule Head
Name allow
Value true
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow {
input.method == "GET"
input.user == "bob"
}
Rule Head
Name allow
Value true
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow {
input.method == "GET"
input.user == "bob"
}
Rule Body
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow {
input.method == "GET"
input.user == "bob"
}
Rule Body
Multiple statements
in rule body
are ANDed together.
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules have a Head and a Body.
allow {
input.method == "GET"
input.user == "bob"
}
Rule Body
Multiple statements
in rule body
are ANDed together.
allow is true IF
input.method equals "GET" AND
input.user equals "bob"
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Multiple rules with same name.
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
openpolicyagent.org
Input
Create Rules
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Multiple rules with same name.
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}Rule Head
Multiple statements
with same head
are ORed together.
openpolicyagent.org
Create Rules
Input
{
"method": "POST",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules can be undefined.
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
openpolicyagent.org
Create Rules
Input
{
"method": "POST",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules can be undefined.
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
Different method.
"POST" instead of "GET"
openpolicyagent.org
Create Rules
Input
{
"method": "POST",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Rules can be undefined.
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
Different method.
"POST" instead of "GET"
Neither rule matches.
allow is undefined (not false!)
openpolicyagent.org
Create Rules
Input
{
"method": "POST",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Use default keyword.
default allow = false
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
openpolicyagent.org
Create Rules
Input
{
"method": "POST",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Use default keyword.
default allow = false
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
default <name> = <value>
If no rules match
default value is returned.
openpolicyagent.org
Create Rules
Input
{
"method": "POST",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Use default keyword.
default allow = false
allow {
input.method == "GET"
input.user == "bob"
}
allow {
input.method == "GET"
input.user == input.path[2]
}
default <name> = <value>
If no rules match
default value is returned.
at most one default per rule set
openpolicyagent.org
Create Functions
Input
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Path is a string now.
openpolicyagent.org
Create Functions
Input
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Example rule
default allow = false
allow {
trimmed := trim(input.path, "/")
path := split(trimmed, "/")
path = ["finance", "salary", user]
input.user == user
}
Path is a string now.
openpolicyagent.org
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Create Functions
Input Example rule
default allow = false
allow {
trimmed := trim(input.path, "/")
path := split(trimmed, "/")
path = ["finance", "salary", user]
input.user == user
}
Path is a string now.
Avoid duplicating
common logic like
string manipulation
openpolicyagent.org
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Create Functions
Input Put common logic into functions
default allow = false
allow {
path := split_path(input.path)
path = ["finance", "salary", user]
input.user == user
}
split_path(str) = parts {
trimmed := trim(str, "/")
parts := split(trimmed, "/")
}
Path is a string now.
Avoid duplicating
common logic like
string manipulation
openpolicyagent.org
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Create Functions
Input Functions are Rules with arguments.
read_method(str) = true {
str == "GET"
}
read_method(str) = true {
str == "HEAD"
}
openpolicyagent.org
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Create Functions
Input Functions are Rules with arguments.
read_method(str) = true {
str == "GET"
}
read_method(str) = true {
str == "HEAD"
}
"Function" Head
Multiple statements
with same head
are ORed together.
openpolicyagent.org
{
"method": "GET",
"path": "/finance/salary/alice",
"user": "bob"
}
Create Functions
Input Functions are Rules with arguments.
read_method(str) {
str == "GET"
}
read_method(str) {
str == "HEAD"
}
"Function" Head
Multiple statements
with same head
are ORed together.
openpolicyagent.org
Policies can use Context from Outside World
PUT v1/data/<path> HTTP/1.1
Content-Type: application/json
<JSON>
Load Context/Data Into OPA
DataLogic
openpolicyagent.org
{
"users": {
"alice": {"department": "legal"},
"bob": {"department": "hr"},
"janet": {"department": "r&d"}
}
}
{
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"
}
Policies Use Context
Data (context)
Policy
allow {
# Users can access their own salary
input.user == input.path[2]
}
allow {
# HR can access any salary
user := data.users[input.user]
user.department == "hr"
}
Input
openpolicyagent.org
Summary
Lookup values input.path[1]
Compare values "bob" == input.user
Assign values user := input.user
Rules <head> { <body> }
Rule Head <name> = <value> { … } or <name> { … }
Rule Body <statement-1>; <statement-2>; … (ANDed)
Multiple Rules with same name <rule-1> OR <rule-2> OR ...
Default Rule Value default <name> = <value>
Functions Rules with arguments
Context Reference with data. instead of input.
Agenda
● How Policies are Invoked
● Simple Policies
● Policies with Iteration
● Additional Topics
○ Modularity
○ Negation
○ Any/All
○ Non-boolean Decisions
Policies With Iteration
● Iteration
● Virtual documents
● Virtual documents vs Functions
openpolicyagent.org
{
"resources": [
{"id": "54cf10", "owner": "alice"},
{"id": "3df429": "owner": "bob"}
...
],
...
}
# allow if resource is at element 0
allow {
input.resource == data.resources[0].id
input.user == data.resources[0].owner
}
What about Arrays?
Allow if user owns resource.
Not sure where resource is in array
{
"user": "alice"
"resource": "54cf10",
}
Input
Data
openpolicyagent.org
{
"resources": [
{"id": "54cf10", "owner": "alice"},
{"id": "3df429": "owner": "bob"}
...
],
...
}
# allow if resource is at element 0
allow {
input.resource == data.resources[0].id
input.user == data.resources[0].owner
}
# OR if resource is at element 1
allow {
input.resource == data.resources[1].id
input.user == data.resources[1].owner
}
What about Arrays?
Allow if user owns resource.
Not sure where resource is in array
{
"user": "alice"
"resource": "54cf10",
}
Input
Data
openpolicyagent.org
{
"resources": [
{"id": "54cf10", "owner": "alice"},
{"id": "3df429": "owner": "bob"}
...
],
...
}
# allow if resource is at element 0
allow {
input.resource == data.resources[0].id
input.user == data.resources[0].owner
}
# OR if resource is at element 1
allow {
input.resource == data.resources[1].id
input.user == data.resources[1].owner
}
...
What about Arrays?
Allow if user owns resource.
Not sure where resource is in array
{
"user": "alice"
"resource": "54cf10",
}
Input
Data
Problem: Unknown number of elements.
Cannot write allow for every index.
openpolicyagent.org
{
"resources": [
{"id": "54cf10", "owner": "alice"},
{"id": "3df429": "owner": "bob"}
...
],
...
}
# allow if resource is anywhere in array
allow {
input.resource == data.resources[index].id
input.user == data.resources[index].owner
}
Iterate over Arrays
Allow if user owns resource.
Not sure where resource is in array
{
"user": "alice"
"resource": "54cf10",
}
Input
Data
openpolicyagent.org
{
"resources": [
{"id": "54cf10", "owner": "alice"},
{"id": "3df429": "owner": "bob"}
...
],
...
}
# allow if resource is anywhere in array
allow {
input.resource == data.resources[index].id
input.user == data.resources[index].owner
}
Iterate over Arrays
Allow if user owns resource.
Not sure where resource is in array
{
"user": "alice"
"resource": "54cf10",
}
Input
Data
Solution:
● allow is true if SOME value for index makes
the rule body true.
● OPA automatically iterates over values for
index.
● allow is true for index = 0
openpolicyagent.org
Iterate over Everything
Iterate over arrays/dictionaries (whether input or data)
# Iterate over array indexes/values
resource_obj := data.resources[index]
# Iterate over dictionary key/values
user_obj := data.users[name]
# Doesn’t matter whether input or data
value := input[key]
# Use _ to ignore variable name
# Iterate over just the array values
resource_obj := data.resources[_]
{
"method": "GET",
"path": ["resources", "54cf10"],
"user": "bob"
}
Input
{
"resources": [
{"id": "54cf10", "owner": "alice"},
{"id": "3df429": "owner": "bob"}
],
"users": {
"alice": {"admin": false},
"bob": {"admin": true},
"charlie": {"admin": true},
}
}
Data
openpolicyagent.org
Duplicated Logic Happens with Iteration too
{
“users”: [
{"name": "alice", "admin": false, “dept”: “eng”},
{"name": "bob", "admin": true, “dept”: “hr”},
{"name": "charlie", "admin": true, “dept”: “eng”},
}
}
Data
allow {
user := data.users[_]
user.admin == true
user.name == input.user
input.method == “GET”
}
allow {
user := data.users[_]
user.admin == true
user.name == input.user
input.method == “POST”
}
Duplicated logic with iteration
openpolicyagent.org
Duplicated Logic Happens with Iteration too
{
“users”: [
{"name": "alice", "admin": false, “dept”: “eng”},
{"name": "bob", "admin": true, “dept”: “hr”},
{"name": "charlie", "admin": true, “dept”: “eng”},
}
}
Data
allow {
user := data.users[_]
user.admin == true
user.name == input.user
input.method == “GET”
}
allow {
user := data.users[_]
user.admin == true
user.name == input.user
input.method == “POST”
}
Duplicated logic with iteration
Avoid duplicating
common logic like a
search for admins
openpolicyagent.org
Create a Virtual Document
{
“users”: [
{"name": "alice", "admin": false, “dept”: “eng”},
{"name": "bob", "admin": true, “dept”: “hr”},
{"name": "charlie", "admin": true, “dept”: “eng”},
}
}
Data
allow {
admin[input.user]
input.method == “GET”
}
allow {
admin[input.user]
input.method == “POST”
}
admin[user.name] {
user := data.users[_]
user.admin == true
}
Duplicated logic with iteration
admin is a set that contains all of the admin names
Sets are an extension of JSON.
admin == { "bob", "charlie" }
openpolicyagent.org
Different Syntaxes for Virtual Sets
{
“users”: [
{"name": "alice", "admin": false, “dept”: “eng”},
{"name": "bob", "admin": true, “dept”: “hr”},
{"name": "charlie", "admin": true, “dept”: “eng”},
}
}
admin[user.name] {
user := data.users[_]
user.admin == true
}
Data Rule Syntax
admin = {user.name |
user := data.users[_]
user.admin == true
}
Set Comprehension Syntax
openpolicyagent.org
Different Syntaxes for Virtual Sets
{
“users”: [
{"name": "alice", "admin": false, “dept”: “eng”},
{"name": "bob", "admin": true, “dept”: “hr”},
{"name": "charlie", "admin": true, “dept”: “eng”},
}
}
admin[user.name] {
user := data.users[_]
user.admin == true
}
Data Rule Syntax
admin = {user.name |
user := data.users[_]
user.admin == true
}
Set Comprehension Syntax
Supports OR with
multiple rules.
No support for OR.
openpolicyagent.org
Create Virtual Dictionaries too
{
“users”: [
{"name": "alice", "admin": false, “dept”: “eng”},
{"name": "bob", "admin": true, “dept”: “hr”},
{"name": "charlie", "admin": true, “dept”: “eng”},
}
}
Data
admin[user.name] = user.dept {
user := data.users[_]
user.admin == true
}
Rule Syntax
admin = {user.name: user.dept |
user := data.users[_]
user.admin == true
}
Dictionary Comprehension Syntax
openpolicyagent.org
Virtual Docs support iteration. Functions don’t.
admin[user_name] = user.dept {
user := data.users[_]
user.admin == true
user.name == user_name
}
admin(user_name) = user.dept {
user := data.users[_]
user.admin == true
user.name == user_name
}
Dictionary Function
# lookup bob’s department
admin[“bob”]
# iterate over all user/dept pairs
admin[user] = department
# iterate over everyone in HR
admin[user] == “hr”
# lookup bob’s department
admin(“bob”)
# iterate over all user/dept pairs
Can’t. Write different function.
# iterate over everyone in HR
Can’t.
openpolicyagent.org
Virtual Documents must be finite. Functions don’t.
Can’t express split_path.
Virtual docs must be “safe”.
Safety means the set of all
input/output pairs is finite.
split_path takes any string as
input. There are infinitely
strings.
split_path(str) = parts {
trimmed := trim(str, "/")
parts := split(trimmed, "/")
}
Virtual Doc Function
Agenda
● How Policies are Invoked
● Simple Policies
● Policies with Iteration
● Additional Topics
○ Modularity
○ Negation
○ Any/All
○ Non-boolean Decisions
openpolicyagent.org
People can Create Multiple Policies and Delegate
package http.service_graph
allow {
input.source == “frontend”
input.destination == “finance”
}
...
Service graph policy
package http.org_chart
allow {
admin[user.input]
}
...
Organization chart policy
package http.authz
import data.http.service_graph
import data.http.org_chart
allow {
org_chart.allow
service_graph.allow
}
Entry point policy
openpolicyagent.org
Policies can use Negation
package http.service_graph
deny {
input.source == “frontend”
input.destination == “finance”
}
...
Service graph policy
package http.org_chart
allow {
admin[user.input]
}
Organization chart policy
package http.authz
import data.http.service_graph
import data.http.org_chart
allow {
org_chart.allow
not service_graph.deny
not deny
}
deny { ... }
Entry point policy
openpolicyagent.org
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
}
}
Any vs. All
Data Check if all users are admins. Wrong ans:
all_admins = true {
data.users[user_name].admin == true
}
openpolicyagent.org
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
}
}
Any vs. All
Data Check if all users are admins. Wrong ans:
all_admins = true {
data.users[user_name].admin == true
}
Problem: all_admins is true if ANY users are admins.
openpolicyagent.org
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
}
}
Any vs. All
Data Check if all users are admins.
all_admins = true {
not any_non_admins
}
any_non_admins = true {
user := data.users[user_name]
not user.admin
}
Solution:
1. Check if any users
are NOT admins
2. Complement (1)
openpolicyagent.org
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
}
}
Any vs. All
Data Check if all users are admins.
all_admins = true {
not any_non_admins
}
any_non_admins = true {
user := data.users[user_name]
not user.admin
}
Solution:
1. Check if any users
are NOT admins
2. Complement (1)
openpolicyagent.org
allow/deny are NOT special. Decisions are JSON
DataLogic
POST v1/data/http/authz/admin
{"input": {
"method": "GET",
"path": ["finance", "salary", "alice"],
"user": "bob"}}
2. Example Policy
1. Example Request
3. Example Response
{“result”: [“bob”, “charlie”]}
package http.authz
import data.http.service_graph
import data.http.org_chart
admin[x] {
org_chart.admin[x]
}
admin[x] {
service_graph.admin[x]
}
Sets defined with
multiple rules
are unioned together.
Policy decision can be any
JSON data: boolean, number,
string, null, array, or dictionary.
Sets are serialized to JSON
arrays.
openpolicyagent.org
Thank You!
github.com/open-policy-agent/opa
slack.openpolicyagent.org
openpolicyagent.org
Policy Example with Join
openpolicyagent.org
Policies Iterate to Search for Data
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
},
“orgs”: {
“00”: {“name”: “HR”},
“11”: {“name”: “Legal”},
“22”: {“name”: “Research”},
“33”: {“name”: “IT”},
“44”: {“name”: “Accounting”},
}
}
Data Search for the data you need
user_obj user_name org_name
{"admin": true, ...} bob Research
{"admin": true, ...} charlie IT
# Find admin users and their organization
user_obj := data.users[user_name];
user_obj.admin == true;
org_name := data.orgs[user_obj.org_code].name
Variable assignments that satisfy search criteria
openpolicyagent.org
Policies Give Names to Search Results
Data Name the search results
admins[[org_name, user_name]] {
user_obj := data.users[user_name]
user_obj.admin == true
org_name := data.orgs[user_obj.org_code].name
}
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
},
“orgs”: {
“00”: {“name”: “HR”},
“11”: {“name”: “Legal”},
“22”: {“name”: “Research”},
“33”: {“name”: “IT”},
“44”: {“name”: “Accounting”},
}
}
admins is a set that contains
all of the [org_name, user_name] pairs
that make the body true.
admins == {
["Research", "bob"],
["IT", "charlie"],
}
openpolicyagent.org
Policies Apply Search Results to Make Decisions
Input
{
“users”: {
"alice": {"admin": false, “org_code”: “11”},
"bob": {"admin": true, “org_code”: “22”},
"charlie": {"admin": true, “org_code”: “33”}
},
“orgs”: {
“00”: {“name”: “HR”},
“11”: {“name”: “Legal”},
“22”: {“name”: “Research”},
...
Apply the search results
allow {
# allow admins to do everything
admins[[_, input.user]]
}
admins[[org_name, user_name]] {
user_obj := data.users[user_name]
user_obj.admin == true
org_name := data.orgs[user_obj.org_code].name
}
Check if bob is an admin
Lookup IT admins
Iterate over all pairs
admins[[_, “bob”]]
admins[[“IT”, name]]
admins[x]
{
"method": "GET",
"path": ["resources", "54cf10"],
"user": "bob"
}
Data

Rego Deep Dive

  • 1.
  • 2.
    openpolicyagent.org OPA: Add fine-grainedpolicy to other projects OPA Cloud Orchestrator Risk Management Linux Container Execution, SSH, sudo OPA Hashicorp Terraform OPA Microservice APIs Istio Linkerd OPA Placement & Admission Control
  • 3.
    openpolicyagent.org Use OPA topolicy-enable your project Integrate Offload policy decisions from your project to OPA Author Write OPA policies that make decisions Manage Deploy OPA, retrieve policy, audit decisions, monitor health 1 2 3
  • 4.
    Agenda ● How Policiesare Invoked ● Simple Policies ● Policies with Iteration ● Additional Topics ○ Modularity ○ Negation ○ Any/All ○ Non-boolean Decisions
  • 5.
    How Policies areInvoked ● Overview ● Example: ○ HTTP API Authorization
  • 6.
  • 7.
    openpolicyagent.org How Policies areInvoked DataLogic POST v1/data/<policy-name> {“input”: <JSON>} 1. Decision Request Any JSON value: ● "alice" ● ["api", "v1", "cars"] ● {"headers": {...}}
  • 8.
    openpolicyagent.org How Policies areInvoked DataLogic 200 OK {“result”: <JSON>} 1. Decision Request 2. Decision Response Any JSON value: ● true, false ● "bob" ● {"servers": ["server-001", …]} POST v1/data/<policy-name> {“input”: <JSON>} Any JSON value: ● "alice" ● ["api", "v1", "cars"] ● {"headers": {...}}
  • 9.
    openpolicyagent.org How Policies areInvoked DataLogic 200 OK {“result”: <JSON>} Input is JSON. Policy decision is JSON. 1. Decision Request 2. Decision Response POST v1/data/<policy-name> {“input”: <JSON>} Any JSON value: ● true, false ● "bob" ● {"servers": ["server-001", …]} Any JSON value: ● "alice" ● ["api", "v1", "cars"] ● {"headers": {...}}
  • 10.
  • 11.
    openpolicyagent.org Example: HTTP APIAuthorization DataLogic 1. Example Request to OPA POST v1/data/http/authz/allow {"input": { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob"}} 1
  • 12.
    openpolicyagent.org Example: HTTP APIAuthorization DataLogic 1. Example Request to OPA 2. Example Policy in OPA POST v1/data/http/authz/allow {"input": { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob"}} package http.authz allow { input.user == “bob” } 1 2
  • 13.
    openpolicyagent.org Example: HTTP APIAuthorization DataLogic 1. Example Request to OPA 2. Example Policy in OPA 3. Example Response from OPA POST v1/data/http/authz/allow {"input": { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob"}} package http.authz allow { input.user == “bob” } {“result”: true} 1 2 3
  • 14.
    Agenda ● How Policiesare Invoked ● Simple Policies ● Policies with Iteration ● Additional Topics ○ Modularity ○ Negation ○ Non-boolean Decisions
  • 15.
    Simple Policies ● Lookupvalues ● Compare values ● Assign values ● Create rules ● Create functions ● Use context (data)
  • 16.
    openpolicyagent.org Lookup and CompareValues { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob" } Input input.method input.path[0] Lookup values.
  • 17.
    openpolicyagent.org Lookup and CompareValues { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob" } Input input.method == “GET” input.path[0] == “finance” input.user != input.method Lookup values. Compare values.
  • 18.
    openpolicyagent.org Lookup and CompareValues { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob" } Input input.method == “GET” input.path[0] == “finance” input.user != input.method startswith(input.path[1], “sal”) count(input.path) > 2 Lookup values. Compare values. See 50+ operators documented at openpolicyagent.org/docs/language-reference.html
  • 19.
    openpolicyagent.org path := input.path path[2]== "alice" Assign Values to Variables { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob" } Input Assign variables. Use variables like input.
  • 20.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow = true { input.method == "GET" input.user == "bob" }
  • 21.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow = true { input.method == "GET" input.user == "bob" } Rule Head
  • 22.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow = true { input.method == "GET" input.user == "bob" } Rule Head Name allow Value true
  • 23.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow { input.method == "GET" input.user == "bob" } Rule Head Name allow Value true
  • 24.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow { input.method == "GET" input.user == "bob" } Rule Body
  • 25.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow { input.method == "GET" input.user == "bob" } Rule Body Multiple statements in rule body are ANDed together.
  • 26.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Rules have a Head and a Body. allow { input.method == "GET" input.user == "bob" } Rule Body Multiple statements in rule body are ANDed together. allow is true IF input.method equals "GET" AND input.user equals "bob"
  • 27.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Multiple rules with same name. allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] }
  • 28.
    openpolicyagent.org Input Create Rules { "method": "GET", "path":["finance", "salary", "alice"], "user": "bob" } Multiple rules with same name. allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] }Rule Head Multiple statements with same head are ORed together.
  • 29.
    openpolicyagent.org Create Rules Input { "method": "POST", "path":["finance", "salary", "alice"], "user": "bob" } Rules can be undefined. allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] }
  • 30.
    openpolicyagent.org Create Rules Input { "method": "POST", "path":["finance", "salary", "alice"], "user": "bob" } Rules can be undefined. allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] } Different method. "POST" instead of "GET"
  • 31.
    openpolicyagent.org Create Rules Input { "method": "POST", "path":["finance", "salary", "alice"], "user": "bob" } Rules can be undefined. allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] } Different method. "POST" instead of "GET" Neither rule matches. allow is undefined (not false!)
  • 32.
    openpolicyagent.org Create Rules Input { "method": "POST", "path":["finance", "salary", "alice"], "user": "bob" } Use default keyword. default allow = false allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] }
  • 33.
    openpolicyagent.org Create Rules Input { "method": "POST", "path":["finance", "salary", "alice"], "user": "bob" } Use default keyword. default allow = false allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] } default <name> = <value> If no rules match default value is returned.
  • 34.
    openpolicyagent.org Create Rules Input { "method": "POST", "path":["finance", "salary", "alice"], "user": "bob" } Use default keyword. default allow = false allow { input.method == "GET" input.user == "bob" } allow { input.method == "GET" input.user == input.path[2] } default <name> = <value> If no rules match default value is returned. at most one default per rule set
  • 35.
    openpolicyagent.org Create Functions Input { "method": "GET", "path":"/finance/salary/alice", "user": "bob" } Path is a string now.
  • 36.
    openpolicyagent.org Create Functions Input { "method": "GET", "path":"/finance/salary/alice", "user": "bob" } Example rule default allow = false allow { trimmed := trim(input.path, "/") path := split(trimmed, "/") path = ["finance", "salary", user] input.user == user } Path is a string now.
  • 37.
    openpolicyagent.org { "method": "GET", "path": "/finance/salary/alice", "user":"bob" } Create Functions Input Example rule default allow = false allow { trimmed := trim(input.path, "/") path := split(trimmed, "/") path = ["finance", "salary", user] input.user == user } Path is a string now. Avoid duplicating common logic like string manipulation
  • 38.
    openpolicyagent.org { "method": "GET", "path": "/finance/salary/alice", "user":"bob" } Create Functions Input Put common logic into functions default allow = false allow { path := split_path(input.path) path = ["finance", "salary", user] input.user == user } split_path(str) = parts { trimmed := trim(str, "/") parts := split(trimmed, "/") } Path is a string now. Avoid duplicating common logic like string manipulation
  • 39.
    openpolicyagent.org { "method": "GET", "path": "/finance/salary/alice", "user":"bob" } Create Functions Input Functions are Rules with arguments. read_method(str) = true { str == "GET" } read_method(str) = true { str == "HEAD" }
  • 40.
    openpolicyagent.org { "method": "GET", "path": "/finance/salary/alice", "user":"bob" } Create Functions Input Functions are Rules with arguments. read_method(str) = true { str == "GET" } read_method(str) = true { str == "HEAD" } "Function" Head Multiple statements with same head are ORed together.
  • 41.
    openpolicyagent.org { "method": "GET", "path": "/finance/salary/alice", "user":"bob" } Create Functions Input Functions are Rules with arguments. read_method(str) { str == "GET" } read_method(str) { str == "HEAD" } "Function" Head Multiple statements with same head are ORed together.
  • 42.
    openpolicyagent.org Policies can useContext from Outside World PUT v1/data/<path> HTTP/1.1 Content-Type: application/json <JSON> Load Context/Data Into OPA DataLogic
  • 43.
    openpolicyagent.org { "users": { "alice": {"department":"legal"}, "bob": {"department": "hr"}, "janet": {"department": "r&d"} } } { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob" } Policies Use Context Data (context) Policy allow { # Users can access their own salary input.user == input.path[2] } allow { # HR can access any salary user := data.users[input.user] user.department == "hr" } Input
  • 44.
    openpolicyagent.org Summary Lookup values input.path[1] Comparevalues "bob" == input.user Assign values user := input.user Rules <head> { <body> } Rule Head <name> = <value> { … } or <name> { … } Rule Body <statement-1>; <statement-2>; … (ANDed) Multiple Rules with same name <rule-1> OR <rule-2> OR ... Default Rule Value default <name> = <value> Functions Rules with arguments Context Reference with data. instead of input.
  • 45.
    Agenda ● How Policiesare Invoked ● Simple Policies ● Policies with Iteration ● Additional Topics ○ Modularity ○ Negation ○ Any/All ○ Non-boolean Decisions
  • 46.
    Policies With Iteration ●Iteration ● Virtual documents ● Virtual documents vs Functions
  • 47.
    openpolicyagent.org { "resources": [ {"id": "54cf10","owner": "alice"}, {"id": "3df429": "owner": "bob"} ... ], ... } # allow if resource is at element 0 allow { input.resource == data.resources[0].id input.user == data.resources[0].owner } What about Arrays? Allow if user owns resource. Not sure where resource is in array { "user": "alice" "resource": "54cf10", } Input Data
  • 48.
    openpolicyagent.org { "resources": [ {"id": "54cf10","owner": "alice"}, {"id": "3df429": "owner": "bob"} ... ], ... } # allow if resource is at element 0 allow { input.resource == data.resources[0].id input.user == data.resources[0].owner } # OR if resource is at element 1 allow { input.resource == data.resources[1].id input.user == data.resources[1].owner } What about Arrays? Allow if user owns resource. Not sure where resource is in array { "user": "alice" "resource": "54cf10", } Input Data
  • 49.
    openpolicyagent.org { "resources": [ {"id": "54cf10","owner": "alice"}, {"id": "3df429": "owner": "bob"} ... ], ... } # allow if resource is at element 0 allow { input.resource == data.resources[0].id input.user == data.resources[0].owner } # OR if resource is at element 1 allow { input.resource == data.resources[1].id input.user == data.resources[1].owner } ... What about Arrays? Allow if user owns resource. Not sure where resource is in array { "user": "alice" "resource": "54cf10", } Input Data Problem: Unknown number of elements. Cannot write allow for every index.
  • 50.
    openpolicyagent.org { "resources": [ {"id": "54cf10","owner": "alice"}, {"id": "3df429": "owner": "bob"} ... ], ... } # allow if resource is anywhere in array allow { input.resource == data.resources[index].id input.user == data.resources[index].owner } Iterate over Arrays Allow if user owns resource. Not sure where resource is in array { "user": "alice" "resource": "54cf10", } Input Data
  • 51.
    openpolicyagent.org { "resources": [ {"id": "54cf10","owner": "alice"}, {"id": "3df429": "owner": "bob"} ... ], ... } # allow if resource is anywhere in array allow { input.resource == data.resources[index].id input.user == data.resources[index].owner } Iterate over Arrays Allow if user owns resource. Not sure where resource is in array { "user": "alice" "resource": "54cf10", } Input Data Solution: ● allow is true if SOME value for index makes the rule body true. ● OPA automatically iterates over values for index. ● allow is true for index = 0
  • 52.
    openpolicyagent.org Iterate over Everything Iterateover arrays/dictionaries (whether input or data) # Iterate over array indexes/values resource_obj := data.resources[index] # Iterate over dictionary key/values user_obj := data.users[name] # Doesn’t matter whether input or data value := input[key] # Use _ to ignore variable name # Iterate over just the array values resource_obj := data.resources[_] { "method": "GET", "path": ["resources", "54cf10"], "user": "bob" } Input { "resources": [ {"id": "54cf10", "owner": "alice"}, {"id": "3df429": "owner": "bob"} ], "users": { "alice": {"admin": false}, "bob": {"admin": true}, "charlie": {"admin": true}, } } Data
  • 53.
    openpolicyagent.org Duplicated Logic Happenswith Iteration too { “users”: [ {"name": "alice", "admin": false, “dept”: “eng”}, {"name": "bob", "admin": true, “dept”: “hr”}, {"name": "charlie", "admin": true, “dept”: “eng”}, } } Data allow { user := data.users[_] user.admin == true user.name == input.user input.method == “GET” } allow { user := data.users[_] user.admin == true user.name == input.user input.method == “POST” } Duplicated logic with iteration
  • 54.
    openpolicyagent.org Duplicated Logic Happenswith Iteration too { “users”: [ {"name": "alice", "admin": false, “dept”: “eng”}, {"name": "bob", "admin": true, “dept”: “hr”}, {"name": "charlie", "admin": true, “dept”: “eng”}, } } Data allow { user := data.users[_] user.admin == true user.name == input.user input.method == “GET” } allow { user := data.users[_] user.admin == true user.name == input.user input.method == “POST” } Duplicated logic with iteration Avoid duplicating common logic like a search for admins
  • 55.
    openpolicyagent.org Create a VirtualDocument { “users”: [ {"name": "alice", "admin": false, “dept”: “eng”}, {"name": "bob", "admin": true, “dept”: “hr”}, {"name": "charlie", "admin": true, “dept”: “eng”}, } } Data allow { admin[input.user] input.method == “GET” } allow { admin[input.user] input.method == “POST” } admin[user.name] { user := data.users[_] user.admin == true } Duplicated logic with iteration admin is a set that contains all of the admin names Sets are an extension of JSON. admin == { "bob", "charlie" }
  • 56.
    openpolicyagent.org Different Syntaxes forVirtual Sets { “users”: [ {"name": "alice", "admin": false, “dept”: “eng”}, {"name": "bob", "admin": true, “dept”: “hr”}, {"name": "charlie", "admin": true, “dept”: “eng”}, } } admin[user.name] { user := data.users[_] user.admin == true } Data Rule Syntax admin = {user.name | user := data.users[_] user.admin == true } Set Comprehension Syntax
  • 57.
    openpolicyagent.org Different Syntaxes forVirtual Sets { “users”: [ {"name": "alice", "admin": false, “dept”: “eng”}, {"name": "bob", "admin": true, “dept”: “hr”}, {"name": "charlie", "admin": true, “dept”: “eng”}, } } admin[user.name] { user := data.users[_] user.admin == true } Data Rule Syntax admin = {user.name | user := data.users[_] user.admin == true } Set Comprehension Syntax Supports OR with multiple rules. No support for OR.
  • 58.
    openpolicyagent.org Create Virtual Dictionariestoo { “users”: [ {"name": "alice", "admin": false, “dept”: “eng”}, {"name": "bob", "admin": true, “dept”: “hr”}, {"name": "charlie", "admin": true, “dept”: “eng”}, } } Data admin[user.name] = user.dept { user := data.users[_] user.admin == true } Rule Syntax admin = {user.name: user.dept | user := data.users[_] user.admin == true } Dictionary Comprehension Syntax
  • 59.
    openpolicyagent.org Virtual Docs supportiteration. Functions don’t. admin[user_name] = user.dept { user := data.users[_] user.admin == true user.name == user_name } admin(user_name) = user.dept { user := data.users[_] user.admin == true user.name == user_name } Dictionary Function # lookup bob’s department admin[“bob”] # iterate over all user/dept pairs admin[user] = department # iterate over everyone in HR admin[user] == “hr” # lookup bob’s department admin(“bob”) # iterate over all user/dept pairs Can’t. Write different function. # iterate over everyone in HR Can’t.
  • 60.
    openpolicyagent.org Virtual Documents mustbe finite. Functions don’t. Can’t express split_path. Virtual docs must be “safe”. Safety means the set of all input/output pairs is finite. split_path takes any string as input. There are infinitely strings. split_path(str) = parts { trimmed := trim(str, "/") parts := split(trimmed, "/") } Virtual Doc Function
  • 61.
    Agenda ● How Policiesare Invoked ● Simple Policies ● Policies with Iteration ● Additional Topics ○ Modularity ○ Negation ○ Any/All ○ Non-boolean Decisions
  • 62.
    openpolicyagent.org People can CreateMultiple Policies and Delegate package http.service_graph allow { input.source == “frontend” input.destination == “finance” } ... Service graph policy package http.org_chart allow { admin[user.input] } ... Organization chart policy package http.authz import data.http.service_graph import data.http.org_chart allow { org_chart.allow service_graph.allow } Entry point policy
  • 63.
    openpolicyagent.org Policies can useNegation package http.service_graph deny { input.source == “frontend” input.destination == “finance” } ... Service graph policy package http.org_chart allow { admin[user.input] } Organization chart policy package http.authz import data.http.service_graph import data.http.org_chart allow { org_chart.allow not service_graph.deny not deny } deny { ... } Entry point policy
  • 64.
    openpolicyagent.org { “users”: { "alice": {"admin":false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} } } Any vs. All Data Check if all users are admins. Wrong ans: all_admins = true { data.users[user_name].admin == true }
  • 65.
    openpolicyagent.org { “users”: { "alice": {"admin":false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} } } Any vs. All Data Check if all users are admins. Wrong ans: all_admins = true { data.users[user_name].admin == true } Problem: all_admins is true if ANY users are admins.
  • 66.
    openpolicyagent.org { “users”: { "alice": {"admin":false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} } } Any vs. All Data Check if all users are admins. all_admins = true { not any_non_admins } any_non_admins = true { user := data.users[user_name] not user.admin } Solution: 1. Check if any users are NOT admins 2. Complement (1)
  • 67.
    openpolicyagent.org { “users”: { "alice": {"admin":false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} } } Any vs. All Data Check if all users are admins. all_admins = true { not any_non_admins } any_non_admins = true { user := data.users[user_name] not user.admin } Solution: 1. Check if any users are NOT admins 2. Complement (1)
  • 68.
    openpolicyagent.org allow/deny are NOTspecial. Decisions are JSON DataLogic POST v1/data/http/authz/admin {"input": { "method": "GET", "path": ["finance", "salary", "alice"], "user": "bob"}} 2. Example Policy 1. Example Request 3. Example Response {“result”: [“bob”, “charlie”]} package http.authz import data.http.service_graph import data.http.org_chart admin[x] { org_chart.admin[x] } admin[x] { service_graph.admin[x] } Sets defined with multiple rules are unioned together. Policy decision can be any JSON data: boolean, number, string, null, array, or dictionary. Sets are serialized to JSON arrays.
  • 69.
  • 70.
  • 71.
    openpolicyagent.org Policies Iterate toSearch for Data { “users”: { "alice": {"admin": false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} }, “orgs”: { “00”: {“name”: “HR”}, “11”: {“name”: “Legal”}, “22”: {“name”: “Research”}, “33”: {“name”: “IT”}, “44”: {“name”: “Accounting”}, } } Data Search for the data you need user_obj user_name org_name {"admin": true, ...} bob Research {"admin": true, ...} charlie IT # Find admin users and their organization user_obj := data.users[user_name]; user_obj.admin == true; org_name := data.orgs[user_obj.org_code].name Variable assignments that satisfy search criteria
  • 72.
    openpolicyagent.org Policies Give Namesto Search Results Data Name the search results admins[[org_name, user_name]] { user_obj := data.users[user_name] user_obj.admin == true org_name := data.orgs[user_obj.org_code].name } { “users”: { "alice": {"admin": false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} }, “orgs”: { “00”: {“name”: “HR”}, “11”: {“name”: “Legal”}, “22”: {“name”: “Research”}, “33”: {“name”: “IT”}, “44”: {“name”: “Accounting”}, } } admins is a set that contains all of the [org_name, user_name] pairs that make the body true. admins == { ["Research", "bob"], ["IT", "charlie"], }
  • 73.
    openpolicyagent.org Policies Apply SearchResults to Make Decisions Input { “users”: { "alice": {"admin": false, “org_code”: “11”}, "bob": {"admin": true, “org_code”: “22”}, "charlie": {"admin": true, “org_code”: “33”} }, “orgs”: { “00”: {“name”: “HR”}, “11”: {“name”: “Legal”}, “22”: {“name”: “Research”}, ... Apply the search results allow { # allow admins to do everything admins[[_, input.user]] } admins[[org_name, user_name]] { user_obj := data.users[user_name] user_obj.admin == true org_name := data.orgs[user_obj.org_code].name } Check if bob is an admin Lookup IT admins Iterate over all pairs admins[[_, “bob”]] admins[[“IT”, name]] admins[x] { "method": "GET", "path": ["resources", "54cf10"], "user": "bob" } Data