How to Write Your First Rules in Rego, the Policy Language for OPA

5 min read

What is Rego? 

Rego is the purpose-built, declarative policy language of Open Policy Agent (OPA). With Rego, both reading and writing policy code is easy. Fundamentally, Rego works by inspecting and transforming data in structured documents, allowing OPA to make policy decisions. Rego was originally inspired by Datalog, a common query language with a decades-long history, but extends its capabilities to support structured document models like JSON as well as adding useful functions for modern policy requirements. 

Rego queries, put simply, ask questions of data – both preloaded data and data provided at request time. Rego policies then evaluate whether data complies with, or violates, the expected state — that is how policy decisions are returned. For instance, a policy in Rego might declare, “Containers may never run in privileged mode.” If a request comes to OPA to run a container in privileged mode, that request is denied. Another policy, this time related to application entitlements, might say, “Only users in the development group who control more than $500K in budget are allowed to access Service A.” If Alice, who is in the development group and controls more than $1M in budget, requests access to service A, that request is allowed. This is the simple power of policy as code expressed with Rego. 

Why should I use Rego?

Policy as code, via OPA (CNCF graduated), has become the de facto standard for authorization across the cloud-native stack. At the same time, enterprises are running more services in more places; new regulations are coming into force and security requirements are getting more stringent. OPA and Rego the foundation of a policy as code platform with features for policy definition, evaluation and audit.

Rego focuses on providing powerful support for referencing nested documents and ensuring that queries are correct and unambiguous. Rego is a declarative language, meaning policy authors can focus on what they want a query to return rather than how policy should be evaluated – similar to other declarative languages you might be familiar with like SQL. These queries are simpler and more concise than the equivalent code in imperative languages. 

As with any programming language, Rego can take some getting used to. Still, as Rego is purpose-built for authorization, users often find that Rego is the best and clearest way to express policy as code. Rego was designed to create policies that are easy to read and write, and we’ve been hard at work to make Rego easier to learn too! As you get up to speed with Rego, you might want to make use of the following:

  • Rego Playground: the online Rego sandbox to try out new policies and data.
  • Rego Cheat Sheet: a quick reference for common Rego patterns and functions.
  • Styra Academy: Learning platform with free courses on Rego and OPA.
  • Regal: The best linter and Rego editor integration to learn Rego as you type.
  • OPA Errors Guide: A quick reference for common errors you might encounter when writing Rego policies.

Using Rego, aided by these tools, you’ll be wiring up your policies in no time!

How do I start using Rego?

In the simplest form, users can get started running Rego by using the OPA CLI and running commands. However, to understand Rego, one must understand the principles behind its design, and how those principles make Rego particularly well suited for describing policy:

1. Syntax should reflect real-world policies

2. Embrace hierarchical data

3. Optimize performance automatically

With these principles in mind, you can understand the core value of Rego: to embrace, in full, policy as code.

Rego makes its policy decisions by evaluating query inputs against policies and data. Therefore, to express policy you need to write expressions in Rego against that policy and other stored data. A number of expressions can be used in rego, and are joined by several built-in operators, such as ; (and) and := (assignment). A rule in Rego may then take the following form:

package example.rules

import rego.v1

# net.id is in the list of public_network_ids set if...
public_network_ids contains net.id if {
	# some network exists...
	some net in data.networks

	# and it is public.
	net.public
}

This rule defines a list of networks which are public. We then can use this as a building block to define other rules that allow or deny operations.

What rules can I enforce with Rego?

To produce policy decisions in Rego, you write expressions against input and other data, such as temporary variables. True to its role as an authorization toolkit, OPA includes a set of built-in functions you can use to perform common operations like string manipulation, regular expression matching, arithmetic, aggregation and more. The simplest rule is a single expression and is defined in terms of a scalar (single) value, as opposed to a set. Every rule consists of a head and a body. In Rego, we say the rule head is true if the rule body is true. These bodies will typically be made up of multiple inputs, with the head composed of the name of the rule (such as “allow” or “deny”) and the value to assign (such as true/false, but could be any JSON value).

Here is an allow rule that makes reference to the public_network_ids rule from above. It’ll allow requests where the network’s ID is in the list of public networks.

allow if input.network_id in public_network_ids

Sometimes, you need to AND multiple conditions, for example, we might want to only allow admins to access a resource from a public network:

allow if {
	input.network_id in public_network_ids

	# and
	input.user.role == "admin"
}

Another important concept in getting started with Rego is understanding how to define constants. Constants can be used to specify values used in multiple other rules. For example:

permitted_roles := {"admin"}

allow if {
	input.network_id in public_network_ids

	# and
	input.user.role in permitted_roles
}

Here we create a constant called permitted_roles, it’s a constant set of roles that we might want to allow in various rules.

Another important construct in Rego are partial rules. These can be used to break down a complicated series of checks into easy to understand chunks of policy. Here deny is used to make multiple checks on the input:

deny contains "request was not from public network" if {
	not input.network_id in public_network_ids
}
# OR
deny contains message if {
	input.user.role != "admin"

	message := sprintf(
		"User %s is not an admin",
		[input.user.name],
	)
}

deny will contain a message for any of the violating conditions for that request. Each deny block can be read as a non-exclusive OR. Just remember, statements within a rule are AND while different rules are OR. Aside, checkout out this post on expressing OR in Rego which covers the topic in much more detail.

Finally, we can pull everything we’ve learned together. We can use the deny rule to validate the input, and roll up the result into an allow rule that either allows or denies the request with a number of messages explaining why. Here we also use the default keyword to define the base case where the request is allowed, unless there are deny messages.

default allow := {
	"result": true,
	"messages": [],
}

allow := {
	"result": false,
	"messages": deny,
} if count(deny) > 0

deny contains "request was not from public network" if {
	not input.network_id in public_network_ids
}

deny contains message if {
	input.user.role != "admin"

	message := sprintf(
		"User %s is not an admin",
		[input.user.name],
	)
}

Now you know the basics of writing your first rules in Rego! From here, the sky’s the limit for you and your policy as code rollout. More integrations for OPA and Rego are created every month – perhaps you’ll make the next one? (please tell us if you do!)

If you’d like to learn to write Rego yourself, don’t forget to look into the resources from above:

  • Rego Playground: the online Rego sandbox to try out new policies and data.
  • Rego Cheat Sheet: a quick reference for common Rego patterns and functions.
  • Styra Academy: Learning platform with free courses on Rego and OPA.
  • Regal: The best linter and Rego editor integration to learn Rego as you type.
  • OPA Errors Guide: A quick reference for common errors you might encounter when writing Rego policies.

Got questions? We’re also hanging out in the Styra Community Slack. See you there!

Cloud native
Authorization

Entitlement Explosion Repair

Join Styra and PACLabs on April 11 for a webinar exploring how organizations are using Policy as Code for smarter Access Control.

Speak with an Engineer

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