Five Things You Didn’t Know About OPA
When introducing Open Policy Agent (OPA) to application developers and platform engineers, I normally end my presentation with a bulleted list detailing what I think are the best steps to take to start learning OPA and its declarative policy language, Rego. Simple things like “start small,” “the docs are great, read them!” and “try the Styra Academy.”
That kind of basic understanding of what OPA is and how to write simple policies in Rego and deploy them might actually be enough to solve most of the problems around application authorization a development team might be facing. Other scenarios will require a deeper understanding. Or someone might just find themselves enjoying learning more.
Assuming you know the basics—and you’d like to learn more—how do you take your skills to the next level? The answer varies depending on where your interests lay (e.g., performance, data integrations, etc.). So without any pretense of providing a complete answer, here are five ways to step up your OPA/Rego game.
1. Comprehensions Are key to comprehension!
If you ever approached collections of data in Rego, you might have found yourself grasping for common functional idioms like “map”, “filter” or “reduce”. Rego offers an interesting—and just as powerful—alternative in the form of comprehensions. Comprehensions work on all the collection (i.e. composite value) types in Rego, like arrays, sets and objects.
Comprehensions provide a concise, elegant way to take one collection (or more!) as input, and return a new, modified collection as output. Want to capitalize all strings in an array? Use a comprehension. Want to filter out all items from a set not matching a predicate? Use a comprehension. Have one array of keys and another one of values and from those two want to build a new map? You guessed it, comprehensions got your back.
In the following example, we expect to find an array of permissions in the form of URNs as part of the input. Any permission would be structured as “urn:domain:resource:action”, so if user Jane was allowed to read credit reports the permission might look like this: “urn:credit:report:read”. If we wanted to get a list of all resources readable by Jane, we could use a comprehension to do so:
In the comprehension above, we iterate over the roles, split each URN on “:” and check if the last part (the action) is “read”. If it is, the name of the resource will be included as part of a new array. Should we rather return a set, we can simply replace the outer square brackets with curly brackets.
2. Sets are awesome!
While JSON supports only arrays, Rego has both arrays and sets. While these unordered collections of unique values may not seem like such a big deal, they are often preferable over arrays in Rego policies. Why?
Maps well to the domain—common objects like groups or roles are almost always unique and unordered.
Common “contains” and “not contains” operations do not require iteration. Checking if my_set contains item could not be simpler: my_set[item]. Prepend not to check if the set does not contain item.
Really useful built-ins for performing set operations like intersection to succinctly answer common questions like “is this user in all the required groups?” or union to query “what are all the roles I’ll need if I want to do operation X, Y and Z?”
Creating new sets in Rego is simple, but if JSON won’t support sets, and any input provided to OPA is provided as JSON, how do we get to work with them from input? Simple: use a set comprehension (see, I told you comprehensions were useful!) to iterate through an array and copy each item into a new set:
my_set := {x | x := my_arr[_]}
So, why aren’t they more common? First of all, since sets are not as well-known as arrays, and set operations are not as familiar as array iteration, a policy using sets rather than arrays might be perceived as more complex. Luckily the differences are small enough that just a few minutes of education should be enough to convince most of the benefits of sets.
Also, since sets can’t be represented as JSON we’ll need to add some extra array-to-set conversion ceremony in our policies whenever we want to work with something in input as a set. Even with this considered, chances are pretty good that your policy dealing with sets will be more straightforward and succinct than the one iterating over many arrays.
To conclude: sets are awesome! Used correctly they may greatly simplify some policies. Learn when to use (and not to use) them.
3. The OPA document model and Rego the query language
A key aspect to understanding both OPA and Rego is to understand how the OPA document model works. Knowing the document model really is key to any advanced OPA development, but perhaps in particular interactive development, where you frequently query the document for results, whether it’s done with help of the REST API, the REPL or as part of writing Rego tests.
The OPA document model consists of two types of documents. First, there are policies and rules, which generate virtual documents, commonly based on other rules, data or built-in functions available to the rule that was queried. Secondly, the OPA document model includes base documents, which is any data loaded into OPA. Rego documents generated or not, all data is stored under the same hierarchical document, that may be queried using Rego the query language.
Perhaps the most effective way to really get a feel for OPA’s document model is to query it through the OPA REST API. Simply point your favorite API client at the /v1/data endpoint to retrieve a complete view of all the data in the OPA instance, whether generated or not.
4. Rego Playground tricks!
One of the most fundamental requirements for mastering any language or tool is knowing what’s going on. Observing the state and flow of things in order to answer not just what a query, rule or function produces, but also why something happened—this skill is absolutely crucial in Rego as much as it is in any field of software development.
Before reaching that state of enlightenment where you’re able to predict exactly what a Rego document a rule will return just by staring at it, you are going to need some tools to help you out. OPA itself provides a few tools to help you with this, and you’ll find even more in the ecosystem, like IntelliJ IDEA plug-in, the VS Code extension and addons for other editors.
The Rego Playground is luckily well known, and has quickly become the favorite tool for many Rego developers to write and share policy snippets. Is your policy not behaving as you’d expect and you can’t figure out why? Use the “Publish” button to get a permalink which you can then share with others for support, in the OPA Slack or elsewhere.
While well used, there are some great features of the playground that many seem to miss. First, checking the “Coverage” checkbox before evaluating will have each line displayed either in green or red. Green for when evaluation succeeded and red for when it failed, for example due to a value being undefined. This often makes it immediately obvious where and why evaluation failed, providing an opportunity to correct any mistakes in the policy before re-evaluating.
Secondly, if you ever select some text in the policy you are authoring, the “Evaluate” button will switch to show “Evaluate Selection”. This makes it super convenient to quickly identify the value of a variable, or the result of an expression, even inside of rules!
Finally, did you know the Rego Playground includes a bundle server? After pressing “Publish”, check out the instructions listed under “Run OPA with playground policy”. This takes the policy (and data) you see on screen and runs it in OPA on your local machine, in a Kubernetes cluster or wherever you want! While obviously not meant for production use, this is a great way to quickly move playground code into real applications for testing.
5. The REPL rocks!
Possibly not as well known or used as the Playground, but extremely useful for exploratory policy development, the OPA REPL (Read-Evaluate-Print-Loop) provides a “live” environment for querying OPA for anything from the inside, be it data, rules or functions.
Equipped with an understanding of the OPA document model, the REPL becomes the go-to environment for quickly navigating around OPA documents, whether they are base documents or virtual documents. Simply start the REPL by issuing opa run without the –server flag. Type “help” in the REPL to get a list of all available commands, or just start typing your queries directly.
Just like with the REST API you can either query data in its entirety, or drill down into specific documents. Since you’ll most likely want to experiment with providing different input to the documents you query, the with keyword is extremely useful in this context:
data.policies.authz.allow with input.user as {“roles”: [“developer”]}
If you think this looks similar to a test assertion you’d be absolutely right! Using the OPA test runner in combination with the REPL is a great way to explore the outcome of different variations of evaluation when writing tests. As with most REPLs and shells, you can navigate the history of past commands by using the up and down arrow, and many of the keyboard shortcuts familiar from those environments are also available in the OPA REPL.
Wrapping up
Finally, Rego, OPA and its tooling keeps improving all the time, and what might be considered best practices for OPA/Rego development today might have better alternatives available tomorrow. Perhaps a Rego debugger would simplify policy development for those used to that type of workflow? Also, sometimes all we really want is just to print something—be it “hello”, the value of a variable, or profanities—to the console. Perhaps the time is right for having a proper print function added to the growing list of useful built-ins?
With the pace at which OPA development moves forward, we’re in for some exciting times ahead!
Learn more how OPA users deploy Open Policy Agent at scale in our ebook!
This blog first appeared in The New Stack on June 17, 2021