Selecting the correct system for managing access is critical to the overall security of any application being developed today: broken forms of access control are the number one issue on OWASP’s Top Ten list of security concerns for application and system developers.
Over the past few years, a number of approaches to modeling access have become standard, including role-based access control (RBAC), relationship-based access control (ReBAC) and, the focus of this post, attribute-based access control (ABAC). While each approach has its positives and negatives, this post will discuss the benefits of ABAC and how, with SpiceDB, ABAC can be combined with ReBAC to provide the benefits of both, with little to no downside.
What is ABAC?
Attribute-based access control, or “ABAC,” is a system for access control that uses attributes found on objects alongside a set of rules or functions to determine how to secure access and how permissions should be granted. ABAC is often also referred to as a policy engine for permissions.
Unlike role-based access control, standard ABAC allows for defining almost arbitrary sets or categories of information to use for making decisions.
Unlike relationship-based access control, standard ABAC defines the attributes on the objects themselves, rather than as relationships between objects.
An ABAC example
To define whether a user can view a document using an ABAC approach, the following attributes might be defined on the document:
Attribute Name | Attribute Value |
---|---|
viewers | [user1, user2, user3, …] |
editors | [user1, user2, user3, …] |
public_days | [tuesday] |
In the above table, the multiple attributes of “viewers” and “editors” are being used to define roles (in a way similar to RBAC), while “public_days” is being used to add a custom role, which is then enforced using the following policy to check whether a particular user has access to view the document:
func can_view_document(document: Document, user: User) {
current_week_day in public_days or user.id in document.viewers or user.id in document.editors
}
Here we can see how ABAC is used to enforce access: the attributes (or data) defined on resources or objects or subjects are used to enforce the rules (or a policy) to make a permissions decision, including whether the document is public today, and, if not, how a user may have access to the document.
In essence, we are executing a program, defined in a custom language over a subset of data to return a single result: true if the user has the requested permission.
ABAC is everything?
Attribute-based access control is considered by some to be a superset of other approaches to access control: after all, while it can simulate RBAC (as we saw above), RBAC and ReBAC cannot, in turn, operate on attributes. Many therefore assume that they should just use ABAC since it can, in theory, do anything that other forms of access control can do.
This idea rapidly breaks down once scale and performance requirements are introduced: Since ABAC is running code, it is quite difficult to effectively scale while maintaining performance guarantees. This was one of the major reasons, in fact, that Google invented Zanzibar as a ReBAC solution: it was the only design capable of addressing these issues over Google’s existing policy-based access control.
So we should not use ABAC for everything, but we also (at times) would like to have the ability to make permission decisions based on dynamic or mutating data… how might we approach combining the best features of ABAC (custom policy) with those of ReBAC?
ABAC in some things
Recognizing the above problem, SpiceDB invented and introduced the concept of caveats: ABAC applied to specific subsets of permissions decisions, such that we can leverage the flexibility of attribute-based decision making with the power and efficiency of Zanzibar.
Taking the previous ABAC example used above, we can translate this into SpiceDB’s schema like so:
definition user {}
caveat is_public_today(current_week_day string, public_days list<string>) {
current_week_day in public_days
}
definition document {
relation viewer: user | user:* with is_public_today
relation editor: user
permission view = viewer + editor
}
Here, we can see the basic roles of “viewer” and “editor” are defined in the SpiceDB schema as standard relationships between the document and the user(s). The new addition is the “with is_public_today”, which states that the a relationship between a document and “user:*” (indicating the document is public) will only exist if is_public_today returns true: we’ve caveated the existence of the public relationship on a tiny ABAC-like piece of policy called “is_public_today.”
Like ABAC, the attributes used to compute the result, in this case “public_days”, can be stored. Unlike ABAC, however, it is stored not on the document itself, but on the relationship written between the document and the subject. This allows for more flexibility than even ABAC, as different relationships can contain different data!
The real benefit, however, comes in how these checks are computed: like checks without caveats, SpiceDB can make use of parallelization, structural graph walking and event aggressive caching to compute answers. SpiceDB caches the computed caveat expressions themselves, ensuring that the cache can even be used when the incoming context changes!
With caveats you therefore get all the power of ReBAC and Zanzibar, but with most of the flexibility of a traditional ABAC system!