Rego Cheat Sheet

Shubhi Agarwal
10 min readOct 1, 2020

--

Contributors: Shubhi Agarwal & Ravi Chauhan

Table Of Content

Pre-requisites

Introduction to rego

Opa installation steps

Some common concepts and use-cases

Testability

Github link for examples

Pre-requisites

  1. Understanding of open policy agent
  2. Basic understanding of rego and rego playground
  3. Opa version : 0.13.5 (minimum)

Introduction to rego

OPA is purpose-built for reasoning around information represented in structured documents. The data that your service and its users publish can be inspected and transformed using OPA’s native query language Rego.

Rego is declarative so policy authors can focus on what queries should return rather than how queries should be executed.

Writing policies in rego can be sometimes tricky mainly because of its declarative nature.

This document compiles some of the important concepts and use-cases that we came across while writing policies. These are quite generic and serves a variety of use-cases. Hopefully, it will benefit a lot of people.

Opa installation steps

  1. Download opa

1.1. Download using opa binary for your platform from GitHub Releases.

For macOS (64-bit) and Linux (64-bit) : 
curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_darwin_amd64
For Windows download using the link here: Link

1.2. Download and run opa via docker

Docker Link for OPA image: DockerImage

2. Set permissions on the opa executable:

chmod 755 ./opa

3. Run opa as a server

./opa run --server (By default OPA listens for HTTP connections on 0.0.0.0:8181)

4. Curls to push policy and data files, and post a request

Push data file to the server

curl -X PUT http://localhost:8181/v1/data/{myData} --data-binary @{myData}.json

Push rego file to the server

curl -X PUT http://localhost:8181/v1/policies/{mypolicy} --data-binary @{mypolicy}.rego

Execute the rules

curl --location --request POST 'http://localhost:8181/v1/data/$policyPath$/{ruleName}' \
--header 'Content-Type: text/plain' \
--data-raw '{Input_Json}'

For details refer: Run opa as server

5. Run the test on opa

From the root directory containing rego files and data files(JSON), run the following command: opa test . -v

For details refer: OPA Documentation Testing

Some common concepts and use-cases

  1. What are partial and complete rules?

Rules define the context of the policy document in OPA.

Key in the head can refer to a value, array, object etc. The head of the rule is assigned values that are an aggregation of all the rules that evaluate to true.

  1. Partial rules

Generating sets: Head declares only keys whose value is defined and returned from the body.

allow[result] {
input.action.id == "HR"
result = "allow HR"
}
allow[result] {
input.action.name == "employee"
result = "allow employee"
}
Playground Link: https://play.openpolicyagent.org/p/nRkaBvzZXw

Generating objects: Head declaring a key and a value for the rule.

allow[result] = action_id{
input.action.id == "HR"
action_id = input.action.id
result = "allow HR"
}
allow[result] = action_id{
input.action.name == "employee"
action_id = input.action.name
result = "allow employee"
}
Playground Link: https://play.openpolicyagent.org/p/C0WIUYMSC2

2. Complete rules

Rules provide a complete definition by omitting the key in the head. Documents produced by rules with complete definitions can only have one value at a time. If evaluation produces multiple values for the same document, an error will be returned.

Complete rule with no multiple outputsallow = {"result": "allow"}{
input.role =="Employee"
input.action == "salary view"
}
allow = {"result": "deny"} {
input.role =="Manager"
not input.action == "salary view"
}
Playground Link: https://play.openpolicyagent.org/p/VnqGE3ZZNsComplete rule with multiple outputsallow = {"result": "allow"}{
input.role =="Employee"
input.action == "salary view"
}
allow = {"result": "deny"} {
input.senior =="Manager"
input.action == "salary view"
}
Output
//eval_conflict_error: complete rules must not produce multiple
outputs
Playground Link: https://play.openpolicyagent.org/p/o2NV002oGo

2. Equality in regos

Rego supports three kinds of equality as mentioned below:

  1. Assignment(:=)

Assigned variables are locally scoped to that rule and shadow global variables.

employeeType := "HR"
allow {
employeeType := "Manager" # declare local variable
'employeeType' and assign value
Manager
employeeType != "HR" # true because 'employeeType'
refers to local variable
}
Output
{
"allow": true,
"employeeType": "HR"
}
Playground Link: https://play.openpolicyagent.org/p/HkWlDf2HPa

2. Comparison(==)

Comparison checks if two values are equal within a rule. If the left or right-hand side contains a variable that has not been assigned a value, the compiler throws an error.

employee {
input.employeeType == "HR"
}
Playground Link: https://play.openpolicyagent.org/p/sUJ99P7EvX

3. Unification(=)

Unification (=) combines assignment and comparison.

Rego will assign variables to values that make the comparison true. Unification lets you ask for values for variables that make an expression true.

employee = [employeeType,action]{
[action,"Engineer"] = ["viewSalary",employeeType]
}
Output
[
"Engineer",
"viewSalary"
]
Playground Link: https://play.openpolicyagent.org/p/gVSIfFtpKP

3. Iterating arrays

  1. Lookup by iteration

This is useful to verify if an input exists in the array list. Time Complexity of this operation is O(n).

Iterate using [_]
allow {
input.role =="Employee"
allowedActionEmployee :=
["salary_view","update_personal_info","view_personal_info"]
input.action = allowedActionEmployee[_]
}
Iterate using an iterator
allow {
input.role =="HR"
allowedActionHR :=
["salary_view","salary_edit","view_personal_info",
"add/remove_employee"]
some i
input.action = allowedActionHR[i]
}
Playground Link: https://play.openpolicyagent.org/p/b8ngVw42Df

2.Iteration through multiple arrays

There are use-cases where we need to compare multiple values corresponding to the value in the static-list. We can use both the iterations above.

We can extract object info corresponding to the same values in two lists along with their index as described below.

#Find the type of all the roles corresponding to the input
role_id_set[input_id] = role_id{
some k,i
role_info.roles[k].id == input.role_set[i]
role_id := role_info.roles[k].type
input_id := input.role_set[i]
}
Playground Link: https://play.openpolicyagent.org/p/Pl9cUbpsfS

Note that, in the above examples, statements that are written below ‘[_]’ or ‘some <variable>’ are always under the loop.

4. Using negations in rego

  1. Not equals to (!= )

Rego is existentially quantified. This means that rule bodies and queries express FOR ANY and not FOR ALL. Thus, while using ‘!=’ operator it looks for a single value which is not equal to the value compared, however when we use negations we often need to compare FOR ALL rather than FOR ANY.

allow {
allowed_actions := { "add", "delete", "edit" }
input.action != allowed_actions[_]
}
Input : {"action": "edit"}
Output : {"allow": true }
Playground Link: https://play.openpolicyagent.org/p/nvUPWyh3WU

2. Another rule that’s enforced by OPA is that a variable appearing in a negated expression must also appear in another non-negated equality expression in the rule else it will throw an error. For instance,

allow {
allowed_actions :=
{"view_personal_info","edit_personal_info","view_salary",
"update_salary"}
not input.action == data.allowed_actions[_]
}
Output : rego_unsafe_var_error: var _ is unsafe
Playground Link: https://play.openpolicyagent.org/p/qtanOZaJdQ

To express FOR ALL in Rego, complement the logic in the ruling body (e.g., ‘!=’ becomes ‘==’) and then, complement the check using negation (e.g. is_Action_Allowed becomes ‘not’ is_Action_Allowed) as shown.

To solve for both the issues, we use negations by using the ‘not’ operator as follows:

allow = true { not is_Action_Allowed }
is_Action_Allowed = true {
allowed_actions := { "add", "delete", "edit" }
input.action == allowed_actions[_]
}
Input : {"action": "edit"}
Output : { "allow": false}
Playground Link: https://play.openpolicyagent.org/p/ZL8DU4x2u8

5. Glob for pattern matching

Glob is useful for matching the pattern separated by delimiters as defined. This is suitable for use-cases where regex matching is required or where URL matching helps in defining output.

The default delimiter is [“.”] when delimiter field is empty. Refer to playground link for applications.

api_attributes = {
"[abc]at/dogs" ,
"?et[a-z]/dogs/*",
"pets/dogs/*/adopt",
"pets/dogs/**/adopt"
}
matchUrl {
some path
api_attributes[path]
glob.match(path, ["/"], input.path)
}
Test Case
matchUrl with input as {
"path": "pets/dogs/dog123/dog234/cat123/adopt"
}
Rego Playground: https://play.openpolicyagent.org/p/5QNfjE3hiF

6. Conflict resolution

Rego evaluates and returns the output of all the rules that evaluate to true while executing partial rules. This can create conflicts in decision making, especially when both the permit and deny get executed.

These kinds of conflicts can be avoided by wrapping the rules with the parent rule which is complete and maintains the uniqueness of the result.

We solved it by creating an allow rule which is a complete rule and wraps the partial rules to unite to a single decision.

default allow = {"reason": "access denied" }allow = {"permissions": permit } {
permit
not deny
}
allow = { "permissions": deny } {
deny
}
permit[x] = y { [x, "hr"] = ["permit", y] }
deny[x] = y { [x, "employee"] = ["deny", y] }
Playground Link: https://play.openpolicyagent.org/p/O63ZYDXani

7. Debugging using rego

Debugging in playground/styra is simple but in live environments, it’s challenging to analyse and figure out which rule is executed.

We add a negative rule for each rule we add which will execute when the corresponding positive rule fails to execute.

permit {
is_valid_employee
is_allowed_action_of_employee
has_required_employee_permissions
}
deny[reason] {
not is_valid_employee
reason := "EMPLOYEE_DO_NOT_EXISTS"
}
deny[reason]{
not is_allowed_action_of_employee
reason := "INVALID_ACTION"
}
deny[reason]{
not has_required_employee_permissions
reason := "INVALID_PERMISSIONS"
}
Playground Link: https://play.openpolicyagent.org/p/fKunnjFlbL

8. Hierarchical data traversal

Often we come across use cases where data is static but it branches in various layers like a tree[JSON tree].

For instance: The HTTP request format is hierarchical branching from URI, method type to attribute parameters.

We had one such use case where we needed to find if a mapping exists corresponding to the attribute value in a static data.

Please refer to the playground link to check the exact use-case.

allow {
employeeExistInCompany
employeeIdMap = split(input.employeeId,",")
count(checkMapping(employeeIdMap)) == 0
}
employeeExistInCompany {
count(findMapping)> 0
}
checkMapping(identityProvidersInput) = {a | a := identityProvidersInput[_]} - {b | b := findMapping[_]}
findMapping = output {
output := data.ApplicationPartnerMap[input.originalRequestURI][input.httpMethod][input.teamId]
}
Data
{
"ApplicationPartnerMap": {
"/company/hr/manager": {
"GET": {
"team1": [
"Ross",
"Rachel"
],
"team2": [
"Penny"
]
}
},
"/company/tech/employee": {
"GET": {
"team1": [
"Monica",
"Pheobe",
"Chandler"
],
"team2": [
"Lenord"
]
},
"PUT": {
"team3": [
"Mike",
"Gunther",
"Jannice"
]
}
}
}
}
Playground Link: https://play.openpolicyagent.org/p/I2poPkRxX7

9. Traversing environment-specific data

In the software world, we don’t make a release to prod directly instead we have various development environments for quality, performance, end to end testing before we make a release in production.

The data, however, is different in these different environments and there should be some way to identify what to use.

There are various ways we can solve for it.

  1. Store environment-specific data in separate files and import them
checkMapping(identityProvidersInput) = {a | a := identityProvidersInput[_]} -  {b | b := findMapping[_]}findMapping = output {
input.connectingEnvironment == "qal"
output := qal[input.originalRequestURI][input.httpMethod][input.teamId]
}
findMapping = output {
input.connectingEnvironment == "prod"
output := prod[input.originalRequestURI][input.httpMethod][input.teamId]
}
Data
{
"prod": {
...
}
},
"qal": {
"ApplicationPartnerMap": {
...
}
}
}
Playground Link: https://play.openpolicyagent.org/p/dwET4mc19c

2. Maintain single storage for all the environments data described as follows

import data.AllEnvironmentData as appData
findMapping = output {
output := appData[input.connectingEnvironment][input.originalRequestURI][input.httpMethod][input.teamId]
}
Data
{
"AllEnvironmentData": {
"prod": {
...
},
"qal": {
...
}
}
}
Playground Link: https://play.openpolicyagent.org/p/39RW9FUBrv

10. Walking the JSON data hierarchy

Traversing deep down the hierarchy and find out the path exists or not can be solved by using “walk”.

We can manipulate this traversal information in various ways and make deductions.

##find the management chain for role Id in input
foundpath = path {
[path, value] := walk(ManagementChain)
value= input.roleId
}
Data
{
"ManagementChain": {
"HR": 5,
"SVP": {
"Executive Assistant": 1,
"FellowEngineer": {
"DistinguishedEngineer": 2
},
"VP": {
"Manager": 18,
"PrincipalEngineer": 3
}
}
}
}
Playground Link: https://play.openpolicyagent.org/p/nJ9tR0j6VA

11. Object preprocessing

We can refactor the raw input received before using it. We also do clean up like remove whitespaces, spellchecks, basic validations, concatenations etc.

But also remember, everything comes at a cost. Therefore, this additional clean up is going to incur some amount of latency and service should be okay with that.

We can then use it to make decisions or return parts of it or the complete object.

#### Request Modification ####
request_preprocessing = preprocessedObject {
#### Trim white spaces ####
documentType := trim(input.DocumentType, " ")
action := trim(input.action, " ")
permissionsString := trim(input.Permissions, " ")

#### Check if not null and empty ####
not is_null(permissionsString)
permissionsString != ""
not is_null(documentType)
documentType != ""
not is_null(action)
action != ""

#### concat to for valid action ####
tempArray := array.concat([documentType],[action])
permission := concat(".",tempArray)

preprocessedObject := {"documentType" : documentType,
"action" :action,
"permissionsString" : permissionsString,
"resource-action" : tempArray,
"permission" : permission}
}
Playground Link: https://play.openpolicyagent.org/p/12EhSDPu4A

12. User-defined functions

Rego provides great function support.

But sometimes we need to define our utility functions to fulfil the needs of the policy.

Here are examples of the functions that are mostly present in java and replicated in rego.

contains_all_ignore_case(input_list,value_list){
input_list_array := split(lower(input_list),",")
count( {b | b:= lower(value_list[_]) } - {a | a :=
lower(input_list_array[_])}) == 0
}
contains_any_ignore_case(input_list,value_list){
input_list_array := split(lower(input_list),",")
diff := {b | b:= lower(value_list[_]) } & {a | a :=
lower(input_list_array[_])}
count(diff) > 0
}
containsIn(PermissionArray, permission) {
PermissionArray[_] = permission
}
Playground Link: https://play.openpolicyagent.org/p/OadLtxjNPX

13. Making a batch-call

We often make batch calls in a single request. We can use ‘with’ to iterate over the resources in input and written output as a list. This is how we do it. Please refer to the playground link for a complete example.

batch_authorize = [result   | 
some i
request := input.requests[i]
result := authorize with input as {
"employeeId" : input.employeeId,
"httpMethod" : request.httpMethod,
"originalRequestURI" : request.originalRequestURI,
"teamId" : request.teamId

}
]
Input
{ ... #common request attributes# ...

"requests" : [ ...]
}
Playground Link: https://play.openpolicyagent.org/p/rnvlq55fVA

14. Hierarchical search

Rego provides a feature to load static data and use that information to author and derive outcomes from the policy. Based on the given input, how do we search and find a pattern?

An example of it is described below :

##### return all publically accessable apis and method ########
api_attributes = {
"v1/test1": {
"GET": {"readonly": true, "public": true}
...
},
"v2/test2/*": {
...
},
...
}
api_is_read[result]{
some path
value := api_attributes[path]
value[input.method].public == true
result := path
}
Input: {"method" : "GET"}Output: [ "v1/test1"]PlayGround Link: https://play.openpolicyagent.org/p/qmkxsHHNQs

Testability

Testing is an important part of the software development process. Rego supports unit testing.

We can write test cases for all the scenarios and check if the system behaves the way we expect it to.

Important features of testing in rego

  1. Function with parameters
contains_all_ignore_case(input_list,value_list){
...
}
test_contains_all_ignore_case {
contains_all_ignore_case("prep,review",{"prep", "review"})
}
not_test_contains_all_ignore_case {
not contains_all_ignore_case("prep",{"prep", "review"})
}
Playground Link: https://play.openpolicyagent.org/p/uydymRpjNY

2. Function without parameter

is_Valid_action{ input.action == data.AllowedAction[_]}test_is_Valid_action {
is_Valid_action with input as {
"action" : "read"
} with data.AllowedAction as MockData
}
test_not_is_Valid_action {
not is_Valid_action with input as {
"action" : "edit"
} with data.AllowedAction as MockData
}
Playground Link: https://play.openpolicyagent.org/p/0PAratV6QC

3. Testing rules returning sets/objects

permit[permission]{
...
}
test_Permissions {
permit == {"reviewPermission"} with input as {
"role" : "expert"
}
}
test_Permit_developer {
count(getPermission("developer"))>0
with data.permissionList as {
"developer" : "test"
}
}
Playground Link : https://play.openpolicyagent.org/p/1QnSa6PfKd

4. Mock data

is_Valid_action{
...
}
test_not_is_Valid_action {
not is_Valid_action with input as {
"action" : "edit"
} with data.AllowedAction as MockData
}
MockData = ["read","write","update", "delete"]
Playground Link : https://play.openpolicyagent.org/p/cPqybxYqCd

5. Test complete rules

authorize = {"decision": "permit"} {
...
}
authorize = {"decision": "deny"} {
...
}
test_authorize {
authorize.decision =="permit" with input as {
"action": {
"id" : "exp"
}
}
}
Playground Link : https://play.openpolicyagent.org/p/UZe04GBh6J

6. Assign and compare

test_allow {
result := allow with input as {
"action": { "id" : "exp" }
}
result.decision == "permit"
}
test_fail_allow {
result := allow with input as {
"action": { "name" : "adm" }
}
result.decision == "deny"
}
Playground Link: https://play.openpolicyagent.org/p/UyV9hvbr9P

For all the above examples, please find Github repository below:

Github-link: https://github.com/shubhi-8/RegoCheatSheetExamples

--

--