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,
"displayName": "Euan Garden",
"email": "euang@acmecorp.com",
"picture": "https://github.com/aserto-demo/contoso-ad-sample/raw/main/UserImages/Euan%20Garden.jpg",
"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"],
},
"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 version for the policy instance you created. If you click the peoplefinder-rbac
policy instance in the Policies tab, you will see the current set of policy modules.
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.path
import input.user.properties.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 - peoplefinder.GET.api.users
- directly without specifying the full path. Similarly, we import input.user.properties.roles
in the user object as user_roles
so that later on in the policy we can refer to it directly.
default allowed = false
default visible = false
default 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
andenabled
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.
{
"properties": {
"roles": ["acmecorp", "sales-engagement-management", "user", "viewer"],
}
}
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).
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.