Check it out: How permissions are answered in Authzed

Check it out: How permissions are answered in Authzed
NOTE: This blog post makes use of Zanzibar terminology and namespace configuration. It will be updated in the future to match current Authzed terms and schema configuration

What to ask

Authentication and Authorization are searching for the answers to questions.

For Authentication the question being asked is “Who are you?”. The software requests to know who the calling user (or machine or entity) is, so that it can make decisions, assign ownership or simply log usage. Authentication is about identity.

An image of a Vorlon from Babylon 5
The Vorlons (and Authentication providers) ask: Who are you?

Authorization, on the other hand, is concerned with a different question: “What do you want (to do)?” The software requests to know what the calling user (or machine or entity) wants to do, so that it can verify and validate that the calling user is allowed to take such action. Authorization is about permission.

An image of a Shadow from Babylon 5
The Shadows (and Authzed) ask: What do you want (to do)?

Framing the question

Imagine a simple permissions situation: A user (“Fred”) is attempting to open a document. We’ve already answered the Authentication question here (“Who are you?” -> “Fred!") and now we are attempting to answer the Authorization question: “What do you want to do?” -> “I want to read the document”.

This question can be simplified:

Can Fred read the document?

Many developers tend to think of such permission questions as “easy” or even as an afterthought; afterall, how hard can it be to answer a question like “Can Fred read the document?”. However, within the shadows of that simple question actually lies a host of potential complexity, performance bottlenecks, and security concerns.

At Authzed, our authorization platform as a service is designed to answer these kinds of permissions questions efficiently and with minimal overhead, while providing tooling to handle the bulk of the complexity.

Subject: Taking action on the object

Within the Authzed API, these basic permissions questions are called Checks, and here we’ll shine a light on the process that Authzed uses to answer these queries, and how doing so allows for stronger and more powerful permissions models.

Let’s take our example from above: “Can Fred read the document?” Or perhaps another example: Can this machine delete this row in the database? Both examples express a question with the same form:

“Can <subject> perform <action> on the <object>?”

In our first example, the object is the specific document being accessed, the action is read and the subject is the user Fred: We want to know whether Fred has permission to read the document.

Similarly, in our second example, the action is delete, the object is the specific row to be deleted, and the subject is the account for the machine making the call: We want to know whether the machine has permission to delete the row.

However permissions systems are rarely about a single subject, action or object. Rather, we need to know how we can generically answer this kind of question, for all combinations of subjects, actions and objects.

Forming relation(ships)

At their core, Permissions' answers are decided by relationships: Fred can read the document if there exists the correct type of relationship between Fred and the document. How these relationships are formed depends on the kind of permissions system being used. In RBAC, for example, these relationships are expressed as roles, such as reader, writer, or admin, each defining a set of permissions for subjects, while in ABAC, as another example, these relationships are expressed as attributes, such as can_read.

When using Authzed, developers define their own relationships via a combination of namespaces and tuples. Each namespace defines a kind of object along with its possible relationships to other objects. A tuple is the data that realizes the actual link between specific objects using one of the possible relationships.

While Authzed is not restricted to a specific kind of permissions model (because developers have the freedom to create their own) for convenience we will demonstrate an RBAC-like model with three different roles: reader, owner and an admin on the parent “organization” that owns the document.

The kinds of objects (and how they can possibly relate) are expressed in the Authzed namespace configuration language (for more information, see Developing a namespace). Each namespace defines a kind of object, and has zero (or more) relations, which defines a possible relationship between objects of that type and other objects.

Let’s define for our example:

user

name: "user"

organization

name: "organization"

# Users can be marked as being admins on the organization.
relation {
    name: "admin"
}

document

name: "document"

# Users can be marked as being readers of the document.
relation {
    name: "reader"
    userset_rewrite {
        union {
            child { _this {} }
            child { computed_userset { relation: "owner" } }
        }
    }
}

# Users can be marked as being owners of the document.
relation {
    name: "owner"
    userset_rewrite {
      union {
        child { _this {} }
        child {
          tuple_to_userset {
            tupleset { relation: "organization" } # document -> organization
            computed_userset {
              object: TUPLE_USERSET_OBJECT # org admins
              relation: "admin"
            }
          }
        }
      }
    }
}

# A document can be marked with being part of an organization.
relation {
    name: "organization"
}

In addition to the namespace configurations, we also need to realize the relationships between these objects. In Authzed, we do so by writing one or more tuples, defining edges from one object to another:

document:somedocument#reader@user:sean#...                 # Sean is a reader on somedocument
document:somedocument#reader@user:fred#...                 # Fred is a reader on somedocument
document:somedocument#owner@user:jill#...                  # Jill is the owner of somedocument
organization:theorg#admin@user:hannah#...                  # Hannah is the admin of the organization
document:somedocument#organization@organization:theorg#... # `theorg` is the organization for the document

These edges and namespaces together form a graph, and it is this graph that Authzed uses to compute answers to permissions questions.

Can Fred read the document?

Let’s take a look at the graph formed by the configuration and data above.

First, we apply the namespace configurations, which defines the shape of the graph:

An image of the shape of the graph
The shape of the graph defined by our namespace configurations

The graph shows a set of paths from the object (document), through the roles (reader, owner, organization, admin) and finally to the subjects (user, organization).

Note that in this graph the paths are the possible paths of relationships between documents and users, but nothing yet concrete; the real paths emerge only once we apply the tuple data and realize the relationships:

An image of the realized graph
The graph defined by our namespace configurations and tuples

Now we can see a fully realized graph, with possible paths between a specific document, via specific relations, to specific users.

Answering the question

To Check whether Fred can read the document, we issue the following API call to Authzed:

CheckRequest {
    test_userset: ObjectAndRelation {
        namespace: 'document'
        object_id: 'somedocument'
        relation: 'reader'
    }
    user: {
        userset: ObjectAndRelation {
            namespace: 'user'
            object_id: 'fred'
            relation: '...'
        }
    }
}

How does Authzed answer this Check question? It searches for a path from the object (here: somedocument) through the action (reader) to the subject (fred). If such a path is found, then we know there exists a reader relationship between somedocument and fred, and therefore he can read the document.

Let’s take our Check above and follow the resolutions. We begin with the object (somedocument) and search for the matching action (reader). Next, we walk from reader to any subjects found, which gives us these users:

user:sean#...
user:fred#...

And… we’re done! We’ve found fred!

Visualizing

As we saw above, there does indeed exist such a path for Fred, and so the answer to our question is “yes”:

If we instead attempted to ask this question for another (say, adam), we can see that we find no such path, indicating that the answer is “no”:

What about Jill?

In our tuple data above, we defined that the user jill has role owner on the document. Does this mean she can also read the document?

Let’s run our Check call, starting at somedocument. We begin by checking reader, which gives us the two users previously found:

user:sean#...
user:fred#...

jill isn’t one of these users, so we’d terminate lookup and return no… except that reader is defined with a second lookup path.

Recall our definition above:

relation {
    name: "reader"
    userset_rewrite {
        union {
            child { _this {} }
            child { computed_userset { relation: "owner" } }
        }
    }
}

In addition to checking the reader relation (_this), we also check the relation owner (via computed_userset). Therefore, the next step in our walk of the graph would be to list those subjects related by owner, which gives us:

user:jill#...

Once again, we’ve found the user we want, thus resulting in a “yes” answer:

As we can see, because the owner relation is defined to have a path that includes the relation reader, a valid reader relation exists for Jill and she is therefore also allowed to read the document, despite she herself not being explicitly given the role of reader: We’ve implicitly defined that all owners are also granted the role of reader.

Who’s the boss?

Our example namespace configurations also defines another role, that of admin of organizations. Logically speaking, any admin of the organization should be able to read all the documents found “within” that organization. Since we defined hannah as being the admin of the organization theorg, and theorg as being the organization for the document, let’s see how a Check for reader on the document for Hannah might work:

The path used to reach hannah from the document certainly appears to be longer, but once again, we can see that the resolution is a fairly straightforward graph walk: We were able to walk from the document, through reader, then owner, then from the document to the organization, and finally through admin to Hannah, showing that, in the end, she’s the boss.

In summary

As we’ve seen above, answering Authorization Check questions is accomplished within Authzed by use of a fairly straightforward graph walking mechanism. By making use of a walk over a graph, Authzed is able to support permissions models with arbitrary numbers of roles, arbitrary levels of hierarchies and even complex decisions that make use of operators such as intersection or exclusion.

It should be noted that while the above walking examples are straightforward, the way we accomplish these Checks within Authzed is in reality far more complex, to ensure reasonable performance and redundancies. We’ve barely scratched the surface of Authzed today, so we will be diving deeper into the mechanisms behind these resolutions in future posts.

Ready to get started?
Sign up for Updates
Recent Articles
blog-image
Product Update: August
August is for Authzed!
blog-image
Securing Prometheus with prom-authzed-proxy
A new proxy for authorizing PromQL queries
blog-image
Observability shouldn't be private
How we're opening up our internal metrics to all of our users