Skip to main content

Understanding Policies

The Structure of a Policy Bundle#

The Aserto Authorizer is loaded with a policy bundle that includes all the assets needed to make decisions. The bundle includes a policies folder, a data.json file and a manifest file.

In this example, the data.json file specifies the roles that are available for all the policies. For each role, it enumerates the module which governs them. Finally, for each module it enumerates the permissions settings that would apply for that role and module combination. Every module corresponds to a method and route in the API, following the convention policyroot.METHOD.route (where the route is dot delimited instead of slash delimited). For example:

{  "roles": {    "viewer": {        "description": "PeopleFinder viewer",        "perms": {          "peoplefinder.GET.api.users": {              "allowed": true,              "visible": true,              "enabled": true          },         ...        }    },    "editor": {...},    "admin": {...}  }}

In this snippet we can see that people with the role of viewer are allowed to access the method GET and the route /api/users for the policy root peoplefinder.

The policies folder contains all the policies used by the bundle. Policies contain rules which govern how the authorizer makes its decision. Aserto makes two pieces of information available to the policies: the roles that are defined in the data.json file and the user information from the user directory.

The User object#

Aserto caches all user information in the Authorizer, so that we can use the identity context as a key for looking up all the known data about the user in constant time. Here is an example of a user object that is stored in the directory:

{  "id": "cirkzmrhzgmzos03mzm1ltqwngqtywy2ni1jnzdjzjezyte1zjgsbwxvy2fs",  "enabled": true,  "display_name": "Euan Garden",  "email": "euang@acmecorp.com",  "picture": "https://github.com/aserto-demo/contoso-ad-sample/raw/main/UserImages/Euan%20Garden.jpg",  "identities": {    "+1-804-555-3383": {      "kind": "IDENTITY_KIND_PHONE",      "provider": "",      "verified": false    },    "CiRkZmRhZGMzOS03MzM1LTQwNGQtYWY2Ni1jNzdjZjEzYTE1ZjgSBWxvY2Fs": {      "kind": "IDENTITY_KIND_PID",      "provider": "local",      "verified": true    },    "euang": {      "kind": "IDENTITY_KIND_USERNAME",      "provider": "",      "verified": false    },    "euang@acmecorp.com": {      "kind": "IDENTITY_KIND_EMAIL",      "provider": "local",      "verified": true    }  },  "attributes": {    "properties": {      "department": "Sales Engagement Management",      "manager": "2bfaa552-d9a5-41e9-a6c3-5be62b4433c8",      "phone": "+1-804-555-3383",      "title": "Salesperson"    },    "roles": ["acmecorp", "sales-engagement-management", "user", "viewer"],    "permissions": []  },  "applications": {},  "metadata": {    "created_at": "2021-11-08T21:16:13.883383606Z",    "updated_at": "2021-11-08T21:16:13.883383606Z",    "deleted_at": null  },  "deleted": false}

Next we’ll see how we can refer back to this user object as part of our policies.

Review the policy in the Aserto Console#

The Aserto console shows the current loaded policy for the hosted Authorizer instance for your tenant. If you click the peoplefinder-rbac policy in the Policies tab, you will see the current set of policy modules.

console-policy-get

The PeopleFinder policy follows the Aserto convention of a single policy module for each { method, route } of the API. So PeopleFinder defines a policy for GET /api/users, POST /api/users, and so on.

The modules are named using the Rego package directive, and follow the convention policyroot.METHOD.route.

The Structure of a Policy#

Policies are written in a language called Rego - the policy language defined by the Open Policy Agent (OPA). Aserto uses OPA as its authorization engine, and therefore adopts Rego as its authorization language.

Let’s examine the peoplefinder.GET.api.users policy module:

package peoplefinder.GET.api.users

The first line of the policy defines the name of the package using the Rego package directive. This name corresponds to the method and route in the application. In this case, the policy with the root peoplefinder is applied to a route for GET /api/users - which returns a list of all the users shown in the “People” view.

import input.policy.pathimport input.user.attributes.roles as user_roles

Here we use the import directive to access the user object which is made available to the policy by Aserto. In this example, we alias input.policy.path object which will allow us to refer the policy’s path - people.GET.api.users - directly without specifying the full path. Similarly, we import attributes.roles in the user object as user_roles so that later on in the policy we can refer to it directly.

default allowed = falsedefault visible = falsedefault enabled = false

There are three decisions that are set as false by default:

  • allowed governs whether an operation is allowed at the API level.
  • visible and enabled are used by the React app to determine how to render the UI elements associated with this operation (in this case, whether to show or hide the user list).

The policy rules that follow define the exception to the default behavior.

allowed {  some index  data.roles[user_roles[index]].perms[path].allowed}

The first rule in our policy is for the allowed decision. Let’s break it up, since it is somewhat complex:

user_roles[index]

This expression will use the some index expression to iterate over all the roles a user has in the user object as seen below.

{  "attributes": {    "properties": {...},    "roles": ["acmecorp", "sales-engagement-management", "user", "viewer"],    "permissions": []  }}

Let’s look at the entire expression again:

data.roles[user_roles[index]].perms[path].allowed

For each one of those roles, the policy will access the roles object in the data.json file (seen below), drill down to the perms object, and then continue drilling down to the path that defines the permission being evaluated - peoplefinder.GET.api.users. Finally the rule accesses the allowed property and that will dictate what the final value will be.

{  "roles": {    "viewer": {        "description": "PeopleFinder viewer",        "perms": {          "peoplefinder.GET.api.users": {              "allowed": true,              "visible": true,              "enabled": true          },         ...        }    },    "editor": {...},    "admin": {...}  }}

In this example, our user has the role of a viewer, the policy will follow the path roles -> viewer -> perms -> peoplefinder.GET.api.users -> allowed and will eventually return the value true. On the other hand, if a viewer attempts to access the route PUT api/users/:id the value for allowed will be false and the user will be disallowed (this is why the viewer Euan was prevented from editing the details of another user).

Note

Roles that do not exist in the data.json file will be ignored.

Unlike a viewer, an admin user trying to access PUT api/users/:id will be allowed to do so - that’s the behavior we observed when attempting to edit a user’s phone number as the user “Kris”. Here is the admin definition in the data.json file:

{    "roles": {        "viewer": {...},        "editor": {...},        "admin": {            "description": "PeopleFinder administrator",            "perms": {               ...                "peoplefinder.PUT.api.users.__id": {                    "allowed": true,                    "visible": true,                    "enabled": true                },               ...            }        }    }}

Let's review the rest of the policy:

visible {  some index  data.roles[user_roles[index]].perms[path].visible}
enabled {  some index  data.roles[user_roles[index]].perms[path].enabled}

The visible and enabled rules are built similarly, they just access their respective properties under the path object. If present, these rules can be used in conjunction with the Display State Map pattern.

In the next section we'll learn how to modify and deploy our policy, without requiring any modification to the application.