RBAC Best Practices with Open Policy Agent

Amit Kanfer January 22, 2021
RBAC

Whether they realize it or not, almost every company today is a software company as they build and re-architect a growing number of applications on a daily basis. Securing those applications and workloads requires implementing solid authentication and authorization. Authentication is responsible for verifying the identity of  the requester, while authorization is responsible for putting guardrails around what this identity is entitled to do within the application or resource.

The two pillars of enforcing access control in applications are through Role Based Access Control (RBAC) and Attribute Based Access Control (ABAC). Accordingly, the first part of this blog series will focus on RBAC while the second will concentrate on ABAC.  

This post will explain the basics of RBAC and best practices, including how to implement a solid RBAC framework at the very beginning of a program’s lifecycle. Using a hypothetical use case of a management system, we will demonstrate how to enforce policy in application with almost zero code changes.

What is RBAC

RBAC Schema users, roles, permissions

RBAC is a mechanism that manages access controls on the basis of user roles. RBAC administrators determine the access privileges of each role, such as whether a role can only view and read information. A more privileged role may also be able to make necessary changes or even delegate permissions to other users. Under RBAC, the permissions attached to a role define what actions those users can take.

Access level can be influenced by the seniority of a user or by whether the asset they are accessing is critical to their everyday work. When using RBAC, it’s always best practice to restrict user access to resources that are absolutely required in order to limit the risk of data leaks.

In other words, users should only have access to the systems and materials strictly needed to carry out their jobs—and nothing more—in order to minimize the risk of an asset getting compromised.

The National Institute of Standards and Technology (NIST) defines four subtypes of RBAC:

  • Flat: All employees have at least one role that defines permissions, but some have more than one.
  • Hierarchical: Seniority levels define how roles work together. Senior executives have their own permissions, but they also have those attained by their underlings.
  • Constrained: This introduces the separation of duties and allows several people to work on one task together. This helps ensure security and prevent fraudulent activities.
  • Symmetrical: Role permissions are reviewed frequently, and permissions change as the result of that review.

Later in this article I’ll describe how we can achieve the hierarchical subtype using Open Policy Agent as it’s the most popular and common type.

Benefits of RBAC

While RBAC lacks the multi-dimensionality of ABAC, its myriad advantageous benefits for managing access permissions have kept it a cornerstone of IAM. These are as follows:

  • Increased efficiency
  • Lower risk of data breaches
  • Regulatory compliance
  • Lower costs

A key advantage of RBAC is that it’s highly efficient. Security teams can add new roles and edit existing ones quickly, allowing them to onboard new staff and manage user access with ease.  Moreover, controlling access to sensitive data is critical for better mitigating the risk of data breaches.

This increased efficiency also helps drive compliance, as it enables teams to verify that privacy of sensitive data. It also helps maintain better visibility into how fellow employees interact with data which is vital to demonstrating compliance to regulatory controls. This is invaluable for remaining compliant with industry regulations.

RBAC can also be a cost reduction tactic by limiting access to certain resources that may have usage costs associated with them. For example, if preventing employees from accessing a bandwidth-intensive application can better preserve other resources like network bandwidth.

Limitations of RBAC

While RBAC offers many benefits, it’s still an imperfect best practice. Some of its challenges include:

  • Role explosion
  • Complexity
  • Scalability
  • Security

Role explosion is surely one of the biggest RBAC issues bogging down security teams as the amount of applications and users only grow. Environments with many roles with unique permissions are difficult to manage in order to work effectively. This is where the automated nature of ABAC stands to make it a better alternative.

While RBAC can be efficient, its lack of automation makes it comparably harder to manage than ABAC. It becomes even more difficult to manage when administrators add roles to users without removing them. It’s not uncommon for users to end up with multiple roles and permissions all of which need to be proactively managed or they can easily spiral out of control.

As companies onboard new hires, scaling RBAC will only grow more complex and time consuming as teams will have to manually define new roles for each hire.

What is Open Policy Agent

The Open Policy Agent (OPA, pronounced “oh-pa”) is an open source, general-purpose policy engine that unifies policy enforcement across the stack. OPA provides high-level declarative language that lets users specify policy as code and simple APIs to offload policy decision-making from their software. OPA can help enforce policies in microservices, Kubernetes, CI/CD pipelines, API gateways, and more. Its key lies in decoupling policy decision-making from policy enforcement: whenever application must act on policy decisions, it can simply query OPA and supplies structured data (e.g., JSON) as input. 

OPA flow

https://www.openpolicyagent.org/

OPA generates policy decisions by evaluating the query input against the policy and data. 

Dot-notated syntax for permissions 

A word about permissions. 

Permissions are simpler to manage in dot notation syntax. For example:

<resource-A>.<sub-resource-B>.<action>

The above means that each part of the permission is a resource or a sub-resource, and the last part is usually the requested action (view / update / delete / create / etc`). The format also provides a simple and implicit hierarchy. For example, whoever has billing.invoice implicitly has billing.invoice.create and also billing.invoice.delete.

Why OPA is the smart choice

Simple applications without complex RBAC  and ABAC models are better off with policy written directly into their codes. Where policies are to be enforced across the cloud native stack, on multiple applications and / or gateways and / or clusters of microservices — OPA will yield the most performant and straightforward policy-making.

Here’s a comparison of the 3 approaches, hardcoded vs. centralized service with OPA vs. distributed service with OPA:

Hardcoded vs OPA table

Putting it all together…

When a developer is given the task to deploy access control to an application (either RBAC or ABAC), the best practice requires:

  1. Working with the product team and defining required roles and the permissions for product roles.
  2. Mapping all application resources (usually external APIs) and attaching the required permissions for each and every resource.
  3. Modeling roles and permissions, the hierarchy between them (if there is any) and keeping the mapping option available.
  4. Implement the ability to attach one or more roles to each user of the application.
  5. Enforce the required permissions upon every request to the application’s resources (backend and frontend) using a policy engine.
  6. Test! Fix! Repeat!

In the hypothetical example below, we’ll use a simple policy that extracts the user and resource information from a given request. We’ll also use roles and permissions mapping to decide whether the request shall be allowed or denied.

Let’s imagine we’re building an inventory management application for a pet store, using Java Spring for the backend and React for the frontend.

Step 1 – define the roles. After talking with the product team, 4 roles are needed: 

  • Owner – The owner of the account and tenant. Has access to all resources, including billing information, and ability to create users with any roles.
  • Admin – An administrator of the system, has access to all resources except for the API that provides the ability to create more administrator users.
  • User – A standard user of the system that requires access to all non-administrative resources and can change and modify settings as needed
  • Viewer – Similar permissions to the user role without the ability to make changes (view only).

Step 2 – Map all application resources and attach permissions to them

For modern applications that comply with the Open API initiative – we recommend mapping all application APIs to a swagger file. From there, security teams can comb through and decide which permissions are required to interact with them.

For example, an API adding a new pet to the store (eg. POST /pet) will have a corresponding permission called: pet.create. Meanwhile, the API that provides information for a specific pet, could be pet.view. In this case, the “pet” is our resource, and the last part of the permission string is its related action.

We must then attach required permissions to each and every relevant API. There are a number of ways to do this:

  1. Attach permissions to relevant APIs using decorators and/or annotation in the code itself. For example, it would look like this in Java:

This is a sound approach from a developer’s standpoint, as he or she has full control and visibility into the required permissions for each and every resource in the application. Moreover, the mapping between the roles and the permissions is out of scope for the developers, allowing security teams to change the logic w/o having to redeploy the code.

    2. Maintain separate mapping between resources and required permissions, decoupled from the code itself. For example:

 

resources.json

This JSON represents a dictionary between requested paths (noted as regular expressions) and their respective permissions. While this approach has benefits and may be appropriate for some cases, it’s more error prone and a bit too decoupled from the IMHO. It should therefore be avoided when building RBAC (or ABAC) for applications.

Step 3 – Model the roles and permissions

JSON offers an easy way to define the roles and permissions and happens to be the format that Open Policy Agent works best with:

roles.json

The above example demonstrates RBAC with hierarchy. Here, each role can point to other sub-roles and/or extra permissions. In future posts we will discuss how to visualize and manage these mappings.

4th step – Implement the ability to attach roles to users in the system

Many security teams desire the ability to add additional users. They must simply attach a role to an individual user.

5th step – Enforcement of requests using Open Policy Agent

When using OPA, there are multiple considerations to take into account when enforcing authorization policies on incoming requests:

  • Option 1 – the best option is a reverse proxy in front of the application (eg. Envoy) that supports external authorization decision making.
  • Option 2 – teams can also integrate with API gateways, such as Kong, AWS API gateway and more. Like proxies, API gateways typically offer an external authorization function.
  • Option 3 – teams can leverage Middlewares and/or SDKs within the application itself. There’s an entire ecosystem of integrations out there for ASP.NET, Python, Golang, Java Spring, node.js express and more. 

In all of three above considerations, the integrations intercept incoming requests to the API, query OPA for an allow/deny decision, and then enforce the decision by either returning 403 forbidden as a response or allowing the request to move forward.

It’s critical to deploy OPA as close as possible to the protected application, preferably on the same host, as authorization requests happen frequently and are latency sensitive.

The OPA policy in our case is straightforward:

OPA policy

Data and policies in the OPA playground for our hypothetical pet store are available here: https://play.openpolicyagent.org/p/FszPu3u7oS

Go ahead and play with it, change the user from “alice” to “bob” and see how the “allow” decision changes as well.

Conclusion

In this post we covered the basics of RBAC and how OPA can help developers author and enforce RBAC in their applications. In practice, OPA may be overkill for a single application. However, it becomes a life saver when working with microservice architectures, where similar policies (RBAC / ABAC) are to be enforced upon multiple applications using a wide range of technologies such as API gateway, proxies, service meshes and more. 

Our next post will cover ABAC basics and how OPA fits well into that space even more smoothly..

Subscribe to build.security’s newsletter

Keep up with the latest news on our authorization policy management platform