Open Policy Agent Alternatives
We get a lot of questions about the differences between Oso and Open Policy Agent (OPA). There are three practical distinctions that affect how you use them:
- the primitives that each provides
- the data format that each supports
- the rubric by which you’ll implement authorization in each
In this post, we summarize these distinctions and then use examples to show how you use each product to solve a similar problem - enforcing hierarchical permissions. In a nutshell, Oso is purpose-built for application authorization, while OPA is a general-purpose policy engine. Let’s see what that means in practice.
Comparing Oso and OPA
Primitives for Writing Authorization Policies
Oso is designed specifically for application authorization. Authorization in applications generally takes the form of an actor requesting permission to perform some action on a given resource. Because these notions (actor, action, resource, permission) are so pervasive in application authorization, Oso provides primitives for them – that is, they are built in to Oso as entities with intrinsic meaning. For example, Oso provides keywords for actors, resources, and roles.
These primitives make it straightforward to implement common application authorization models such as role-based access control (RBAC) and relationship-based access control (ReBAC). You can see how this looks below.
How Oso Handles Data
Oso accepts data in a format called facts. Facts are closely related to Polar, Oso’s rules language. A fact takes this general form:
The facts format is capable of expressing any authorization-relevant data in your application. For instance, you could say that the user alice has the admin role in the acme Organization as follows:
Similarly, you could say that the roadmap repository is public with
In an application, the requests that are being authorized are often user-facing actions like placing an order or showing a list of documents. These operations need to be fast so the application can respond without degrading the user experience. With Oso, you transform your application data from its native format to facts when you load the data. Oso then makes authorization decisions by performing a small number of simple lookups against a highly indexed database of facts. This allows Oso to make these decisions quickly, minimizing the impact of authorization on application performance.
Rubric: Model, Data, Enforcement
Authorization in Oso consists of three elements: model, data, and enforcement. When you get started with Oso, you’ll first define your authorization model in in terms of the actors, actions, relationships, and attributes that you’ve built into your application. Once you have your model, you’ll load authorization data into Oso as facts. These facts will define the specific actors and resources in your application and the relationships between them. Once Oso has your rules and the authorization-relevant data from your application, it provides enforcement by rendering authorization decisions for requests by a given actor to perform a specific action on a particular resource. For example, it will determine whether the current user can create a new file in a directory by looking for a has_rolefact on the user that is associated by the model with the create permission on the directory.
What is Open Policy Agent (OPA)?
Primitives for Writing Open Policy Agent (OPA) Policies in Rego
Open Policy Agent (OPA) is a general-purpose policy engine that has an emphasis on policy enforcement for cloud infrastructure. Requests for access to cloud resources are often expressed in structured data documents (e.g. JSON, YAML) that define the characteristics of the thing that’s being requested. Because of this, OPA’s policy langage, Rego, provides primitives for inspecting and transforming structured data.
OPA’s data inspection primitives include operators for traversing dictionaries by key, iterating over and indexing into lists, testing membership in a set, and similar operations. For data transformation, OPA supports functions such as concatenating strings, defining new lists, or creating unions of sets. This lets OPA work well with structured data. Some examples are shown below
How OPA Handles Data
OPA accepts arbitrary structured data as input. It natively supports JSON and YAML because of their prevalence in modern systems, but is designed for the general case of working with data documents. Data in OPA can be deeply nested, and a value at any level of the document hierarchy may itself be a hierarchical data document. This allows OPA to accept data in its native format from a wide variety of systems. However, this also means that OPA needs to be told how to interpret the data in the context of authorization decisions.
To do this, you transform your data at policy evaluation time using the primitives that Rego provides. This allows you to coerce the input data into a structure that allows you to use OPA’s data inspection primitives to evaluate policy rules, but it adds time to each authorization decision. This can affect performance as the policy becomes more complex or as the data grows. OPA provides guidance for optimizing policy performance to address these issues.
Rubric: Load, Transform, Inspect
Authorization in OPA starts with loading data. Because authorization in OPA consists of operations on arbitrarily structured data documents, you first need to know the structure of your data before you can define the operations that implement your policy. Once you’ve loaded your data, you’ll write policy rules to transform the data as needed in order to derive the authorization-specific meaning from the source data. When the data is in the right structure, you inspect the data to determine whether to allow or deny a request. For example, in OPA, you can determine whether a given container should be deployed to kubernetes by searching the input data for a spec.containers.image key and testing whether its value exists in a list of approved container registries.
Open Policy Agent Example: Inherited Permissions
Inherited permissions are a common pattern in authorization. People often gain access to a resource by means of a permission on some other resource that contains it. For example, a person may have access to a file by means of:
- a permission on the file itself
- a role on the folder that contains the file
- a role on the team or organization that owns the file
Let’s see how inherited permissions work in Oso and OPA.
Inherited Permissions in Oso
In Oso, you model inherited permissions like this:
The code above is written in Polar, Oso’s policy language. The model is defined in terms of the primitives that Oso provides for application authorization. There is an actor block that defines a User entity, and resource blocks that define Folder and File entities. Within the resource blocks, we define the roles and relations that apply to that resource, as well as the actions that are allowed to the various roles. Finally, we define how roles on a resource are inherited from roles on that resource’s parent. Oso knows what it means for two resources to be related, so we can express a recursive permission model like this in a couple of lines:
This says that a given actor:
- can be assigned the role of “reader” or “writer” on a given folder
- has the “reader” or “writer” role on a folder if they have that role on the folder’s parent
Once the model is defined in Oso, we can supply data as facts, and use that data to validate enforcement by running a test:
This says that a given actor:
- can be assigned the role of “reader” or “writer” on a given folder
- has the “reader” or “writer” role on a folder if they have that role on the folder’s parent
Once the model is defined in Oso, we can supply data as facts, and use that data to validate enforcement by running a test:
Inherited Permissions in OPA
In OPA, you model inherited permissions like this (the following code samples come from this blog post):
The input data looks like this:
The code is written in Rego, OPA’s policy language. Here, you can see the primitives that OPA provides for manipulating structured data: the some keyword iterates over a list, list items are referenced by a numeric index, dictionary items are referenced by key, etc.
The first half of the policy is spent transforming the input data into a graph. This graph defines the relationships between the files, teams, and organizations in a way that allows OPA to perform authorization operations on the data. We build the graph by extracting the files, teams, and organizations into lists and rewriting their IDs so they match the parent_id field in the source data. We then use the union_n operator to merge these lists into the graph.
Once we’ve transformed the input data to a graph, we can inspect the data to determine whether a given user has permission to perform a requested operation on a file. We first search the user’s roles and the role’s grants to determine whether the user has the requested permission. We then use the graph.reachable operator to determine whether the user is associated with the specified file through parent/child relationships between the user’s organizations and teams. The combination of the two determines whether the user has the necessary grant on the file.
Differences between Oso and OPA
There are high quality alternatives to OPA, like Oso. The core differences between Oso and OPA are summarized below:
Oso is built for application authorization. Its rules language, Polar, supports application authorization concepts such as actors, roles, and relationships. Its data model, facts, captures authorization-relevant data in terms that can be directly evaluated against the rules. The complementary nature of Polar and facts allow Oso to render authorization decisions quickly. This is a primary consideration for application authorization operations.
OPA, on the other hand, is a general-purpose policy engine. It doesn’t define a data model, but instead supports arbitrary structured data. This allows it to accept data from a variety of systems in its native format. Its rules language, Rego, provides primitives that allow you to transform and inspect its data as needed during evaluation to make authorization decisions.
If you’re getting started with Application Authorization, you can try Oso Cloud by signing up for a free account here.