Back to listing

How do Aserto Rego Policies Work?

Roie Schwaber-Cohen avatar

Roie Schwaber-Cohen

Developer Advocate

Mar 17th, 2022

Authorization

Aserto Rego Policies

The cornerstone of any decision made by the Aserto authorizer is the authorization policy. The policy encapsulates the authorization logic, allowing for a clear separation of concerns between the application code and the authorization-specific logic.

There are several benefits to having the authorization logic separated from the rest of the code:

  1. It allows the application to evolve separately from the authorization logic and independent versioning for policy artifacts.
  2. It makes it easier to test the authorization logic. Since the policy is a standalone unit, it can be tested independently of the rest of the code without relying on the application state.
  3. It promotes the desired separation of roles and responsibilities between security, development teams, and auditors, which is the foundation for trustworthy decision-making.
  4. It allows the development of reusable authorization building blocks, which promote more consistency and stability.

Now that we understand why we’d want to have the authorization logic separate from the application code itself, let’s review the ingredients required to make an authorization decision.

The ingredients of an authorization decision

Three ingredients are involved in making an authorization decision:

  • Identity context: the identity of the user (or system) making the request. This information is provided by the application after the user has been authenticated.
  • Resource context: when a user takes an action on a resource, the request will include information regarding that resource.
  • Policy context: policies are made up of policy modules that together comprise a policy bundle. The policy context contains a reference to the policy bundle as well as the specific module name the authorizer is called with.

The identity context, resource context, and policy context are provided by the application through the middleware function attached to the endpoint being requested. The decision engine combines all three ingredients, resolves the decision, and returns the result to the middleware function.

Structure of an Aserto policy bundle

The policy includes multiple modules that correspond to an application's protected endpoints. Each module includes a set of clauses that correspond to decisions the authorizer could make for a given endpoint.

Under the hood, Aserto uses the Open Policy Agent (OPA) decision engine to resolve authorization decisions. OPA policies are written in Rego, which is a high-level declarative language for expressing authorization logic. Rego’s declarative nature makes its queries simpler than its imperative counterparts. OPA is able to optimize queries and produce single-digit millisecond responses for most queries.

The Aserto policy is packaged as a bundle that includes a collection of files. Let’s take a look at the structure of a typical Aserto policy bundle:

└── src
    ├── .manifest
    ├── data.json
    └── policies
        ├── __id
        │   ├── delete.rego
        │   ├── get.rego
        │   ├── post.rego
        │   └── put.rego
        ├── get.rego
        └── post.rego

Under the src directory, we can see

  • the .manifest file which tells the authorizer what the root of the policy is, as well as which portions of the data.json file should be made available to the policy.
  • data.json - contains any additional data that needs to be made available to all policy modules in the bundle.
  • The policies directory which contains the policy modules.

Workflow Automation

Aserto supports a policy-as-code workflow that allows you to use GitHub or GitLab to store and version your policies. When set up in Github or GitLab, The .github or .gitlab directory contains the configuration for the workflow used to build the bundle and push it to the Aserto policy registry. Whenever a new version of the application is pushed to the repository, the workflow will be triggered and the policy bundle will be built and pushed to the Aserto Policy Registry.

The Rego Policy module

Package path

The first line of the policy module is the package name. It corresponds directly to the HTTP verb and path of the endpoint used in the application. For the convention that we chose for REST-style services, the structure of the package name combines the root of the policy as defined in the .manifest file, the HTTP verb, and the path of the endpoint:

package <policy-root>.<http-verb>.<path>

For example, given a policy with the root mypolicy, if we defined a GET /protected endpoint, the package definition would be:

package mypolicy.GET.protected

If the endpoint is parametrized, for example, GET /protected/:id, the package name would be:

package mypolicy.GET.protected.__id

When a request is sent to an endpoint, the Aserto middleware will extract the HTTP verb used as well as the path of the endpoint and use it to determine which policy module to apply. The policy root is provided to the middleware when it is initialized with the policy ID (alongside the tenant ID and authorizer API key).

Decision clauses

The decision clauses are the “meat” of the policy. A decision clause can evaluate as either true or false. For example:

allowed {
  foo == bar 
}

Clauses can be dependent on other clauses. For example, we can write an enabled clause that would resolve to true if the allowed clause is true:

allowed {
  foo == bar
}

enabled {
  allowed
}

In this example, if foo == bar then the enabled decision will be true as well.

To express an “or” relationship between clauses, we simply define multiple clauses using the same name. For example:

allowed {
  foo == bar
}

allowed { 
  bar == baz
}

The module will evaluate to true if either of the clauses is true.

To express an “and” relationship, we define multiple conditions within a single clause:

allowed {
  foo == bar
  bar == baz
}

The module will evaluate to true if both of the clauses are true.

Documents

The policy can access two types of documents: input documents and data documents.

Input

The input object is used by Aserto to make the identity and resource context available to the decision engine.

The identity context passed from the application is usually a JWT or a SAML token. Aserto uses this token to resolve additional identity information from the directory service, and it then passes the resolved user object to the decision engine. This typically includes any roles and attributes associated with an identity. Aserto attaches this information to the input object under input.user.

If the application passes a resource context to the decision engine, Aserto attaches it to the input object under input.resource.

Data

The data object reflects the contents of the data.json file, which can be optionally included in the bundle. It contains any additional data that needs to be made available to all policy modules in the bundle. For example, consider the following data.json file:

{
  "foo": "bar",
  "baz": "qux"
}

Given the resource context:

{
  "foo": "bar",
  "baz": "wow"
}

The following decision clause will evaluate to true:

allowed {
  input.resource.foo == data.foo
}

And this decision clause will evaluate to false:

allowed {
  input.resource.baz == data.baz
}

If the file isn’t placed under the root folder, it’ll be namespaced under the directory in which it is placed. For example, if the file is placed in src/mydata/data.json, the data will be available to the policy module under data.mydata.

Visible and Enabled decisions

A common pattern for leveraging authorization in the UI is to conditionally render components based on the user’s access to a resource.

Other than the allowed decision which is used to determine whether a request can be completed given a specific identity and resource context, we can also define visible and enabled decisions. These enabled the middleware to make conditional UI rendering decisions.

Default values

The default keyword sets the default value for a decision clause. We routinely use the default keyword to ensure that the allowed, enabled and visible clauses are set to false by default. This would only allow access in case the decision clauses are satisfied. For example:

default allowed = false
default enabled = false
default visible = false

If no default value is assigned to a decision clause, and the decision clause does not evaluate to true, then the result of the evaluation will be undefined.

Aliasing

When we use the import keyword, we can refer to the imported object without referring to its parent object, for example:

import input.user

enabled {
  user.enabled
}

We can use an import statement and the as keyword to reference a particular portion of an object and alias it. For example:

import input.user.attributes.properties as user_properties

allowed {
  user_properties.department == "Sales"
}

This makes it easier to write the decision clauses a bit more concisely.

Built-in functions

On top of the built-in functions provided by OPA, Aserto provides a number of built-in functions that can be useful when trying to resolve specific relationships between users in the Aserto directory. They are based on a convention where each user has a managerID property that links one user to another. The built-ins currently supported are:

  • dir.identity(user) - returns the user’s Aserto directory id by a key.
  • dir.is_same_user(userA, userB) - returns true if the identities of userA and userB are the same identity.
  • dir.user(user) - returns the user object by the user identity.
  • dir.manager_of(user) - returns the user object of the manager of a user, based on the manager attribute of the user-specified.
  • dir.is_manager_of(userA, userB) - returns true if userA is a manager of userB, based on the manager attribute of userB and the identity of userA.
  • dir.management_chain - returns the management chain of a user as a list of user ids.

Examples of policies

To get an idea of how a full policy bundle might look in the wild, check out the RBAC and ABAC policies used for the PeopleFinder application.

You can review the middleware definition in the PeopleFinder application here and its usage here.

Read more about how the PeopleFinder application and policy are set up in our documentation.