Choosing the best access control strategy
Implementing data security within application can be done with various access control strategies. The access control determines if a given user is allowed to perform a particular action on a requested resource. Do they have access to see the data, make updates, or delete it? The answer to that lies within your access control policies. There are different ways to implement security each with its own benefits and detractors. Finding the right one for your application can be the difference between happy users with secure, private data, and a disastrous data breach.
Depending on the types of applications or services your product has, different models may be more or less viable. Below I'll discuss the prominent types as well as their benefits and detractors. For each of these it helps to have a concrete situation in mind. While it may not completely mirror your use case, an example better shows the difference.
This article references the document repository example to help explain the following concepts. It helps to take a quick review to better understand the core product concept.
Let's recall our common user stories for the document repository example:
There are many documents. Each document can have any number of users and each of those users may have different permissions to that document. Users will share documents and change other users’ permissions as well as create, modify, and delete documents. We’ll assume there are two services, one that stores text documents and another than stores complex binary data, text and binary.
Inline access policy (IAP)
Pros:
- The architectural design is simple, so once it is taken care, only the implementation is necessary
- Implementation is straightforward
- Since the architecture is simple, it is easy to reason about
- Access controls are saved in the same database which means the service exposing that data can retrieve both the document and permissions at the same time. A separate database isn’t likely necessary.
Cons:
- Duplicated implementation from scratch. Best case is to copy the implementation from another repository to use.
- No standard on optimizing access, no consistency for users. Accessing permissions via an API requires separate requests to multiple services. Those services might not have consistent APIs.
- Permissions cannot be shared across services
- Each service must create dedicated APIs to update and mutate the permissions
- Coupled access prevents changing permissions independent of changing the document since they are saved in the same database
- Users don't have the ability to understand what documents they have access to across services, because access policies are distributed
- Cannot not execute complex access patterns based on user attributes or granular conditions
Role-based access control (RBAC)
Role-based access control separates the responsibility of permissions from the documents. Roles are created and assigned to users. These assignments are stored in a separate service, so that any document application or service can access the role data. Checking user roles requires either your document service to make an api call to the role service or the list of roles is exposed via the user’s authentication token. Because the roles apply to the user only, permissions to individual documents do not exist in this paradigm. A user has access to all documents of type X or none of them at all.
Pros:
- Roles can be stored in your authentication service, every document repository can consume the roles from the tokens generated by the identity provider
- Scales with the creation of additional document repositories
- Reviewing the user in the identity provider reveals the full scope of roles the user has.
Cons:
- There is no de facto standard on the management of roles. Some patterns exists however.
- No ability to see what documents a user has access to given their role
- No way to grant granular access to documents.
- Permissions are attached to the access token, so role changes require expiry of previous token (~1 hour or longer).
- Roles may grant access to all documents of every type.
- Roles are ambiguous as to what they actually mean and give access to
- Users can only be given a finite number of roles due to storage patterns. As the role number increases so does bandwidth overhead of every request throughout your application. This is because the token contains all the roles, and that token must be reused for every call.
- Service clients cannot act on behalf of users, async interactions do not work as once the token expires so do the permissions contained in that token.
- We cannot define access at the resource level to permit sharing of documents between users. Documents cannot be shared between users.
Access control lists (ACL)
Access-Controlled lists flip responsibility around work from the document-first perspective. On a document you define an ACL (which can inherit lists from other places) which specifically allow/deny user actions on the document. Because these only apply to the document, it has the opposite problems from RBAC, most notably lack of understanding of what documents a user has access to. Given the lack of complexity and the fact all the policy has to be applied to the document in one place. The flexibility is extremely lacking.
Pros:
- Easily see the complete set of permissions for a document.
- ACL is stored in one place and can be edited inline
- Provides better control than RBAC to documents
Cons:
- No ability to identify which users have access to a document, unless they are specifically identified in the ACL.
- The max ACL is extremely limited in size.
- The larger the ACL the longer it takes to check permission for a document.
- Duplicating a document requires duplicating the ACL, and therefore no way to keep them in sync.
- ACLs are difficult to visualize and therefore manage
Attribute-based access control (ABAC)
Attribute-based access control removes the notions of roles and documents as far as access is concerned. Instead we’ll completely rely on attributes attached to the user and the document. Permission is granted if all the document & user attributes evaluated result in a success. In practice this means converting every authorization request to a user attribute list combined with a document attribute list and then using a policy evaluator. This offers advantages over the native RBAC paradigm, and improvements over ACLs.
Pros:
- Write first class rules to define access
- Rules are ubiquitous so granular access is available
- Control permissions over more documents at the same time compare to the one-document ACLs
Cons
- Impossible to quantify who has specific access to all documents with particular classifications. Because this requires getting a full list of every potential user. Users are resolvable to attributes, but not the other way around.
- No understanding of resources, which means it is impossible to fetch all text documents a user has access to.
- No understanding of users, therefore no ability to give access to a specific user or get all users that fit a pattern.
Fundamentally we believe ABAC to be flawed by design. The Cons represent two critical problems with it.
These critical problems are:
- Attribute-Storage/Evaluation. Of course you want your Authorization solution to do the evaluation. That means that it has to know about all the attributes of the resource in question as well as all the attributes of the user. Since user attributes usually change slowly, it is uniquely suited to store those. However, there are some scenarios where the user attributes are not known to the Authorization solution up front, it's possible they haven't been synced yet because a user just changed. Or it is a new user. In these cases the Authorization solution doesn't have access to these critical attributes to know how to correctly perform a check. And that's not even the worst part, even if it does know about all the users and their attributes, it for sure doesn't know about the Resource attributes. To solve this, either:
- All the attributes of every user and resource need to be synced to the Authorization solution. At scale this is not a viable solution.
- Which leaves us with the alternative, passing all the attributes of the user and resource into the Authorization solution at evaluation time, at which point the Authorization solution can do the check. This requires the caller to know what all the important attributes are, which also not viable at scale or in any sufficiently distributed system. That's because it pushes the responsibility of fetching all the attributes for those resources to the client component. And thus must rely on it to correctly know which those attributes are and then also perform the fetches to whichever other components / tables / databases they are stored in.
- It is impossible to do more than a simple
Auth Check
for ABAC. That's because since we neither have a full set of users or resources, we are just storing the policies of how to do the comparison. The most critical questions can never be answered by an ABAC solution:Who has access to this resource
, andWhat are all the resources this user has access to
. These are the most common requests that an Authorization solution must answer aftercan a user perform this action on that resource
. The Authorization solution doesn't know all the attributes that could exist.
And even if an Authorization solution did know, it still couldn't answer either of those questions, because it can't know which attributes actual users have nor which resources map to those attributes to be able to return a useful answer.
The only saving grace for #2, is if you don't actually care about those questions and need only the authorization check. Then that problem can be eliminated and we fall back to the problem of #1.
For #1, if we have to pass in all the attributes, then the Authorization solution isn't doing anything valuable anymore. A simple library can check if two "lists" contain overlapping attributes. But this isn't scalable and creates a pit of failure. It does so, because this requires the permissions logic
to be exactly replicated in every component you build without the benefit of everything else that comes from a Authorization solution. You lose out on centrally consistent and up to date checks. And most notably, Don't Repeat Yourself
as well as secondary benefits such as Audit Trails, Anomaly Detection, and metrics on user actions are no longer possible.
Further, if we decided to pass all the attributes to the Authorizations solution, we are in fact just doing a Resource Based Access Control, but with extra steps. It's far easier to send the critical aspects of the Resource (identifier/hierarchy) rather than every possible attribute.
In almost every scenario, the Granular Resource Based Access Control is a much better model, and can be mapped and then migrated to from an ABAC one.
Resource-based access control (ReBAC)
Resource-based access control (also known as granular role-based access control) removes all the restrictions applied above by creating distributed access policies. Fundamentally this is similar to RBAC, but the key difference is the scale and granularity for which it applies. Policies are stored separately from users, documents, and roles, and instead link all three. A policy contains one or more users, one or more roles, and one or more documents. This triad of responsibility combines the benefits of the other strategies by focusing on access control as a first class notion.
Pros:
- Get all the users which have a permission to a document
- Get all the documents a user has access to.
- Dynamically update user permissions via access policies in real time.
- The triad of user, role, resource creates a de facto standard of the combination of the three lists.
- No limits on the number of users, resources, roles, or policies that can be maintained.
- Create service clients that impersonate access policies for long running actions.
- Roles and policies are shareable across documents, one policy can specify both binary and text documents, or any kind of future document.
- Decouples documents from users or roles, allowing clear separation of responsibilities.
- Flexible to migrate and change policies as resources, users, roles all change.
Cons:
- Getting all the policies applied to a document becomes challenging.
- Need a separated provider to store policies and evaluate permissions indices.
Conclusion
There are many strategies to implement access control for documents, and already we see some of them lack the flexibility to scale with changes to our document repository. Documents aren’t even that challenging of a concept, so with more complex resources, finding the right fit for your product becomes even more important. Pick the one that best resembles the future possible access patterns for your product resources.
And while there are caveats to each of these strategies, you can add functionality on top to help compensate for what they are lacking. For example moving RBAC away from the authentication identity provider to a separate service. This better resembles how the other strategies work, but starts to lose the benefits that RBAC had provided. Fundamentally, these changes to the paradigms to improve them, makes them something different, and rather than attempting to patch a system that doesn't fit, it may be better to pick a better access strategy.