How to Implement Attribute-Based Access Control (ABAC) Authorization?
- Share:
Introduction
Modern application development, which relies heavily on data-driven solutions, demands reliable access control mechanisms. Access control doesn’t end with who can access your application (That’s authentication) but also what they have access to once they are inside (Authorization) and, most importantly - in what context.
Attribute-Based Access Control (ABAC), which allows us to create highly granular access control policies, stands out as a highly effective approach to managing authorization.
In this blog, we aim to delve into the intricacies of ABAC and explain how it can be implemented efficiently in your application. Being focused on granularity and flexibility, ABAC is a great way to elevate your application's access control, ensuring the right individuals have the right level of access under the right conditions.
Why ABAC?
The ABAC model stands out in access control, primarily due to its granular control and adaptability. Unlike Role Based Access Control (RBAC), which relies on predefined roles, ABAC focuses on attributes - offering unparalleled granularity in making authorization decisions.
ABAC allows us to evaluate numerous characteristics on top of user roles, such as environmental conditions and resource specifics, thereby providing a dynamic, context-sensitive, and extremely fine grained approach to access control.
In its base, ABAC, like RBAC, relies on three basic components - subjects, resources, and actions. Subjects represent the users or entities requesting access; resources are the objects or data being accessed, and actions refer to the operations subjects intend to perform on the resources.
On top of that, ABAC utilizes attributes to enforce these permissions. Let’s see how -
Enforcing Permissions with ABAC
To enforce permissions in ABAC, the concept of a 'black-box' function is essential. This function, which serves as the Policy Enforcement Point (PEP), accepts the three core ABAC components (subject, action, and resource) as arguments. Its primary function is to communicate with the Policy Decision Point (PDP), where the actual decision-making process takes place. The PDP, equipped with the ABAC rules, completes the data with relevant application data and delivers a clear-cut decision: Allow or Deny.
An example of `check` function with all arguments as attributes -
const permitted = await permit.check(
// the user object
{
// the user key
key: "john@permit.io",
// just-in-time attributes on the user
attributes: {
location: "England",
department: "Engineering",
},
},
// the action the user is trying to do
{
type: "create",
},
// Resource
{
// the type of the resource (the resource key)
type: "document",
// just-in-time attributes on the resource
attributes: {
hasApproval: "true",
},
// the tenant the resource belong to
tenant: "companyB",
}
);
This segregation of duties, where policy and code are decoupled, is instrumental in ABAC. By separating the authorization logic from the application code, programmers are able to create a more maintainable, scalable, and cleaner system. The decoupling not only simplifies policy management but also enhances security by centralizing the decision-making process, thus avoiding the pitfalls of scattered and potentially inconsistent access control elements within the application code.
Policy as Code and ABAC
Policy as Code is an emerging approach that bridges between policy management and software development. It suggests that policy definitions, like application code, should be written, managed, and maintained in a version-controlled environment. This approach is especially advantageous when implementing ABAC, as it allows for systematic and repeatable deployment of policies across various environments - from development to production.
In ABAC, using Policy as Code transforms complex access control policies into manageable, auditable, and transparent elements. This alignment enables perfect alignment with continuous integration/continuous deployment (CI/CD) workflows, ensuring that policy changes are as rapid and transparent as code deployments.
Implementing ABAC as Code
Implementing ABAC as code can be achieved with tools like Open Policy Agent (OPA) and its Rego language and AWS’ Cedar. OPA’s Rego, for instance, is a high-level declarative language that allows us to specify policy rules that can interpret and enforce ABAC policies dynamically.
For example, a Rego policy might define that users with an "admin" role can perform "write" actions on "confidential" resources during "business hours".
allow {
some "admin" in user.roles
action == "write"
resource.level == "confidential"
ns.time in resource.business_hours
}
Similarly, Cedar, tailored for application-level authorization, uses a specialized language to define similar rules but with a focus on the application's specific contexts and needs.
Both these tools underscore the essence of Policy as Code in ABAC: turning complex, attribute-driven access control rules into structured, maintainable, and verifiable code artifacts.
Modeling and Implementing ABAC with Condition Sets
The initial reaction of many developers to the idea of implementing and maintaining ABAC is that it sounds basically impossible. People often note (As was the case in a Reddit conversation we had on the topic) that it is “The hardest security model to implement
".
Policy as Code, which we discussed in the previous section, is one of the best practices to make our ABAC implementation much easier. But it’s not the only one -
There are two approaches to handling attributes when modeling an ABAC rule - Unstructured (custom) Rules and Structured Rules.
Unstructured Rules:
The unstructured approach bundles all relevant attribute conditions into a collection of single policy rules, making decisions based on those conditions. While offering a fair bit of freedom, it often leads to complexities and inconsistencies, especially in large-scale applications.
Here’s an example of a policy rule where all the conditions are configured together -# EU employees can perform any action on GDPR Protected Document allow { # Lookup the user's attributes user := user_attributes[input.user] # Check that the user is an employee user.title == "employee" # Check that the employee is based in the EU user.location == "EU" # Check that the document is GDPR Protected document_attributes[input.document].classification == "GDPR Protected" }
Structured Rules:
The other approach of structured rules brings more clarity and order to the policy definition process. With structured rules, you first define two components separate from your policy - a User Set and a Resource Set. These sets bind existing policy components (Like a user or resource) with a specific condition, allowing you to create policies that use these pairs as components of their own.
In comparison to the unstructured rule, here’s an example of a policy rule with the conditions declared as resource and user sets -# Declare EU emplyees user set eu_employees { # Lookup the user's attributes user := user_attributes[input.user] # Check that the user is an employee user.title == "employee" # Check that the employee is based in the EU user.location == "EU" } # Declare GDPR protected resource set gdpr_protected { document_attributes[input.document].classification == "GDPR Protected" } # Allow only EU employees can perform any action on GDPR Protected Document allow { eu_employees gdpr_protected }
Using Structured Rules ensures each policy is explicitly tied to specific attributes of users and resources, making the rules both easier to manage and more secure and reliable. When policies are defined with this level of precision, they become simpler to update and maintain as the application evolves, ensuring that the access control mechanism remains robust and relevant.
Condition Sets allow us to simplify the creation of ABAC policies significantly by splitting them into User Sets and Resource Sets. The components of ABAC are simplified with the use of condition sets by following these criteria:
- Resource Sets - A set of conditions based on resource attributes on one side and any other attributes on the other.
- User Sets - A set of conditions based on user attributes on one side and any other attributes on the other.
Here’s a simple example of the use of resource sets. These include the document adhering to a certain compliance level or being of the current encryption level:
document is GDPR.compliannt
and document.encryption equals level.6
An example of a User Set, could be the location of a user or a users payment status:
[user.](<http://user.id>)location equals EU
and [user.](<http://user.id>)premium equals true
By taking these conditions and assigning action permissions to each, we get a very granular implementation of ABAC, in a way that is easy to maintain and audit. The only downside is the challenge of calculating action attributes in condition sets.
Source: app.permit.io
Sync Data and Policy Configuration
Another critical challenge in ABAC implementation is ensuring that every policy decision is made with the most complete and current attribute data. This challenge is not trivial, as attributes can reside in various parts of an organization's infrastructure and might not always be declared explicitly in the policies.
To address this challenge, a tool such as OPAL (Open Policy Administration Layer) can come into play. OPAL automates the synchronization between the policy store and the real-time data needed for policy decisions, ensuring that policies are always evaluated with the most up-to-date information.
OPAL achieves this through an event-driven method for policy syncing. It listens for changes in data sources, such as user attributes or resource characteristics, and automatically updates the policy store. This real-time updating ensures that the policy decisions are based on the system's current state, making ABAC both responsive and reliable.
Combining ABAC with Other Policy Models
While ABAC offers extensive flexibility and granularity, it's not a one-size-fits-all solution. There are scenarios where other models like RBAC (Role-Based Access Control) or DAC (Discretionary Access Control) might be more suitable, and understanding the limitations of ABAC is key. For instance, ABAC can become complex to manage in highly dynamic environments where attributes change frequently.
Integrating ABAC with other policy models allows for a more balanced and comprehensive approach to access control. Platforms like Permit.io facilitate this integration, enabling a blend of ABAC's attribute-based flexibility with the structured simplicity of RBAC. Using Policy as Code frameworks or backend services, developers can create a hybrid model that leverages the strengths of each approach, ensuring a scalable and manageable access control system.
Conclusion
Implementing ABAC is a journey towards more dynamic and context-aware access control in your applications. While the path involves intricacies and challenges, the destination promises enhanced security, flexibility, and scalability.
As you embark on this journey, remember to embrace Policy as Code and consider the integration with other policy models for a comprehensive solution.
Contributions to open-source projects like OPAL, which are vital in the authorization ecosystem, are invaluable.
You can support this project by giving it a star on GitHub and joining OPAL’s Slack community, where thousands of developers are building and implementing authorization.
Read More:
More ABAC-Related Resources are available here:
Written by
Gabriel L. Manor
Full-Stack Software Technical Leader | Security, JavaScript, DevRel, OPA | Writer and Public Speaker