SaaS applications typically employ role-based access control (RBAC) to establish access permissions, enforce authorization rules, and manage relationships across multiple tenants and entities. Each user is assigned a unique role within the system, and this role is associated with a specific set of permissions and restrictions that determine what the user is allowed to access or do with resources.
However, as large microservices or multi-tenant applications scale, access control extends beyond the basic roles and permissions that RBAC provides. Instead, these kinds of systems rely on a multitude of granular attributes and conditions sourced from various data stores. This is where fine-grained access control (FGAC) models like ABAC and PBAC come into play.
For example, let’s consider the intricacies of implementing access control in a microservices-based e-commerce application similar to Amazon. Each service in such an architecture may expose its own set of resources and operations, and the access policies for each service may vary, depending on the scenario.
Assuming we have the following services:
For a customer to successfully place an order and for it to be shipped, each service has its unique set of access policies that must be strictly met. For instance:
In such systems, you can’t rely on RBAC to implement these complex access requirements. These kinds of systems tend to grow exponentially, and introducing more roles as a solution to granular access requirements ultimately leads to role explosion, where it becomes almost impossible to manage large numbers of roles and permissions.
Attribute-based access control (ABAC) empowers engineering teams to implement granular and contextual access management policies by incorporating a wide range of attributes and conditions. This flexibility complements the static roles and permissions offered by RBAC and enables systems to enforce access control decisions based on a comprehensive understanding of the user or service making a request, the resource being accessed, and the context surrounding the request.
In RBAC, there’s an inherent risk that users may be able to unintentionally acquire elevated permissions beyond their designated scope. This complexity arises when different roles have similar responsibilities, leading to potential overlaps in their access privileges.
These overlaps can be intentional, allowing for more granular permissions, or they can be an unintentional consequence of poor RBAC design. Regardless of how it happens, these overlaps create the risk of users in one role having access to resources or performing actions intended for another role.
When users in different roles end up with similar or identical permissions, it undermines the fundamental principle of least privilege and attackers could exploit this permission leak to execute unauthorized actions that they would not have been able to do otherwise.
For example, let’s consider two predefined roles at Github, the version control and collaborative software development platform: (i) Organization Owner and (ii) Member.
These roles have overlapping permissions in that:
If we decide to leverage RBAC alone to grant the Member role these privileges to be able to manage repositories, then it means the Member role will be able to manage all the repositories in the organization, just as the Owner role.
However, if we want to limit the Member role to only clone and push changes to repositories they own or have been granted access to by the repository owner, we’d have to leverage ABAC attributes to implement this kind of granular permissions.
The actions that can be performed on a repository are attributes of that repository. As such, we can use this resource attribute, alongside user attributes like predefined roles, and even contextual attributes if necessary, to determine the actions that any Member can perform on a repository.
This allows the Organization Owner to retain unlimited control over all repositories, while Members’ permissions are limited to cloning and pushing changes only to repositories they own or have been granted access to.
In essence, GitHub’s ABAC empowers organizations (tenants) to tailor access control based on their unique requirements. This granular control allows organizations to define permissions based on factors such as code sensitivity, user roles, experience level, or even team structure. This helps to ensure that code changes and deployments are properly reviewed and approved, which in turn helps to maintain code quality and alignment with organizational workflows and security policies.
Contrary to popular assumptions, ABAC isn’t a replacement for RBAC. Rather, ABAC reinforces the traditional security standards and principles, such as data abstraction, least privilege, and separation of duties, which you must have already implemented or enforced via RBAC.
Your RBAC framework uses the principle of least privilege to ensure that roles and permissions only grant the minimum level of access required to complete a specific function within the system. On the other hand, the principle of separation of duties enforces restrictions on critical parts or functions of your system, ensuring that no single user has complete control over sensitive operations.
ABAC then extends the capabilities of these existing roles and permissions by introducing attributes to how your system makes contextual and smart access restrictions/decisions. This eliminates the need for overly broad and inflexible roles and provides more scalable control over who can access what, when, where, and why.
Policies are defined by attributes, so administrators don’t need to create a separate policy for each role. Instead, a single policy can be defined using multiple attributes and then applied to any role that requires it.
Administrators can modify a user’s access permissions without having to reassign new roles to that user. They can do this by updating the user attribute values of any policy with the relevant attributes associated with the user. This way, when next the user makes a matching request, the necessary policies will be automatically applied to that user based on the updated attribute values. This makes it easy for engineering and/or DevSecOps teams to keep policies up-to-date and ensure that they’re aligned with evolving business requirements.
At its core, ABAC policies use dynamic boolean functions to reference attributes that determine whether a user or NPE is authorized to perform an action, as defined by the organization’s security policies.
Extensible access control markup language (XACML) is used to write the conditions, rules, and policies that govern access decisions in ABAC architectures, as specified by OASIS (Organization for the Advancement of Structured Information Standards).
XACML policies are made up of rules that define how a given request should be authorized or denied. These rules are composed of conditions and actions — the conditions specify the criteria that must be met for the action to be taken, and the actions specify what should happen if the conditions are met.
Rules in XACML are organized into policies, and policies are further organized into policy sets. Each policy and policy set within a policy configuration includes a target condition. The target condition is built using attribute names and values, and functions as a basic conditional statement, determining whether the policy or policy set can be evaluated for a specific request.
If the target of a policy or policy set evaluates to true, the policy or policy set is said to apply to that request, and subsequent policy rules will be evaluated in the order they are listed in the policy configuration. By carefully crafting the target condition, administrators can ensure that policies are only applied to the requests that they are intended for. The security of an ABAC system relies heavily on proper attribute management.
XACML, however, is quite verbose, and writing policies in it can be boring and hard to debug for most developers. While XML has historically been the standard format for XACML policies due to its defined structure and support for complex data types, JSON is gaining traction as an attractive alternative thanks to its lighter weight and performance benefits, especially in modern web applications. The latest version of XACML (3.0) now officially supports both XML and JSON as request and policy formats.
While XACML is still the most widely used ABAC standard, newer ABAC architectures are now relying on simpler languages and formats that are similar to XACML to write policies within a newer framework that’s now commonly referred to as policy-based access control (PBAC).
Attributes are sets of labels, properties, or key-value pairs that can be used to identify all the entities that must be considered for an ABAC authorization decision. These entities include the Subject (who is requesting access), the Object (what is being accessed), the Action (what is being done to the object), and the Environment (the context in which the request is being made).
In ABAC, administrators create access control policies using a combination of attributes. These policies define the allowable operations for specific combinations of subject, object, action, and environment attributes. If the attributes associated with a request satisfy the predefined conditions of a policy, the request is granted. However, if there are no matching attributes, access is denied.
Subject or user attributes describe the human user or autonomous entity that’s attempting to access a resource within an ABAC system. These subject attributes can include user ID, role, username, department, or any other relevant attributes that can be used to identify the requesting entity. For example, a user’s role might be “billing manager,” “employee,” or “contractor,” while their department could be “Engineering” or “Business Operations”, you get the gist.
In any ABAC system, each subject must be assigned specific attributes that will characterize it during authorization. However, system administrators must be the sole entity responsible for assigning or asserting attribute values to these subjects. Also, subjects must not be allowed to alter their security clearance, as clearance attributes dictate the level of access rights granted to a subject.
Object or resource attributes define the characteristics of the target entities that a subject seeks to access. These attributes could include properties associated with different kinds of resources such as tables, databases, networks, servers, tenants, or microservices, where data storage or retrieval happens.
All objects within an ABAC system must be assigned specific attributes that characterize them during decision-making. These object attributes can include the creation date of a resource, its directory path, owner, format, or even sensitivity level. To maintain integrity, object attributes must not be modifiable by the subject, as this could potentially be used to manipulate the authorization decision.
Also, every object must have at least one policy that outlines the access rules for authorized subjects, permissible operations, and relevant environmental conditions for applicable requests.
Action attributes define the operation that a subject intends to perform on a resource or object. These actions can include read, write, modify, or delete operations, and can be used individually or in combination with each other to define operations in more complex scenarios.
Environment attributes capture the underlying context of a subject’s request within ABAC. These attributes are independent of subject or object attributes and can include conditions like the time of a request, the device type from where the access attempt is originating, or even the encryption protocol that’s securing the connection.
Unlike subject and object attributes, environment attributes aren’t assigned administratively. Rather, they are inherently detectable by the ABAC system.
The XACML standard extends beyond defining the policy language for ABAC alone. It also defines the specifications of a standard ABAC architecture, detailing how requests, policies, rules, and attributes collectively operate within a given environment. In such an environment, these components must function together to ensure seamless decision-making and policy enforcement.
A typical ABAC architecture that follows the XACML standard has five main components that must function together:
It’s worth noting that in ABAC systems where policies are written in custom policy languages like OPA (Rego), certain components like the PRP and PDP may be absent as the policy agent directly handles those functions.
The policy enforcement point (PEP) intercepts and evaluates requests made by subjects to access protected resources (objects) within an ABAC system. When a subject attempts such an access, the PEP captures this request and transforms it into an XACML request.
The XACML request generated by the PEP encapsulates various attributes, including those of the subject, the resource, the intended action, and any relevant contextual attributes. Once the XACML request is constructed, the PEP forwards this request to the PDP for evaluation and waits for it to make a decision before enforcing it.
The PEP enforces the PDP’s decision by either granting or denying the subject’s access to the requested object.
The PDP acts as the central decision-maker within an ABAC system. It evaluates requests from the PEP and determines if the requesting subject has the necessary permissions to access a desired resource and perform the intended action.
Whenever the PEP forwards an XACML request, the PDP evaluates the attributes associated with that request. If all the required attributes and policies are available and the request aligns with these policies, the PDP issues a “permit” decision, authorizing the subject to proceed with the requested action.
However, if the PDP identifies any missing attributes or policies critical for determining the response, it initiates additional steps to obtain the required information. In such cases, the PDP may invoke the policy retrieval point (PRP) to retrieve the missing policies or the policy information point (PIP) to obtain the missing attributes. These components work together to ensure that the PDP has all the necessary data to make an accurate and informed authorization decision.
Once the PDP has evaluated all the relevant factors and obtained the necessary information, it returns a final decision to the PEP. This decision can either be a “permit” or “deny” response. The PEP, upon receiving the PDP’s decision, enforces it by granting or denying access to the requested resource accordingly.
The policy information point (PIP) functions as a centralized hub for retrieving attributes that are necessary for the evaluation of a policy.
For the PDP to make informed decisions, it calls out to the PIP to gather the subject and context attributes that are relevant to a particular request. In cases where the attribute store lacks the necessary attributes for evaluation, the PIP can connect to other data sources to retrieve the required attributes, depending on the system’s design.
However, the PIP may need to map or convert these attributes into compatible formats that can be understood and utilized by the PDP.
The policy retrieval point (PRP) serves as the centralized repository where all policies are stored within ABAC. This consolidation provides a single source of truth for PDPs to retrieve policy information when making authorization decisions.
As policies change to align with evolving business needs or regulatory requirements, the PRP seamlessly integrates and updates the latest policy versions that it receives from the policy administration point (PAP). This ensures that PDPs consistently have access to only the most up-to-date policy information every time.
The policy administration point (PAP) is responsible for deploying policies to the PRP for storage or directly to the PDP for decision-making, depending on the architecture. Notably, some architectures may merge PAP and PRP functionalities to streamline their policy deployment process.
The PAP empowers administrators or policy authors to define new ABAC policies, modify existing ones, and even delete redundant policies when necessary. It also manages policy versioning by providing administrators with a comprehensive audit trail of all modifications made to policies. This historical context enables administrators to understand the evolution of all policies within a system, identify trends, and pinpoint the source of any underlying errors that may arise. Administrators can choose to revert to older policy versions when necessary.
The PAP performs rigorous testing and debugging to identify syntactic or semantic inconsistencies within policies. This ensures that all policies are well-defined and adhere to XACML standards. The PAP also tests policy interactions to detect potential conflicts that may arise when multiple policies are applied simultaneously.
Before now, Uber relied on a hybrid PBAC + RBAC setup to secure its microservices architecture. While this system was functional, it lacked the flexibility and granularity required to handle Uber’s evolving authorization requirements.
In the previous PBAC architecture, access control policies were managed centrally by a policy service. This service assumed the combined roles of a policy administration point (PAP) and a policy retrieval point (PRP) as defined in traditional XACML-ABAC architectures. Administrators could define policies within this centralized policy service in YAML format, and this same service was responsible for distributing relevant policies to the various microservices.
In this same PBAC architecture, there was no centralized decision-making engine or service like the policy decision point (PDP) found in XACML-ABAC. Instead, each microservice independently assessed and applied authorization policies by invoking authorization APIs from a local authorization library, which essentially served as their PDP.
While this basic policy framework was adequate for straightforward use cases, it fell short in nuanced scenarios that required a more fine-grained authorization. For instance, if an Uber customer service rep needed to retrieve payment-related information of customers residing in a specific country or city, their existing YAML policy syntax couldn’t provide additional information beyond the payment profile identifier (UUID).
As such, the engineering challenge was to build a dynamic and context-aware access policy system. This new infrastructure needed to be able to evaluate access requests based on attributes from diverse information systems, instead of relying solely on roles or identifiers.
To address this challenge, Uber’s engineering team adopted Google’s Common Expression Language (CEL) to write policy conditions based on attributes. These policy conditions were then stored in an expression engine. CEL is an open-source language that can be used as an alternative to XML for writing ABAC policies.
They also introduced an attribute store to house all the attributes referenced in the expression engine and implemented a policy information point (PIP) to manage attribute retrieval from the store. This attribute store serves as a repository where consumer services can access and retrieve attributes tailored to their unique business use cases.
In Uber’s revamped ABAC architecture, the authorization engine must obtain attribute values from the attribute store based on the conditions specified in a particular policy. Subsequently, the conditions and attribute values are then forwarded to the expression engine, which evaluates them and returns a boolean result.
Uber currently uses this ABAC architecture to orchestrate authorization for more than 70 microservices. This architecture also secures other critical infrastructure components, such as Kafka, which plays a crucial role in managing distributed communication among these services within Uber.
ABAC architectures can be complex to design and maintain, especially for small engineering teams with limited resources and experience. While ABAC allows for more dynamic and contextual decision-making, this flexibility comes at the cost of a more complex implementation when compared to RBAC.
To implement ABAC, organizations have to manually define attributes, assign them to each entity, and map these attributes to specific rules and policies within the system. This ABAC mapping process can be very time-consuming and error-prone, especially for large organizations with complex development environments and business models.
On the other hand, XACML policies are complicated to write. Orchestrating these policies requires converting every possible access request into XML, which is highly sensitive to syntax. As such, making future modifications to attributes, rules, policies, or policy sets can turn into a painful task.
Implementing ABAC truly requires significant time and resources. However, considering the authorization flexibility it provides, particularly within large architectures, the investment is worthwhile in the long run.
We’ve successfully explored the good and granular parts of ABAC. However, ABAC isn’t a “must-have” framework. It’s what you implement as your infrastructure scales and the authorization need arises.
For new engineering teams, you’ll almost always have to deploy a set of roles and permissions in a typical RBAC architecture and test these roles in production with live users, before leveraging ABAC to cover up the loopholes that RBAC can’t fill.
Moreover, even multi-tenant SaaS applications that may need to implement hybrid architectures from the get-go still have to implement RBAC, before going ahead to use these RBAC roles as attributes in ABAC.
Want to see how you can implement RBAC with Stytch? Check out our documentation and sign up for a free developer account to start building with our easy-to-integrate APIs today. If you have any questions, please reach out to us at support@stytch.com or schedule a chat with a member of our team.