Collaborating on Access Control Policies with Open Policy Agent

3 min read

Zendesk Engineering consists of many teams that own a large number of different domains, ranging from engineering teams that built internal services to teams that work on our various product offerings. One concern that these teams have in common is controlling access to their APIs via fine-grained policies. Some APIs are only available to admins, others to users with a specific set of permissions and some APIs restrict access based on attributes of the data being accessed. We have established a goal of centralizing our access control policies, to ensure consistency and compliance across all our APIs. The foundation of our access control layer is Open Policy Agent, a performant and highly scalable policy engine. This article will focus on its purpose-built policy language and how it can enable the collaborative definition of access control policies across a large engineering organization.

Policy Language

Open Policy Agent uses a declarative, logic programming language called Rego, which is heavily influenced by by Datalog and Prolog. As a logic programming language, Rego is particularly well suited for expressing complex conditions and its concise syntax provides a great starting point for a domain specific language (DSL) to express access control policies. At its core, Rego consists of a set of rules which are lazily evaluated. Rules contain a head and a body, the head gets evaluated only if the body evaluates to true.

The part in braces is the rule body. It references some input data and evaluates a simple condition, whether the current user is an admin user or not. If true, the result of the allow rule is true as well, otherwise the result is undefined.

Since boolean rules are very common, the truth assignment in the rule head can be omitted. It is also possible to define a default for a rule, which will apply if the result would otherwise be undefined. A more complete example of a simple access control policy would look as follows:

Incremental Definitions

Rego has a few interesting features, but the one that is the focus of this post is the ability to define rules incrementally. Instead of having to define all conditions that make the allow rule true in the same rule body, multiple rules with the same name can be defined. If any of the rule bodies evaluate to true, the result of the rule will be true, or in the context of access control the access will be allowed.

The example above adds a second condition, which grants access to the “/public” path irrespective of the user’s role. The key part here is that the two rules can be defined independently, in two different files that are owned by different teams. Note: Rules are scoped by package and the package definition has been left out for simplicity.

Incremental definitions provide a very natural way to establish a contract for writing access control policies. Teams have full control over the definition that pertains to their domain, as long as they use the nomenclature defined by our policy DSL. In other languages, interfaces or traits in combination with a predefined registry or reflection at runtime could be used to achieve a similar result.

Partial Sets

Rego has native support for sets, i.e. an unordered collection of unique values. Access control policies could be used to restrict access to a subset of resources or a subset of functionality, a great use case for sets. Assuming we use OAuth scopes to model our access controls, we could define the following rule to add a scope accessible to staff users:

The allow_scopes rule returns a set, which is denoted by the contains operator.

Note: the import statement is required at the time of writing to add support for the contains keyword. Previous releases of Open Policy Agent use a different syntax not covered here.

This rule is not particularly interesting by itself, but rules that return sets can also be defined incrementally.

A short excursion to the console can demonstrate how this rule would be evaluated:

The fact that incrementally defined rules can be used to build partial sets, enables teams to collaborate on more complex rules while still fully owning all access control logic that relates to their domain.

Conclusion

Centralizing all access control policies is an important goal for Zendesk, but we still want to enable teams to define and own policies that are specific to their product domain. Open Policy Agent and its policy language provide a great way to not only define our own policy DSL but also enable collaboration on policies across many teams.

This blog is co-published on the Zendesk Engineering Blog.

About Ralf Gueldemeister

Ralf is a Principal Engineer at Zendesk and Group Technical Lead for internal core services. He currently leads the development of the Zendesk roles and permissions platform and has been using Open Policy Agent in production systems for two years.

Cloud native
Authorization

Dynamic Authorization for Zero Trust Security

An organizational guide to architecting and implementing Zero Trust authorization in a brownfield environment

Speak with an Engineer

Request time with our team to talk about how you can modernize your access management.