Testing Rego policies

Mar 4th, 2022

Roie Schwaber-Cohen avatar

Roie Schwaber-Cohen

Open Policy Agent  |  

Integration

testing
Testing policies

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:

  1. Regardless of the resource passed, user_1 will always be allowed to execute the operation, because they are in the Operations department.
  2. 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 the Operations 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 avatar

Roie Schwaber-Cohen

Developer Advocate