How to Implement Relationship-Based Access Control (ReBAC) Using Open Policy Agent (OPA)

- Share:
Building authorization can be a complicated endeavor. There are different models for building authorization and different ways of implementing them. At the end of the day, only one thing matters - we want the right person toĀ have the right access to theĀ right thing. Authorization is not to beĀ confused with Authentication, which is about verifying a user's identity.Ā
ReBAC is an alternative model to other common ones - such asĀ Role Based Access Control (RBAC) andĀ Attribute Based Access Control (ABAC). Note that these models are more thinking tools than concrete guidelines, and most applications end up mixing between them (especially as time passes and the applications evolve). Itās up to developers to pick the most suitable authorization model for their application at each moment in time.Ā
In this blog, we will learn how to implement ReBAC withĀ Open Policy Agent - an open-source policy engine for controlling access to systems and resources. OPA allows separating policy logic from application code, enabling easy policy management and updates without requiring code changes or deployments.
What is ReBAC?
ReBAC is a policy model focused exclusively on the relationships, or how resources and identities (aka users) are connected to each other and between themselves. The consideration of these relationships allows us to create authorization policies forĀ hierarchical structures.
ReBAC allows us toĀ deriveĀ authorization policiesĀ based on existing application-level relationships. Creating policies based on relationships, rather than roles or attributes, saves us from having to create authorization policies on a per-instance basis.Ā
To better understand how ReBAC works, letās look at two of the most common relationship types ReBAC allows us to handle:Ā
Parent-child hierarchies are relationships where resources areĀ nestedĀ under other resources. This allows us to create a policy such as: AĀ userĀ who isĀ assigned the role of anĀ OwnerĀ on aĀ folderĀ will also get theĀ OwnerĀ role on everyĀ fileĀ within thatĀ folder.Ā We can see that the user's access to the files is derived from the combination of two elements: TheĀ nestingĀ of the files within the folder and theirĀ roleĀ on the folder.Ā
Organizations are relationships based onĀ groupingĀ togetherĀ users. This allows us to create a policy like:AĀ userĀ who isĀ assigned the role ofĀ MemberĀ on RnD TeamĀ will also be assigned the role ofĀ EditorĀ onĀ RnD File when theĀ RnD Team is theĀ ParentĀ ofĀ RnD File. Putting several users in one group allows us to derive policies based on their group membership, instead of per individual user.
A more in-depth review of ReBAC, including examples and implementation instructions, is availableĀ here.
Why use Open Policy Agent (OPA) for ReBAC policies?
The initial setup of policies for each individual service requires manual configuration within the service itself. As your policies, users, and services expand, managing updates across all relevant services becomes tedious and time-consuming.
Mixing the authorization layer's code with that of the application itself also creates difficulties when upgrading, adding capabilities, and monitoring the code as it is replicated across various microservices.
OPA allows us to create a separate microservice solely dedicated to authorization,Ā effectively decoupling policies from the core application code. Controlling access management centrally through a separate authorization service allows you to offer it as a service to every system that needs to check whether a user can or cannot access its resources.
OPA unifies all policies across each individual service in one server.
It takes on the role of policy decision-making and enforcement from the service:Ā The service queries OPA, OPA makes a decision, sends an output to the service, ā and the service acts according to OPAās reply.Ā
It allows you to have a policy as code that can be easily reviewed, edited, and rolled back.
How to implement ReBAC in OPA?
In order to implement a ReBAC model using OPA, we will need to follow several steps:
First, we must map out the policies we want to enforce. This will help us better understand what we want our authorization layer to accomplish from a functional perspective.Ā
Map out the specific building blocks of our implementation - Resources, Actions, and Resource Roles.Ā
Map out our Relationships and Role Derivations. These will allow us to create ReBAC policies.Ā
Create a data file that will contain all of the relevant policy information in Rego
Write Rego code that checks access based on our created policy via theĀ `graph.reachable()` function.
Ā
How will our Rego code work:
Here's our ReBAC Rego code:
# return a full graph mapping of each subject to the object it has reference to
full_graph[subject] := ref_object {
some subject, object_instance in object.union_n([files, teams,organizations])
# get the parent_id the subject is referring
ref_object := [object.get(object_instance, āparent_idā, null)]
}
# ⦠see full Rego code at https://play.openpolicyagent.org/p/4qkNc0GtPP
# rule to return a list of allowed assignments
allowing_assignments[assignment] {
# iterate the user assignments
some assignment in input_user.assignments
# check that the required action from the input is allowed by the current role
input.action in data.roles[assignment.role].grants
# check that the required resource from the input is reachable in the graph
# by the current team
assignment.resource in graph.reachable(full_graph, {input.resource})
}
The full rego code is available in the OPA Playground
The code builds a full graph based on the teams, files, and teams.
Iterates over the roles and teams assigned to the user by which the access request was made.Ā
Using the `graph.reachable()` function, it checks the graph to see if the user has the appropriate team and role assignment to access the requested resource.
If there is a correlation between the user team assignment, the file team assignment, and the role, the request is allowed (allow rule returns true). Otherwise, the request is denied by default.
*reach / reachable - means that the file node in the graph is linked to the team node in the graphĀ
Itās important to note that `graph.reachable()` is a built-in function available in OPA since version v0.20.0.
Letās go over these steps with a concrete example -
ā ā ReBAC with OPA demo application:
Take an organizationās file system. The organization consists of two teams, with each team having a list of files associated with it. Apart from that, we have a file within one of the teams associated with a specific user.Ā
Here is a visual representation of the relationship policy we wish to set up:

To set up our ReBAC policy as Rego code, we will need to follow these steps:Ā
1. Map out the policies we want to enforce:
Every user part of the āAcmeā organization should have āViewā access toĀ every instance within the file system.Ā
A user assigned as theĀ Admin of a team should have āAdminā access toĀ every file associated with their team.
Tim, the companyās designer, should have āAdminā access to ālogo.psdā.
Ā
2. Mapping our Application Resources and Actions
Letās map out all of the resources we require in our application, as well as the actions that can be performed on each resource:Ā
Organization: View
Team: View, Edit.Ā
Files: View, Edit.
Ā
3. Mapping our Resource Roles
In ReBAC, roles are not system-wide entities assigned to users (Like in RBAC). ReBAC requires us to set up roles per resource. This means that every single one of the resources we previously defined is going to have roles associated with it. Here are the roles we will have to associate with the resources in our demo application:
Organization: Member (View)Ā
Team: Admin (View, Edit), Teammate (View)Ā
Ā Files: Admin (View, Edit), Teammate (View)
4. Mapping our Resource Relationships
Now, itās time to define the relationships between all of our resources. This will allow us to create authorization policies based on these relationships later on:
Marketing Files are nested under the Marketing Team.Ā
RND Files are nested under the RND Team.Ā
Both teams are nested under the āAcmeā organization.
Ā
5. Deciding on our Role Derivations
If a user is a Member of the organization, they should have the viewer role on every resource nested under the organization.Ā
If a user is the Admin of a team, they should receive an admin role on every file instance in that team.
Ā
6. Letās take a look at our users, Sally and Tim:
Sally:Ā
Is part of theĀ Acme organization
Has theĀ organizational role of `viewer`. Based on our derivations, she should be assigned the role of a viewer to every file within the organization.Ā
Is part of theĀ RNDĀ team
Has the team role of `admin`- Based on our derivations, she should receive the admin role on any file associated with theĀ RNDĀ team.
Ā
Tim:Ā
Is part of theĀ Acme organization
Has theĀ organizational role of `viewer`. Based on our derivations, he should be assigned the role of a viewer to every file within the organization.Ā
Is part of theĀ MarketingĀ team
Has theĀ adminĀ role over the file `logo.psd`, him them to perform Admin level actions on this specific file.
Ā
Policy Data:
Letās see how the data for this policy looks in Rego. TheĀ dataĀ for this policy will consist of five parts:
The organization: In our case, Acme, is the parent organization under which we have several teams. In a more complex scenario, we can have several different organizations with a more detailed hierarchy.Ā
{
ā
"organizations": [
{
"id": "acme"
}
ā
}
Teams:Ā a list of all available teams. Both files and users will be associated with their respective teams:
{
"teams": [
{
"id": "rnd",
"parent_id": "organization:acme"
},
{
"id": "marketing",
"parent_id": "organization:acme"
}
}
Roles: a list of all possible user roles relevant to the policy, along with the scopes for each role:Ā
{
"roles": {
"admin": {
"grants": [
"view",
"edit"
]
},
"viewer": {
"grants": [
"view"
]
}
}
Users: a list of our users, and their ids (For the purposes of this example, the ids are a combination of a user's team association and allocated role. In an actual scenario, these ids can be a user's email, a unique GUID, or whatever you choose it to be) and their assigned roles and teams:Ā
{
"users": [
{
"assignments": [
{
"resource": "organization:acme",
"role": "viewer"
},
{
"resource": "team:rnd",
"role": "admin"
}
],
"id": "sally"
},
{
"assignments": [
{
"resource": "team:marketing",
"role": "viewer"
},
{
"resource": "file:design.psd",
"role": "admin"
}
],
"id": "tim"
}
]
}
Files: a list of all the files relevant to the policy, along with their āteam_idā, which associates them to a specific team:
{
"files": [
{
"id": "backend-readme.md",
"parent_id": "team:rnd"
},
{
"id": "frontend-readme.md",
"parent_id": "team:rnd"
},
{
"id": "gateway-config.yaml",
"parent_id": "team:rnd"
},
{
"id": "website.js",
"parent_id": "team:marketing"
},
{
"id": "blog-1.pdf",
"parent_id": "team:marketing"
},
{
"id": "logo.psd",
"parent_id": "team:marketing"
}
}
Now that we have our data established, letās decide on the access policy we want to check for.Ā
In this example, letās check if `sally` can `edit` `gateway-config.yaml`.Ā
To perform this check, this will be ourĀ input:
{
Ā Ā Ā Ā "user": "sally",
Ā Ā Ā Ā "action":"edit",
Ā Ā Ā Ā "resource":"file:gateway-config.yaml"
}
This results in the response youād expect: True.
Congrats! You have successfully implemented ReBAC in OPA!Ā A full demo repository of this Rego exampleĀ is available here.
Letās go over what each part of the code and what it means in more detail:
resource_instances and teams rules: These rules create sets of resources and teams by their ids, respectively. They iterate over the
data.resourcesanddata.teamssets and extract the id of each instance. The result is a set where each item is the id of a resource or team.full_graph rule: This rule constructs a graph where the nodes are subjects (either a resource or a team), and the edges are references to other objects in the graph. The graph is represented as a dictionary where the key is the subject id, and the value is the parent id of the object itās referring to.
users and input_user rules: The
usersrule creates a set of users by their ids. Theinput_userrule then uses the user id from the input to get the userās details from this set.allowing_assignments rule: This rule filters the assignments of the
input_userto find those that allow to perform the requested action on the specified resource.Ā It does so by checking two conditions:The requested action is in the list of actions granted by the role associated with the assignment (using the
data.roles[assignment.role].grantsexpression).The team associated with the assignment can reach the requested resource in the graph. This is done using the
graph.reachable(full_graph,{input.resource})function, which presumably checks if there is a path from the assignmentās team to the resource in thefull_graph.
default allow and allow rules: These are the main decision rules of the policy. The
allowrule will be true if there is at least one assignment that allows the action on the resource (as determined by theallowing_assignmentsrule). If there are no such assignments, thedefault allowrule sets the decision tofalse.
Scalable Implementation
As application requirements evolve, the need to shift from simple authorization models to ReBAC can arise rapidly. Implementing and managing such complex authorization systems can be challenging for developers and other stakeholders, potentially leading to bottlenecks and inflexibility.
Setting up a system as complex as ReBAC could take months of work, which doesnāt end at the point of implementation - as creating additional roles, attributes, and policies requires complex R&D work and steep learning curves.Ā
The solution is implementing and managing your RBAC, ABAC, or ReBAC policies using an authorization service that allows for flexible transition between authorization models and provides a simple no-code UI that makes permission management accessible to other stakeholders.Ā
Thatās where Permit comes in -Ā
Permit.io: ReBAC with a no-code UI
Permit provides developers with a permission management solution that allows for both smooth transitioning between RBAC, ABAC, and ReBAC without any changes to your application code, and the ability to create and manage policies using an accessible no-code UI.Ā
Permitās UI generates Rego code forĀ RBAC,Ā ABAC, andĀ ReBAC, wrapping it nicely into Git, and API / UI interfaces which you can edit, add to, and manage however you like.
This allows both developers and other stakeholders to set up ReBAC policies and add complex graph hierarchies - all without having to write a single line of code.Ā
Permit ReBAC is easily approachable via the low-code policy editor. You can easily define the relations (graph edges) between the resources (graph nodes) with a few clicks, and thereās no need to learn a complex schema language to begin. You can check out how ReBAC can be implemented by using Pemitās no-code UIĀ here.
Implementing authorization with Permit ensures that everyone is included in the permission management process, preventing developers from becoming bottlenecks while also allowing a smooth transition between RBAC, ABAC, and ReBAC.
Want to learn more about Authorization? Join ourĀ Slack community, where there are hundreds of devs building and implementing authorization.
Written by

Daniel Bass
Application authorization enthusiast with years of experience as a customer engineer, technical writing, and open-source community advocacy. Comunity Manager, Dev. Convention Extrovert and Meme Enthusiast.


