How to Implement Role Based Access Control (RBAC) 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.
For this purpose, we want to review a couple of authorization models (RBAC and ABAC), and then explain how (and why) you should implement them using Open Policy Agent (OPA) - which allows you to create a separate microservice for authorization, decoupling our policy from our code.
So why RBAC and ABAC?
RBAC and ABAC are the two most basic and commonly used authorization models, and they provide the baseline for most other complex and specific ones. Let’s start by getting to know them a little better:
What is RBAC?
Role-based access control (RBAC), is an authorization model used to determine access control based on predefined roles. Permissions are assigned onto roles (Like “Admin or “User”), and roles are assigned to users by the administrator. This structure allows you to easily understand who has access to what.
The combination of who (What role are they assigned?) can do what (What actions are they allowed to perform) with a resource (On which resources) is called a policy.
What is ABAC?
ABAC (Attribute-based access control), determines access based on a set of characteristics called “attributes”. Attributes include parameters such as a user’s role, security clearance, time of access, location of the data, current organizational threat levels, resource creation date or ownership, data sensitivity, etc.
You can learn more ABAC here.
Check out our tutorial on implementing ABAC in OPA
RBAC VS ABAC
The choice between RBAC and ABAC depends on the needs of your organization -
RBAC provides a rather simple solution for determining authorization and is usually sufficient for most organizations. In some cases, a more in-depth approach for authorization is needed in order to prevent unauthorized access - This is where ABAC comes in. While requiring more processing power and time, ABAC provides a more complex and detailed authorization method factoring a much greater number of variables.
In many cases, RBAC and ABAC can be used together hierarchically, with broad access enforced by RBAC protocols and more complex access managed by ABAC. That being said, it is important to choose relevant authorization methods tailored to your organization’s needs - so the authorization process is neither too simplistic nor too complex.
You can learn more about the decision between RBAC and ABAC here.
For the purpose of this post, we’ll assume you decided you want to set up your policies with RBAC.
The challenge of setting up policies with RBAC
After you decide to set up your policies using RBAC, it is important to note the challenges you’ll have to face along the way:
The set of policies for each individual service has to be manually set up inside the service itself. This can be kind of a pain to do - as the amount of policies, users, and services grows, updating them in each relevant service becomes super tedious and time-consuming. Not only that, but considering the fact that policies change all the time - they have to be at least somewhat fluid.
Another issue can come from having the code of the authorization layer mixed in with the code of the application itself. This creates a situation where we struggle to upgrade, add capabilities and monitor the code as it is replicated between different microservices. Each change would require us to refactor large areas of code that only drift further from one another as these microservices develop.
But how can we solve these challenges?
By creating a separate microservice for authorization, thus decoupling our policy from our 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. This can be done by using Open Policy Agent (OPA).
What OPA gives us?
- OPA unifies all policies across each individual service in one server.
Takes on the role of policy decision-making and enforcement from the service: The service queries OPA, OPA makes a decision and sends an output to the service, 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.
While we have a centralized authorization solution, the enforcement itself is still distributed - We have an OPA agent next to every microservice, providing decisions and enforcement with near-zero network latency. The OPA agents are distributed and can grow as the services scales.
How to implement RBAC in OPA?
In order to use RBAC, we need two types of information:
Which users have which roles
Which roles have which permissions
Once we provide RBAC with this information, we decide how to make an authorization decision; A user is assigned to a role and is authorized to do what the role permits.
For example, let us look at the following role assignments:
User (Who is performing the action) | Role (What is the user’s assigned role) |
|
|
|
|
|
|
And this role/permission assignment:
Role | Operation (What are they doing) | Resource (What are they performing the action on) |
|
|
|
|
|
|
|
|
|
|
|
|
In this example, RBAC will make the following authorization decisions:
User | Operation | Resource | Decision (Should the action be allowed, and why?) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
With OPA, you can write the following snippets to implement the example RBAC policy shown above:
package rbac.authz
# Assigning user roles
user_roles := {
"crab": ["admin"],
"bob": ["cook"],
“plankton”:[“villain”]
}
# Role permission assignments
role_permissions := {
"cook": [{"action": "read", "object": "secret_formula"}],
"admin": [{"action": "read", "object": "secret_formula"},
{"action": "write", "object": "secret_formula"}],
"villain": [{"action": "want", "object": "secret_formula"}]
}
# Logic that implements RBAC.
default allow = false
allow {
# Lookup the list of roles for the user
roles := user_roles[input.user]
# For each role in that list
r := roles[_]
# Lookup the permissions list for role r
permissions := role_permissions[r]
# For each permission
p := permissions[_]
# Check if the permission granted to r matches the user's request
p == {"action": input.action, "object": input.object}
}
Querying the allow rule with the following input:
{
"user": "plankton",
"action": "read",
"object": "secret_formula"
}
Results in the response you’d expect: False
.
Congrats! You have successfully implemented RBAC in OPA!
To sum things up:
Let’s do a quick review of what we learned:
RBAC is an authorization model based on predefined roles, while ABAC determines access based on a set of characteristics called “attributes”.
While RBAC provides a rather simple solution for determining authorization that fits most organizations, ABAC is a more complex and detailed authorization method factoring in a much greater number of variables.
It is important to choose an authorization model that fits your organization's needs so that it’s neither too simple nor too complex.
The main challenges of setting up policies with RBAC are the requirement to manually set up the set of policies for each individual service and having the code of the authorization layer mixed in with the code of the application itself. Both can be solved by using OPA.
OPA solves these issues by unifying all policies in one server, taking on the role of policy decision-making and enforcement from the service, and allowing you to manage policy as code.
We saw an example of how to successfully implement RBAC in OPA :)
Want to learn more? Join our Slack channel to ask questions and talk about 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.