Testing Rego policies
Mar 4th, 2022
Roie Schwaber-Cohen
Open Policy Agent |
Integration
An integral part of creating a policy is testing the policy to ensure it behaves as we expect it to. The OPA engine provides a straightforward way to test policies. In this post, we'll walk through the process of testing a policy created with Aserto.
Setup
In order to use the OPA engine for testing, we need to set it up locally. First, download the OPA engine to your machine:
On MacOS:
curl -L -o opa https://openpolicyagent.org/downloads/v0.36.1/opa_darwin_amd64
On Linux:
curl -L -o opa https://openpolicyagent.org/downloads/v0.36.1/opa_linux_amd64_static
Set the permissions on the OPA executable:
chmod 755 ./opa
Clone sample repositories
For the purpose of this post, we created a couple of repositories with policy bundles that you could use for testing. If you’d like, you can create your own policy by following the documentation found here.
ABAC Policy repo:
git clone git@github.com:aserto-demo/policy-peoplefinder-abac.git
RBAC Policy repo:
git clone git@github.com:aserto-demo/policy-peoplefinder-rbac.git
Testing the ABAC policy
Let's take a look at the simplest policy in this bundle: src/policies/get.rego
:
package peoplefinder.GET.api.users
default allowed = true
default visible = true
default enabled = true
The test for this policy is simple as well. We'll create a tests
folder in the root of the repository we cloned, and create a get_test.rego
file with the following contents:
package peoplefinder.GET.api.users
test_allowed {
allowed
}
test_visible {
visible
}
test_enabled {
enabled
}
The test_
prefix on the rule found in the test file tells the OPA engine to use it as a test. Here we're expecting the allowed
, visible
and enabled
decisions to always be true
.
To run the test, we'll execute:
opa test src/policies/get.rego tests/get_test.rego
We should see the result:
PASS: 3/3
Next, let's take a look at the src/policies/post.rego
policy:
package peoplefinder.POST.api.users
import input.user.attributes.properties as user_props
default allowed = false
default visible = false
default enabled = false
allowed {
user_props.department == "Operations"
user_props.title == "IT Manager"
}
visible {
allowed
}
enabled {
allowed
}
In this policy, we're using input.user
to access the user attributes. In order to test them, we're going to need to create a mock user. Here's what the test tests/post_test.rego
file looks like:
package peoplefinder.POST.api.users
allowed_user = {
"attributes": {
"properties": {
"department": "Operations",
"title": "IT Manager"
}
}
}
test_allowed {
allowed with input.user as allowed_user
}
test_enabled {
enabled with input.user as allowed_user
}
test_visible {
visible with input.user as allowed_user
}
We could also test the inverse of the allowed
rule by creating a mock user that we'd expect to be disallowed access:
disallowed_user = {
"attributes": {
"properties": {
"department": "Sales",
"title": "Sales Manager"
}
}
}
test_not_allowed {
not allowed with input.user as disallowed_user
}
test_not_enabled {
not enabled with input.user as disallowed_user
}
test_not_visible {
not visible with input.user as disallowed_user
}
Finally, let's take a look at the src/policies/__id/put.rego
policy:
package peoplefinder.PUT.api.users.__id
default allowed = false
default visible = true
default enabled = true
allowed {
props = input.user.attributes.properties
props.department == "Operations"
}
allowed {
input.user.id == input.resource.id
}
In this policy, the decision will be allowed
if the user is in the "Operations" department or if their ID matches the ID of a resource - which in this case is a user ID as well. This effectively means they'll be able to execute the PUT operation if the target of the operation is the user themselves.
To test the policy, we'll need to mock input.resource
in addition to the user:
package peoplefinder.PUT.api.users.__id
user_1 = {
"id": 1,
"attributes": {
"properties": {
"department": "Operations"
}
}
}
user_2 = {
"id": 2,
"attributes": {
"properties": {
"department": "Sales"
}
}
}
resource_1 = {
"id": 1
}
resource_2 = {
"id": 2
}
test_allowed_user_disallowed_resource {
allowed with input as { "user" : user_1, "resource": resource_2 }
}
test_disallowed_user_disallowed_resource {
not allowed with input as { "user" : user_2, "resource": resource_1 }
}
test_disallowed_user_allowed_resource {
allowed with input as { "user" : user_2, "resource": resource_2 }
}
These tests demonstrate that:
- Regardless of the resource passed,
user_1
will always be allowed to execute the operation, because they are in theOperations
department. user_2
will only be allowed to execute the operation if the resource matches their own ID (meaning they are executing the operation on themselves) - since they aren't in theOperations
department.
Testing RBAC policies
There's one key difference when testing RBAC policies: since roles are defined in the data.json
file, its contents needs to be passed as part of the mock to the test. For example, let's take look at the peoplefinder-rbac
policy template.
In the src/policies/post.rego
file, we'll see the following:
package peoplefinder.POST.api.users
import input.policy.path
import input.user.attributes.roles as user_roles
default allowed = false
default visible = false
default enabled = false
allowed {
some index
data.roles[user_roles[index]].perms[path].allowed
}
visible {
some index
data.roles[user_roles[index]].perms[path].visible
}
enabled {
some index
data.roles[user_roles[index]].perms[path].enabled
}
An example test file, in this case, could be:
package peoplefinder.POST.api.users
data = {
"roles": {
"viewer": {
"description": "PeopleFinder viewers",
"perms": {
"peoplefinder.POST.api.users": {
"allowed": false,
"visible": false,
"enabled": false
}
}
},
"editor": {
"description": "PeopleFinder editor",
"perms": {
"peoplefinder.POST.api.users": {
"allowed": true,
"visible": true,
"enabled": true
}
}
},
"admin": {
"description": "PeopleFinder administrator",
"perms": {
"peoplefinder.POST.api.users": {
"allowed": true,
"visible": true,
"enabled": true
}
}
}
}
}
allowed_user = {
"attributes": {
"roles": ["admin"]
}
}
disallowed_user = {
"attributes": {
"roles": ["viewer"]
}
}
policy = {
"path": "peoplefinder.POST.api.users"
}
test_allowed {
allowed with input as { "user": allowed_user, "policy": policy } with data as data
}
test_disallowed {
not allowed with input as { "user": disallowed_user, "policy": policy } with data as data
}
As we did in the previous tests, we created mocks for the input and data passed to the policy, including both allowed and disallowed users.
Happy testing!
Roie Schwaber-Cohen
Developer Advocate
Related Content
OPA natively consumers OCI images
OPA can now consume policy bundles packaged as OCI images. Used together with the Policy CLI, you can build, tag, push, and pull policies just like docker images.
May 18th, 2022
Goodbye Open Policy Registry, Hello Open Policy Containers!
Open Policy Containers (OPCR) is now a CNCF Sandbox project, and it’s time to sunset the Open Policy Registry!
Dec 30th, 2022
RBAC vs ABAC: pros, cons, and example policies
RBAC and ABAC are two popular models for securing access to resources. Both models have their merits and both have limitations. Learn all about role-based and attribute-based access control and see example policies in this post.
Jan 11th, 2023