Project: Open Policy Agent (OPA) (Provisioning / Security & Compliance)
File: policy-language.md

title: Policy Language kind: documentation weight: 2 toc: true ```live:eg:module:hidden package example import rego.v1 ``` OPA is purpose built for reasoning about information represented in structured documents. The data that your service and its users publish can be inspected and transformed using OPAs native query language Rego. Rego was inspired by , which is a well understood, decades old query language. Rego extends Datalog to support structured document models such as JSON. Rego queries are assertions on data stored in OPA. These queries can be used to define policies that enumerate instances of data that violate the expected state of the system. {{< info >}} The examples in this section try to represent the best practices. As such, they make use of keywords that will become standard keywords in , but have been introduced gradually. for more information. {{< /info >}} Use Rego for defining policy that is easy to read and write. Rego focuses on providing powerful support for referencing nested documents and ensuring that queries are correct and unambiguous. Rego is declarative so policy authors can focus on what queries should return rather than how queries should be executed. These queries are simpler and more concise than the equivalent in an imperative language. Like other applications which support declarative query languages, OPA is able to optimize queries to improve performance. In while reviewing the examples below, you might find it helpful to follow along using the online . The playground also allows sharing of examples via URL which can be helpful when asking questions on the . In addition to these official resources, you may also be interested to check out the community learning materials and tools. {{< ecosystemfeaturelink key="learning-rego" singular_intro="There is currently 1 project" singular_link="listed in the OPA Ecosystem" singular_outro="which can help you learn Rego." plural_intro="These " plural_link="COUNT projects" plural_outro="are listed on the OPA Ecosystem page as related to learning Rego." }} This section introduces the main aspects of Rego. The simplest rule is a single expression and is defined in terms of a : ```live:eg/pi:module pi := 3.14159 ``` Rules define the content of documents. We can query for the content of the `pi` document generated by the rule above: ```live:eg/pi:query:readonly,mergedown pi ``` ```live:eg/pi:output ``` Rules can also be defined in terms of : ```live:eg/rect:module rect := {"width": 2, "height": 4} ``` The result: ```live:eg/rect:query:readonly,mergedown rect ``` ```live:eg/rect:output ``` You can compare two scalar or composite values, and when you do so you are checking if the two values are the same JSON value. ```live:eg/rect/compare:query:merge_down rect == {"height": 4, "width": 2} ``` ```live:eg/rect/compare:output ``` You can define a new concept using a rule. For example, `v` below is true if the equality expression is true. ```live:eg/undefined:module v if "hello" == "world" ``` If we evaluate `v`, the result is `undefined` because the body of the rule never evaluates to `true`. As a result, the document generated by the rule is not defined. ```live:eg/undefined:query:hidden v ``` ```live:eg/undefined:output:expect_undefined ``` Expressions that refer to undefined values are also undefined. This includes comparisons such as `!=`. ```live:eg/undefined/expression:query:merge_down v == true ``` ```live:eg/undefined/expression:output:expectundefined,mergedown ``` ```live:eg/undefined/otherexpression:query:mergedown v != true ``` ```live:eg/undefined/otherexpression:output:expectundefined ``` We can define rules in terms of as well: ```live:eg/rules:module t if { x := 42; y := 41; x > y } ``` The formal syntax uses the semicolon character `;` to separate expressions. Rule bodies can separate expressions with newlines and omit the semicolon: ```live:eg/rules/newlines:module:read_only t2 if { x := 42 y := 41 x > y } ``` Note that the future keyword `if` is

We could have written `v` and `t2` like this: ```live:eg/rules/newlinesnoif:module:read_only v { "hello" == "world" } t2 { x := 42 y := 41 x > y } ``` When evaluating rule bodies, OPA searches for variable bindings that make all of the expressions true. There may be multiple sets of bindings that make the rule body true. The rule body can be understood intuitively as: ``` expression-1 AND expression-2 AND ... AND expression-N ``` The rule itself can be understood intuitively as: ``` rule-name IS value IF body ``` If the value is omitted, it defaults to true. When we query for the value of `t2` we see the obvious result: ```live:eg/rules:query:hidden t ``` ```live:eg/rules:output ``` Rego help you refer to nested documents. For example, with: ```live:eg/references:module sites := [{"name": "prod"}, {"name": "smoke1"}, {"name": "dev"}] ``` And ```live:eg/references/basic:module r if { some site in sites site.name == "prod" } ``` The rule `r` above asserts that there exists (at least) one document within `sites` where the `name` attribute equals `"prod"`. The result: ```live:eg/references/basic:query:hidden r ``` ```live:eg/references/basic:output ``` We can generalize the example above with a rule that defines a set document instead of a boolean document: ```live:eg/references/helper:module q contains name if { some site in sites name := site.name } ``` The value of `q` is a set of names ```live:eg/references/helper:query:hidden q ``` ```live:eg/references/helper:output ``` We can re-write the rule `r` from above to make use of `q`. We will call the new rule `p`: ```live:eg/references/helper/composed:module p if q["prod"] ``` Querying `p` will have the same result: ```live:eg/references/helper/composed:query:hidden p ``` ```live:eg/references/helper/composed:output ``` As you can see, rules which have arguments can be queried with input values: ```live:eg/references/helper/argument:query:merge_down q["smoke2"] ``` ```live:eg/references/helper/argument:output:expect_undefined ``` If you made it this far, congratulations! This section introduced the main aspects of Rego. The rest of this document walks through each part of the language in more detail. For a concise reference, see the [Policy Reference](../policy-reference) document. Scalar values are the simplest type of term in Rego. Scalar values can be , numbers, booleans, or null. Documents can be defined solely in terms of scalar values. This is useful for defining constants that are referenced in multiple places. For example: ```live:eg/scalars:module greeting := "Hello" max_height := 42 pi := 3.14159 allowed := true location := null ``` These documents can be queried like any other: ```live:eg/scalars:query:merge_down [greeting, max_height, pi, allowed, location] ``` ```live:eg/scalars/str:output ``` Rego supports two different types of syntax for declaring strings. The first is likely to be the most familiar: characters surrounded by double quotes. In such strings, certain characters must be escaped to appear in the string, such as double quotes themselves, backslashes, etc. See the [Policy Reference](../policy-reference/#grammar) for a formal definition. The other type of string declaration is a raw string declaration. These are made of characters surrounded by backticks (`` ` ``), with the exception that raw strings may not contain backticks themselves. Raw strings are what they sound like: escape sequences are not interpreted, but instead taken as the literal text inside the backticks. For example, the raw string `` `hello\there` `` will be the text "hello\there", not "hello" and "here" separated by a tab. Raw strings are particularly useful when constructing regular expressions for matching, as it eliminates the need to double escape special characters. A simple example is a regex to match a valid Rego variable. With a regular string, the regex is `"[a-zA-Z]\\w*"`, but with raw strings, it becomes `` `[a-zA-Z]\w*` ``. Composite values define

In simple cases, composite values can be treated as constants like : ```live:eg/cuboid:module cuboid := {"width": 3, "height": 4, "depth": 5} ``` The result: ```live:eg/cuboid:query:merge_down cuboid.width ``` ```live:eg/cuboid:output ``` Composite values can also be defined in terms of or . For example: ```live:eg/compositevariables:query:mergedown a := 42 b := false c := null d := {"a": a, "x": [b, c]} ``` ```live:eg/composite_variables:output ``` By defining composite values in terms of variables and references, rules can define abstractions over raw data and other rules. Arrays are ordered collections of values. Arrays in Rego are zero-indexed, and may contain any value, including variable references. ```live:eg/arrays:module:merge_down arr := [1, "two", 3] ``` ```live:eg/arrays/lookup:query:merge_down last := arr[2] ``` ```live:eg/arrays/lookup:output:merge_down ``` Use arrays when order matters and duplicate values should be allowed. Objects are unordered key-value collections. In Rego, any value type can be used as an object key. For example, the following assignment maps port numbers to a list of IP addresses (represented as strings). ```live:eg/objects:module:merge_down ipsbyport := { 80: ["1.1.1.1", "1.1.1.2"], 443: ["2.2.2.1"], } ``` ```live:eg/objects/lookup:query:merge_down ipsbyport[80] ``` ```live:eg/objects/lookup:output:merge_down ``` ```live:eg/objects/iteration:query:merge_down some port; ipsbyport == "2.2.2.1" ``` ```live:eg/objects/iteration:output ``` When Rego values are converted to JSON non-string object keys are marshalled as strings (because JSON does not support non-string object keys). ```live:eg/objects/marshal:query:merge_down ipsbyport ``` ```live:eg/objects/marshal:output ``` In addition to arrays and objects, Rego supports set values. Sets are unordered collections of unique values. Just like other composite values, sets can be defined in terms of scalars, variables, references, and other composite values. For example: ```live:eg/cuboid/sets:query:merge_down s := {cuboid.width, cuboid.height, cuboid.depth} ``` ```live:eg/cuboid/sets:output ``` Set documents are collections of values without keys. OPA represents set documents as arrays when serializing to JSON or other formats that do not support a set data type. The important distinction between sets and arrays or objects is that sets are unkeyed while arrays and objects are keyed, i.e., you cannot refer to the index of an element within a set. When comparing sets, the order of elements does not matter: ```live:eg/setequality:query:mergedown {1,2,3} == {3,1,2} ``` ```live:eg/set_equality:output ``` Because sets are unordered, variables inside sets must be unified with a ground value outside of the set. If the variable is not unified with a ground value outside the set, OPA will complain: ```live:eg/setunification:query:mergedown {1,2,3} == {3,x,2} ``` ```live:eg/setunification:output:expectunsafe_var ``` Because sets share curly-brace syntax with objects, and an empty object is defined with `{}`, an empty set has to be constructed with a different syntax: ```live:eg/setconstruction:query:mergedown count(set()) ``` ```live:eg/set_construction:output ``` Variables are another kind of term in Rego. They appear in both the head and body of rules. Variables appearing in the head of a rule can be thought of as input and output of the rule. Unlike many programming languages, where a variable is either an input or an output, in Rego a variable is simultaneously an input and an output. If a query supplies a value for a variable, that variable is an input, and if the query does not supply a value for a variable, that variable is an output. For example: ```live:eg/variables:module sites := [ {"name": "prod"}, {"name": "smoke1"}, {"name": "dev"} ] q contains name if { some site in sites name := site.name } ``` In this case, we evaluate `q` with a variable `x` (which is not bound to a value). As a result, the query returns all of the values for `x` and all of the values for `q[x]`, which are always the same because `q` is a

```live:eg/variables:query:merge_down q[x] ``` ```live:eg/variables:output ``` On the other hand, if we evaluate `q` with an input value for `name` we can determine whether `name` exists in the document defined by `q`: ```live:eg/variables/value:query:merge_down q["dev"] ``` ```live:eg/variables/value:output ``` Variables appearing in the head of a rule must also appear in a non-negated equality expression within the same rule. This property ensures that if the rule is evaluated and all of the expressions evaluate to true for some set of variable bindings, the variable in the head of the rule will be defined. References are used to access nested documents. The examples in this section use the data defined in the section. The simplest reference contains no variables. For example, the following reference returns the hostname of the second server in the first site document from our example data: ```live:eg/data/ref1:query:merge_down sites[0].servers[1].hostname ``` ```live:eg/data/ref1:output ``` References are typically written using the dot-access style. The canonical form does away with `.` and closely resembles dictionary lookup in a language such as Python: ```live:eg/data/ref2:query:merge_down sites ``` ```live:eg/data/ref2:output ``` Both forms are valid, however, the dot-access style is typically more readable. Note that there are four cases where brackets must be used: String keys containing characters other than `[a-z]`, `[A-Z]`, `[0-9]`, or `_` (underscore). Non-string keys such as numbers, booleans, and null. Variable keys which are described later. Composite keys which are described later. The prefix of a reference identifies the root document for that reference. In the example above this is `sites`. The root document may be: a local variable inside a rule. a rule inside the same package. a document stored in OPA. a documented temporarily provided to OPA as part of a transaction. an array, object or set, e.g. ``. a function call, e.g. `split("a.b.c", ".")[1]`. a . References can include variables as keys. References written this way are used to select a value from every element in a collection. The following reference will select the hostnames of all the servers in our example data: ```live:eg/data/varkey:query:mergedown sites[i].servers[j].hostname ``` ```live:eg/data/var_key:output ``` Conceptually, this is the same as the following imperative (Python) code: ```python def hostnames(sites): result = [] for site in sites: for server in site.servers: result.append(server.hostname) return result ``` In the reference above, we effectively used variables named `i` and `j` to iterate the collections. If the variables are unused outside the reference, we prefer to replace them with an underscore (`_`) character. The reference above can be rewritten as: ```live:eg/data/mehkey:query:mergedown sites[].servers[].hostname ``` ```live:eg/data/meh_key:output ``` The underscore is special because it cannot be referred to by other parts of the rule, e.g., the other side of the expression, another expression, etc. The underscore can be thought of as a special iterator. Each time an underscore is specified, a new iterator is instantiated. Under the hood, OPA translates the `_` character to a unique variable name that does not conflict with variables and rules that are in scope. References can include as keys if the key is being used to refer into a set. Composite keys may not be used in refs for base data documents, they are only valid for references into virtual documents. This is useful for checking for the presence of composite values within a set, or extracting all values within a set matching some pattern. For example: ```live:eg/composite_key:module s := {[1, 2], [1, 4], [2, 6]} ``` ```live:eg/compositekey/1:query:mergedown s[[1, 2]] ``` ```live:eg/composite_key/1:output ``` ```live:eg/compositekey/2:query:mergedown s[[1, x]] ``` ```live:eg/composite_key/2:output ``` Rules are often written in terms of multiple expressions that contain references to documents. In the following example, the rule defines a set of arrays where each array contains an application name and a hostname of a server where the application is

```live:eg/data/multi:module appsandhostnames contains [name, hostname] if { some i, j, k name := apps[i].name server := apps[i].servers[_] sites[j].servers[k].name == server hostname := sites[j].servers[k].hostname } ``` The result: ```live:eg/data/multi:query:merge_down appsandhostnames[x] ``` ```live:eg/data/multi:output ``` Dont worry about understanding everything in this example right now. There are just two important points: Several variables appear more than once in the body. When a variable is used in multiple locations, OPA will only produce documents for the rule with the variable bound to the same value in all expressions. The rule is joining the `apps` and `sites` documents implicitly. In Rego (and other languages based on Datalog), joins are implicit. Using a different key on the same array or object provides the equivalent of self-join in SQL. For example, the following rule defines a document containing apps deployed on the same site as `"mysql"`: ```live:eg/data/self_join:module same_site contains apps[k].name if { some i, j, k apps[i].name == "mysql" server := apps[i].servers[_] server == sites[j].servers[_].name otherserver := sites[j].servers[].name server != other_server otherserver == apps[k].servers[] } ``` The result: ```live:eg/data/selfjoin:query:mergedown same_site[x] ``` ```live:eg/data/self_join:output ``` Comprehensions provide a concise way of building from sub-queries. Like , comprehensions consist of a head and a body. The body of a comprehension can be understood in exactly the same way as the body of a rule, that is, one or more expressions that must all be true in order for the overall body to be true. When the body evaluates to true, the head of the comprehension is evaluated to produce an element in the result. The body of a comprehension is able to refer to variables defined in the outer body. For example: ```live:eg/data/comprehensionintro:query:mergedown region := "west" names := [name | sites[i].region == region; name := sites[i].name] ``` ```live:eg/data/comprehension_intro:output ``` In the above query, the second expression contains an that refers to the `region` variable. The region variable will be bound in the outer body. When a comprehension refers to a variable in an outer body, OPA will reorder expressions in the outer body so that variables referred to in the comprehension are bound by the time the comprehension is evaluated. Comprehensions are similar to the same constructs found in other languages like Python. For example, we could write the above comprehension in Python as follows: ```python names = [site.name for site in sites if site.region == "west"] ``` Comprehensions are often used to group elements by some key. A common use case for comprehensions is to assist in computing aggregate values (e.g., the number of containers running on a host). Array Comprehensions build array values out of sub-queries. Array Comprehensions have the form: ``` [ <term> | <body> ] ``` For example, the following rule defines an object where the keys are application names and the values are hostnames of servers where the application is deployed. The hostnames of servers are represented as an array. ```live:eg/data/array_comprehension:module apptohostnames[app_name] := hostnames if { app := apps[_] app_name := app.name hostnames := [hostname | name := app.servers[_] s := sites[].servers[] s.name == name hostname := s.hostname] } ``` The result: ```live:eg/data/arraycomprehension:query:readonly,merge_down apptohostnames[app] ``` ```live:eg/data/array_comprehension:output ``` Object Comprehensions build object values out of sub-queries. Object Comprehensions have the form: ``` { <key>: <term> | <body> } ``` We can use Object Comprehensions to write the rule from above as a comprehension instead: ```live:eg/data/object_comprehension:module apptohostnames := {app.name: hostnames | app := apps[_] hostnames := [hostname | name := app.servers[_] s := sites[].servers[] s.name == name hostname :=

} ``` The result is the same: ```live:eg/data/objectcomprehension:query:readonly,merge_down apptohostnames[app] ``` ```live:eg/data/object_comprehension:output ``` Object comprehensions are not allowed to have conflicting entries, similar to rules: ```live:eg/data/objectcomprehensionconflicting:query:merge_down {"foo": y | z := [1, 2, 3]; y := z[_] } ``` ```live:eg/data/objectcomprehensionconflicting:output:expect_conflict ``` Set Comprehensions build set values out of sub-queries. Set Comprehensions have the form: ``` { <term> | <body> } ``` For example, to construct a set from an array: ```live:eg/data/setcomprehension:module:mergedown a := [1, 2, 3, 4, 3, 4, 3, 4, 5] b := {x | x = a[_]} ``` ```live:eg/data/set_comprehension:query:hidden a b ``` ```live:eg/data/set_comprehension:output ``` Rules define the content of in OPA. When OPA evaluates a rule, we say OPA generates the content of the document that is defined by the rule. The sample code in this section make use of the data defined in . {{< info >}} Rule definitions can be more expressive when using the future keywords `contains` and `if` which will become standard in . To follow along as-is, please import the keywords, or preferably, import `rego.v1`: ```live:eg/data/info:module:read_only import rego.v1 ``` for more information. {{< /info >}} The following rule defines a set containing the hostnames of all servers: ```live:eg/data/rules:module:read_only hostnames contains name if { name := sites[].servers[].hostname } ``` When we query for the content of `hostnames` we see the same data as we would if we queried using the `sites[].servers[].hostname` reference directly: ```live:eg/data/rules:query:readonly,mergedown hostnames[name] ``` ```live:eg/data/rules:output ``` This example introduces a few important aspects of Rego. First, the rule defines a set document where the contents are defined by the variable `name`. We know this rule defines a set document because the head only includes a key. All rules have the following form (where key, value, and body are all optional): ``` <name> <key>? <value>? <body>? ``` For a more formal definition of the rule syntax, see the document. Second, the `sites[].servers[].hostname` fragment selects the `hostname` attribute from all the objects in the `servers` collection. From reading the fragment in isolation we cannot tell whether the fragment refers to arrays or objects. We only know that it refers to a collections of values. Third, the `name := sites[].servers[].hostname` expression binds the value of the `hostname` attribute to the variable `name`, which is also declared in the head of the rule. Rules that define objects are very similar to rules that define sets. ```live:eg/data/rule_objects:module appsbyhostname[hostname] := app if { some i server := sites[].servers[] hostname := server.hostname apps[i].servers[_] == server.name app := apps[i].name } ``` The rule above defines an object that maps hostnames to app names. The main difference between this rule and one which defines a set is the rule head: in addition to declaring a key, the rule head also declares a value for the document. The result: ```live:eg/data/ruleobjects:query:mergedown appsbyhostname["helium"] ``` ```live:eg/data/rule_objects:output ``` Using the is optional here. The same rule can be defined as follows: ```live:eg/data/rule_objects2:module appsbyhostname[hostname] := app { some i server := sites[].servers[] hostname := server.hostname apps[i].servers[_] == server.name app := apps[i].name } ``` A rule may be defined multiple times with the same name. When a rule is defined this way, we refer to the rule definition as incremental because each definition is additive. The document produced by incrementally defined rules is the union of the documents produced by each individual rule. For example, we can write a rule that abstracts over our `servers` and `containers` data as `instances`: ```live:eg/data/incremental_rule:module instances contains instance if { server := sites[].servers[] instance := {"address": server.hostname, "name": server.name} } instances contains instance if { container := containers[_] instance := {"address": container.ipaddress, "name": container.name} } ``` If the head of the rule is same, we can chain multiple rule bodies together to obtain the same result. We don't recommend using this form

```live:eg/data/incrementalrulenr:module:read_only instances contains instance if { server := sites[].servers[] instance := {"address": server.hostname, "name": server.name} } { container := containers[_] instance := {"address": container.ipaddress, "name": container.name} } ``` An incrementally defined rule can be intuitively understood as `<rule-1> OR <rule-2> OR ... OR <rule-N>`. The result: ```live:eg/data/incrementalrule:query:readonly,merge_down instances[x] ``` ```live:eg/data/incremental_rule:output ``` In addition to rules that partially define sets and objects, Rego also supports so-called complete definitions of any type of document. Rules provide a complete definition by omitting the key in the head. Complete definitions are commonly used for constants: ```live:complete:module:read_only pi := 3.14159 ``` {{< info >}} Rego allows authors to omit the body of rules. If the body is omitted, it defaults to true. {{< /info >}} Documents produced by rules with complete definitions can only have one value at a time. If evaluation produces multiple values for the same document, an error will be returned. For example: ```live:eg/conflicting_rules:module user := "bob" power_users := {"alice", "bob", "fred"} restricted_users := {"bob", "kim"} maxmemory := 32 if powerusers[user] maxmemory := 4 if restrictedusers[user] ``` Error: ```live:eg/conflictingrules:output:expectconflict ``` OPA returns an error in this case because the rule definitions are in conflict. The value produced by max_memory cannot be 32 and 4 at the same time. The documents produced by rules with complete definitions may still be undefined: ```live:eg/conflictingrules/undefined:query:mergedown max_memory with user as "johnson" ``` ```live:eg/conflictingrules/undefined:output:expectundefined ``` In some cases, having an undefined result for a document is not desirable. In those cases, policies can use the to provide a fallback value. As a shorthand for defining nested rule structures, it's valid to use references as rule heads: ```live:eg/ref_heads:module fruit.apple.seeds := 12 fruit.orange.color := "orange" ``` This module defines two complete rules, `data.example.fruit.apple.seeds` and `data.example.fruit.orange.color`: ```live:eg/refheads:query:mergedown data.example ``` ```live:eg/ref_heads:output ``` Any term, except the very first, in a rule head's reference can be a variable. These variables can be assigned within the rule, just as for any other partial rule, to dynamically construct a nested collection of objects. Input: ```live:generalrefhead:input { "users": [ { "id": "alice", "role": "employee", "country": "USA" }, { "id": "bob", "role": "customer", "country": "USA" }, { "id": "dora", "role": "admin", "country": "Sweden" } ], "admins": [ { "id": "charlie" } ] } ``` Module: ```live:generalrefhead:module package example import rego.v1 usersbyrole := user if { some user in input.users id := user.id role := user.role } usersbyrole.admin[id] := user if { some user in input.admins id := user.id } usersbycountry[country] contains user.id if { some user in input.users country := user.country } ``` Output: ```live:generalrefhead:output ``` The first variable declared in a rule head's reference divides the reference in a leading constant portion and a trailing dynamic portion. Other rules are allowed to overlap with the dynamic portion (dynamic extent) without causing a compile-time conflict. ```live:generalrefhead_conflict:module package example import rego.v1 p[x].r := y if { x := "q" y := 1 } p.q.r := 2 ``` Error: ```live:generalrefheadconflict:output:expecteval_error ``` In the above example, rule `R2` overlaps with the dynamic portion of rule `R1`'s reference (`[x].r`), which is allowed at compile-time, as these rules aren't guaranteed to produce conflicting output. However, as `R1` defines `x` as `"q"` and `y` as `1`, a conflict will be reported at evaluation-time. Conflicts are detected at compile-time, where possible, between rules even if they are within the dynamic extent of another rule. ```live:generalrefhead_conflict2:module package example import rego.v1 p[x].r := y if { x := "foo" y := 1 } p.q.r := 2

:= 3 ``` Error: ```live:generalrefheadconflict2:output:expectrego_error ``` Above, `R2` and `R3` are within the dynamic extent of `R1`, but are in conflict with each other, which is detected at compile-time. Rules are not allowed to overlap with object values of other rules. ```live:generalrefhead_conflict3:module package example import rego.v1 p.q.r := {"s": 1} p[x].r.t := 2 if { x := "q" } ``` Error: ```live:generalrefheadconflict3:output:expecteval_error ``` In the above example, `R1` is within the dynamic extent of `R2` and a conflict cannot be detected at compile-time. However, at evaluation-time `R2` will attempt to inject a value under key `t` in an object value defined by `R1`. This is a conflict, as rules are not allowed to modify or replace values defined by other rules. We won't get a conflict if we update the policy to the following: ```live:generalrefhead_conflict4:module package example import rego.v1 p.q.r.s := 1 p[x].r.t := 2 if { x := "q" } ``` As `R1` is now instead defining a value within the dynamic extent of `R2`'s reference, which is allowed: ```live:generalrefhead_conflict4:output ``` Rego supports user-defined functions that can be called with the same semantics as . They have access to both the and . For example, the following function will return the result of trimming the spaces from a string and then splitting it by periods. ```live:eg/basicfunction:module:mergedown trimandsplit(s) := x if { t := trim(s, " ") x := split(t, ".") } ``` ```live:eg/basicfunction:query:mergedown trimandsplit(" foo.bar ") ``` ```live:eg/basic_function:output ``` Functions may have an arbitrary number of inputs, but exactly one output. Function arguments may be any kind of term. For example, suppose we have the following function: ```live:eg/functioninput:module:readonly foo([x, {"bar": y}]) := z if { z := {x: y} } ``` The following calls would produce the logical mappings given: | Call | ``x`` | ``y`` | | - | - | -- | | ``z := foo(a)`` | ``a[0]`` | ``a[1].bar`` | | ``z := foo(["5", {"bar": "hello"}])`` | ``"5"`` | ``"hello"`` | | ``z := foo(["5", {"bar": [1, 2, 3, ["foo", "bar"]]}])`` | ``"5"`` | ``[1, 2, 3, ["foo", "bar"]]`` | If you need multiple outputs, write your functions so that the output is an array, object or set containing your results. If the output term is omitted, it is equivalent to having the output term be the literal `true`. Furthermore, `if` can be used to write shorter definitions. That is, the function declarations below are equivalent: ```live:eg/functionoutputunset:module:read_only f(x) if { x == "foo" } f(x) if x == "foo" f(x) := true if { x == "foo" } f(x) := true if x == "foo" ``` The outputs of user functions have some additional limitations, namely that they must resolve to a single value. If you write a function that has multiple possible bindings for an output variable, you will get a conflict error: ```live:eg/functionsingleoutput:module:merge_down p(x) := y if { y := x[_] } ``` ```live:eg/functionsingleoutput:query:merge_down p([1, 2, 3]) ``` ```live:eg/functionsingleoutput:output:expect_conflict ``` It is possible in Rego to define a function more than once, to achieve a conditional selection of which function to execute: Functions can be defined incrementally. ```live:eg/doublefunctiondefine:module q(1, x) := y if { y := x } q(2, x) := y if { y := x*4 } ``` ```live:eg/doublefunctiondefine/1:query:merge_down q(1, 2) ``` ```live:eg/doublefunctiondefine/1:output ``` ```live:eg/doublefunctiondefine/2:query:merge_down q(2, 2) ``` ```live:eg/doublefunctiondefine/2:output ``` A given function call will execute all functions that match the signature

If a call matches multiple functions, they must produce the same output, or else a conflict error will occur: ```live:eg/doublefunctiondefinediffout:module r(1, x) := y if { y := x } r(x, 2) := y if { y := x*4 } ``` ```live:eg/doublefunctiondefinediffout:query:merge_down r(1, 2) ``` ```live:eg/doublefunctiondefinediffout:output:expect_conflict ``` On the other hand, if a call matches no functions, then the result is undefined. ```live:eg/doublefunctiondefine_undefined:module s(x, 2) := y if { y := x * 4 } ``` ```live:eg/doublefunctiondefineundefined/1:query:mergedown s(5, 2) ``` ```live:eg/doublefunctiondefine_undefined/1:output ``` ```live:eg/doublefunctiondefineundefined/2:query:mergedown s(5, 3) ``` ```live:eg/doublefunctiondefineundefined/2:output:expectundefined ``` Rego does not currently support the overloading of functions by the number of parameters. If two function definitions are given with the same function name but different numbers of parameters, a compile-time type error is generated. ```live:eg/functionoverloadingerror:module r(x) := result if { result := 2*x } r(x, y) := result if { result := 2x + 3y } ``` ```live:eg/functionoverloadingerror:output:expectregotype_error ``` The error can be avoided by using different function names. ```live:eg/functionoverloadingnaming:module r_1(x) := result if { result := 2*x } r_2(x, y) := result if { result := 2x + 3y } ``` ```live:eg/functionoverloadingnaming:query:merge_down [ r_1(10), r_2(10, 1) ] ``` ```live:eg/functionoverloadingnaming:output ``` In the unusual case that it is critical to use the same name, the function could be made to take the list of parameters as a single array. However, this approach is not generally recommended because it sacrifices some helpful compile-time checking and can be quite error-prone. ```live:eg/functionoverloadingarray:module r(params) := result if { count(params) == 1 result := 2*params[0] } r(params) := result if { count(params) == 2 result := 2params[0] + 3params[1] } ``` ```live:eg/functionoverloadingarray:query:merge_down [ r([10]), r([10, 1]) ] ``` ```live:eg/functionoverloadingarray:output ``` To generate the content of a , OPA attempts to bind variables in the body of the rule such that all expressions in the rule evaluate to True. This generates the correct result when the expressions represent assertions about what states should exist in the data stored in OPA. In some cases, you want to express that certain states should not exist in the data stored in OPA. In these cases, negation must be used. For safety, a variable appearing in a negated expression must also appear in another non-negated equality expression in the rule. OPA will reorder expressions to ensure that negated expressions are evaluated after other non-negated expressions with the same variables. OPA will reject rules containing negated expressions that do not meet the safety criteria described above. The simplest use of negation involves only scalar values or variables and is equivalent to complementing the operator: ```live:eg/simple_negation:module t if { greeting := "hello" not greeting == "goodbye" } ``` The result: ```live:eg/simplenegation:query:readonly,merge_down t ``` ```live:eg/simple_negation:output ``` Negation is required to check whether some value does not exist in a collection: `not p["foo"]`. That is not the same as complementing the `==` operator in an expression `p[] == "foo"` which yields `p[] != "foo"` which means for any item in `p`, return true if the item is not `"foo"`. See more details . For example, we can write a rule that defines a document containing names of apps not deployed on the `"prod"` site: ```live:eg/data/negation:module prod_servers contains name if { some site in sites site.name == "prod" some server in site.servers name := server.name } appsinprod contains name if { some site in sites some app in apps name := app.name some server in app.servers prod_servers[server] } appsnotin_prod contains name if { some app in apps name := app.name not appsinprod[name] } ``` The result: ```live:eg/data/negation:query:readonly,mergedown appsnotin_prod[name] ``` ```live:eg/data/negation:output ``` Rego allows for several ways to express universal quantification. For example, imagine you want to express a policy that says (in English): ``` There must be no apps named "bitcoin-miner". ``` The most expressive way to state this in Rego is using the `every` keyword: ```live:eg/data/everyalternative:module:readonly nobitcoinminersusingevery if { every app in apps {

!= "bitcoin-miner" } } ``` Variables in Rego are existentially quantified by default: when you write ```live:eg/data/everyalternative/1:query:mergedown array := ["one", "two", "three"]; array[i] == "three" ``` The query will be satisfied if there is an `i` such that the query's expressions are simultaneously satisfied. ```live:eg/data/every_alternative/1:output ``` Therefore, there are other ways to express the desired policy. For this policy, you can also define a rule that finds if there exists a bitcoin-mining app (which is easy using the `some` keyword). And then you use negation to check that there is NO bitcoin-mining app. Technically, you're using 2 negations and an existential quantifier, which is logically the same as a universal quantifier. For example: ```live:eg/data/correct_negation:module nobitcoinminersusingnegation if not anybitcoinminers anybitcoinminers if { some app in apps app.name == "bitcoin-miner" } ``` ```live:eg/data/correctnegation/1:query:mergedown nobitcoinminersusingnegation with apps as [{"name": "web"}] ``` ```live:eg/data/correct_negation/1:output ``` ```live:eg/data/correctnegation/2:query:mergedown nobitcoinminersusingnegation with apps as [{"name": "bitcoin-miner"}, {"name": "web"}] ``` ```live:eg/data/correctnegation/2:output:expectundefined ``` {{< info >}} The `undefined` result above is expected because we did not define a default value for `nobitcoinminersusingnegation`. Since the body of the rule fails to match, there is no value generated. {{< /info >}} A common mistake is to try encoding the policy with a rule named `nobitcoinminers` like so: ```live:eg/data/incorrectnobitcoin:module:read_only nobitcoinminers if { app := apps[_] app.name != "bitcoin-miner" # THIS IS NOT CORRECT. } ``` It becomes clear that this is incorrect when you use the keyword, because the rule is true whenever there is SOME app that is not a bitcoin-miner: ```live:eg/data/incorrectnobitcoin_some:module nobitcoinminers if { some app in apps app.name != "bitcoin-miner" } ``` You can confirm this by querying the rule: ```live:eg/data/incorrectnobitcoinsome:query:mergedown nobitcoinminers with apps as [{"name": "bitcoin-miner"}, {"name": "web"}] ``` ```live:eg/data/incorrectnobitcoin_some:output ``` The reason the rule is incorrect is that variables in Rego are _existentially quantified_. This means that rule bodies and queries express FOR ANY and not FOR ALL. To express FOR ALL in Rego complement the logic in the rule body (e.g., `!=` becomes `==`) and then complement the check using negation (e.g., `nobitcoinminers` becomes `not anybitcoinminers`). Alternatively, we can implement the same kind of logic inside a single rule using . ```live:eg/data/comprehesionalternative:module:readonly nobitcoinminersusingcomprehension if { bitcoin_miners := {app | some app in apps; app.name == "bitcoin-miner"} count(bitcoin_miners) == 0 } ``` {{< info >}} Whether you use negation, comprehensions, or `every` to express FOR ALL is up to you. The `every` keyword should lend itself nicely to a rule formulation that closely follows how requirements are stated, and thus enhances your policy's readability. The comprehension version is more concise than the negation variant, and does not require a helper rule while the negation version is more verbose but a bit simpler and allows for more complex ORs. {{< /info >}} In Rego, policies are defined inside modules. Modules consist of: Exactly one declaration. Zero or more statements. Zero or more definitions. Modules are typically represented in Unicode text and encoded in UTF-8. Comments begin with the `#` character and continue until the end of the line. Packages group the rules defined in one or more modules into a particular namespace. Because rules are namespaced they can be safely shared across projects. Modules contributing to the same package do not have to be located in the same directory. The rules defined in a module are automatically exported. That is, they can be queried under OPAs provided the appropriate package is given. For example, given the following module: ```live:packagedeclaration:module:readonly package opa.examples pi := 3.14159 ``` The `pi` document can be queried via the Data API: ```http GET https://example.com/v1/data/opa/examples/pi

``` Valid package names are variables or references that only contain string operands. For example, these are all valid package names: ``` package foo package foo.bar package foo.bar.baz package foo["bar.baz"].qux ``` These are invalid package names: ``` package 1foo # not a variable package foo[1].bar # contains non-string operand ``` For more details see the language . Import statements declare dependencies that modules have on documents defined outside the package. By importing a document, the identifiers exported by that document can be referenced within the current module. All modules contain implicit statements which import the `data` and `input` documents. Modules use the same syntax to declare dependencies on . ```live:importdata:module:readonly package opa.examples import rego.v1 # uses 'in' and 'contains' and 'if' import data.servers http_servers contains server if { some server in servers "http" in server.protocols } ``` Similarly, modules can declare dependencies on query arguments by specifying an import path that starts with `input`. ```live:importinput:module:readonly package opa.examples import rego.v1 import input.user import input.method allow if user == "alice" allow if { user == "bob" method == "GET" } allow if { method == "GET" input.user in data.roles["dev"] } allow if { user == "catherine" day := time.weekday(time.now_ns()) day in ["Saturday", "Sunday"] } ``` Imports can include an optional `as` keyword to handle namespacing issues: ```live:importnamespacing:module:readonly package opa.examples import rego.v1 import data.servers as my_servers http_servers contains server if { some server in my_servers "http" in server.protocols } ``` To ensure backwards-compatibility, new keywords (like `every`) were introduced slowly. In the first stage, users could opt-in to using the new keywords via a special import: `import future.keywords` introduces all future keywords, and `import future.keywords.x` only introduces the `x` keyword -- see below for all known future keywords. Recommended* `import rego.v1` introduces all future keywords, and enforces the use of `if` and `contains` in rule heads where applicable. {{< info >}} It is recommended to use the `rego.v1` import instead of `future.keywords` imports, as this will ensure that your policy is compatible with the future release of . If the `rego.v1` import is present in a module, then `future.keywords` and `future.keywords.*` import is implied, and not allowed. {{< /info >}} In , the new keywords will become standard, and the import will become a no-op that can safely be removed. This should give all users ample time to update their policies, so that the new keyword will not cause clashes with existing variable names. {{< info >}} Note that some future keyword imports have consequences on pretty-printing: If `contains` or `if` are imported, the pretty-printer will use them as applicable when formatting the modules. {{< /info >}} This is the list of all future keywords that will become standard in OPA v1.0: More expressive membership and existential quantification keyword: ```live:eg/kws/in:module:read_only deny { some x in input.roles # iteration x == "denylisted-role" } deny { "denylisted-role" in input.roles # membership check } ``` `in` was introduced in . See for details. Expressive universal quantification keyword: ```live:eg/kws/every:module:read_only allowed := {"customer", "admin"} allow { every role in input.roles { role.name in allowed } } ``` There is no need to also import `future.keywords.in`, that is implied by importing `future.keywords.every`. `every` was introduced in . See for details. This keyword allows more expressive rule heads: ```live:eg/kws/if:module:read_only deny if input.token != "secret" ``` `if` was introduced in . This keyword allows more expressive rule heads for partial set rules: ```live:eg/kws/contains:module:read_only deny contains msg { msg := "forbidden" } ``` `contains` was introduced in . The `some` keyword allows queries to explicitly declare local

Use the `some` keyword in rules that contain unification statements or references with variable operands if variables contained in those statements are not declared using `:=` . | Statement | Example | Variables | | | | | | Unification | `input.a = [["b", x], [y, "c"]]` | `x` and `y` | | Reference with variable operands | `data.foo[i].bar[j]` | `i` and `j` | For example, the following rule generates tuples of array indices for servers in the "west" region that contain "db" in their name. The first element in the tuple is the site index and the second element is the server index. ```live:eg/data/some:module tuples contains [i, j] if { some i, j sites[i].region == "west" server := sites[i].servers[j] # note: 'server' is local because it's declared with := contains(server.name, "db") } ``` If we query for the tuples we get two results: ```live:eg/data/some/i:query:hidden tuples ``` ```live:eg/data/some/i:output ``` Since we have declared `i`, `j`, and `server` to be local, we can introduce rules in the same package without affecting the result above: ```live:eg/data/some/i:module i := 1 ``` If we had not declared `i` with the `some` keyword, introducing the `i` rule above would have changed the result of `tuples` because the `i` symbol in the body would capture the global value. Try removing `some i, j` and see what happens! The `some` keyword is not required but it's recommended to avoid situations like the one above where introduction of a rule inside a package could change behaviour of other rules. For using the `some` keyword with iteration, see . {{< info >}} `every` is a future keyword and needs to be imported. `import rego.v1` or, alternatively, `import future.keywords.every` introduces the `every` keyword described here. for more information. {{< /info >}} ```live:eg/data/every0:module:merge_down nameswithdev if { some site in sites site.name == "dev" every server in site.servers { endswith(server.name, "-dev") } } ``` ```live:eg/data/every0:query:merge_down nameswithdev ``` ```live:eg/data/every0:output ``` The `every` keyword takes an (optional) key argument, a value argument, a domain, and a block of further queries, its "body". The keyword is used to explicitly assert that its body is true for any element in the domain. It will iterate over the domain, bind its variables, and check that the body holds for those bindings. If one of the bindings does not yield a successful evaluation of the body, the overall statement is undefined. If the domain is empty, the overall statement is true. Evaluating `every` does not introduce new bindings into the rule evaluation. Used with a key argument, the index, or property name (for objects), comes into the scope of the body evaluation: ```live:eg/every1:module:merge_down array_domain if { every i, x in [1, 2, 3] { x-i == 1 } # array domain } object_domain if { every k, v in {"foo": "bar", "fox": "baz" } { # object domain startswith(k, "f") startswith(v, "b") } } set_domain if { every x in {1, 2, 3} { x != 4 } # set domain } ``` ```live:eg/every1:output ``` Semantically, `every x in xs { p(x) }` is equivalent to, but shorter than, a "not-some-not" construct using a helper rule: ```live:eg/every2:module:merge_down xs := [2, 2, 4, 8] largerthanone(x) := x > 1 rule_every if { every x in xs { largerthanone(x) } } notlessorequalone if not lte_one lte_one if { some x in xs not largerthanone(x) } ``` ```live:eg/every2:output ``` Negating `every` is forbidden. If you desire to express `not every x in xs { p(x) }` please use `some x in xs; not p(x)` instead. The `with` keyword allows queries to programmatically specify values nested under the or the , or built-in

For example, given the simple authorization policy in the section, we can write a query that checks whether a particular request would be allowed: ```live:importinput/1:query:mergedown allow with input as {"user": "alice", "method": "POST"} ``` ```live:import_input/1:output ``` ```live:importinput/2:query:mergedown allow with input as {"user": "bob", "method": "GET"} ``` ```live:import_input/2:output ``` ```live:importinput/3:query:mergedown not allow with input as {"user": "bob", "method": "DELETE"} ``` ```live:import_input/3:output ``` ```live:importinput/4:query:mergedown allow with input as {"user": "charlie", "method": "GET"} with data.roles as {"dev": ["charlie"]} ``` ```live:import_input/4:output ``` ```live:importinput/5:query:mergedown not allow with input as {"user": "charlie", "method": "GET"} with data.roles as {"dev": ["bob"]} ``` ```live:import_input/5:output ``` ```live:importinput/6:query:mergedown allow with input as {"user": "catherine", "method": "GET"} with data.roles as {"dev": ["bob"]} with time.weekday as "Sunday" ``` ```live:import_input/6:output ``` The `with` keyword acts as a modifier on expressions. A single expression is allowed to have zero or more `with` modifiers. The `with` keyword has the following syntax: ``` <expr> with <target-1> as <value-1> [with <target-2> as <value-2> [...]] ``` The `<target>`s must be references to values in the input document (or the input document itself) or data document, or references to functions (built-in or not). {{< info >}} When applied to the `data` document, the `<target>` must not attempt to partially define virtual documents. For example, given a virtual document at path `data.foo.bar`, the compiler will generate an error if the policy attempts to replace `data.foo.bar.baz`. {{< /info >}} The `with` keyword only affects the attached expression. Subsequent expressions will see the unmodified value. The exception to this rule is when multiple `with` keywords are in-scope like below: ```live:multiplewith:module:readonly inner := [x, y] if { x := input.foo y := input.bar } middle := [a, b] if { a := inner with input.foo as 100 b := input } outer := result if { result := middle with input as {"foo": 200, "bar": 300} } ``` When `<target>` is a reference to a function, like `http.send`, then its `<value>` can be any of the following: a value: `with http.send as {"body": {"success": true }}` a reference to another function: `with http.send as mockhttpsend` a reference to another (possibly custom) built-in function: `with custombuiltin as lessstrictcustombuiltin` a reference to a rule that will be used as the value. When the replacement value is a function, its arity needs to match the replaced function's arity; and the types must be compatible. Replacement functions can call the function they're replacing without causing recursion. See the following example: ```live:eg/withbuiltins:module:readonly f(x) := count(x) mock_count(x) := 0 if "x" in x mock_count(x) := count(x) if not "x" in x ``` ```live:eg/withbuiltins/1:query:mergedown f([1, 2, 3]) with count as mock_count ``` ```live:eg/with_builtins/1:output ``` ```live:eg/withbuiltins/2:query:mergedown f(["x", "y", "z"]) with count as mock_count ``` ```live:eg/with_builtins/2:output ``` Each replacement function evaluation will start a new scope: it's valid to use `with <builtin1> as ...` in the body of the replacement function -- for example: ```live:eg/withbuiltinsnested:module:read_only f(x) := count(x) if { ruleusingconcat with concat as "foo,bar" } mock_count(x) := 0 if "x" in x mock_count(x) := count(x) if not "x" in x ruleusingconcat if { concat(",", input.x) == "foo,bar" } ``` ```live:eg/withbuiltinsnested/1:query:merge_down f(["x", "y", "z"]) with count as mock_count with input.x as ["baz"] ``` ```live:eg/withbuiltinsnested/1:output ``` Note that function replacement via `with` does not affect the evaluation of the function arguments: if `input.x` is undefined, the replacement of `concat` does not change the result of the evaluation: ```live:eg/withbuiltinsnested/2:query:merge_down count(input.x) with count as 3 with input.x as ["x"] ``` ```live:eg/withbuiltinsnested/2:output ``` ```live:eg/withbuiltinsnested/3:query:merge_down count(input.x) with count as 3 with input as {} ``` ```live:eg/withbuiltinsnested/3:output:expect_undefined ``` The `default` keyword allows policies to define a default value for documents produced by rules with

The default value is used when all of the rules sharing the same name are undefined. For example: ```live:eg/default:module default allow := false allow if { input.user == "bob" input.method == "GET" } allow if input.user == "alice" ``` When the `allow` document is queried, the return value will be either `true` or `false`. ```live:eg/default:query:hidden allow ``` ```live:eg/default:input:merge_down { "user": "bob", "method": "POST" } ``` ```live:eg/default:output ``` Without the default definition, the `allow` document would simply be undefined for the same input. When the `default` keyword is used, the rule syntax is restricted to: ``` default <name> := <term> ``` The term may be any scalar, composite, or comprehension value but it may not be a variable or reference. If the value is a composite then it may not contain variables or references. Comprehensions however may, as the result of a comprehension is never undefined. Similar to rules, the `default` keyword can be applied to functions as well. For example: ```live:eg/defaultfunc:module:read_only default clamppositive() := 0 clamp_positive(x) := x if { x > 0 } ``` When `clamp_positive` is queried, the return value will be either the argument provided to the function or `0`. The value of a `default` function follows the same conditions as that of a `default` rule. In addition, a `default` function satisfies the following properties: same arity as other functions with the same name arguments should only be plain variables ie. no composite values argument names should not be repeated The ``else`` keyword is a basic control flow construct that gives you control over rule evaluation order. Rules grouped together with the ``else`` keyword are evaluated until a match is found. Once a match is found, rule evaluation does not proceed to rules further in the chain. The ``else`` keyword is useful if you are porting policies into Rego from an order-sensitive system like IPTables. ```live:eg/else:module authorize := "allow" if { input.user == "superuser" # allow 'superuser' to perform any operation. } else := "deny" if { input.path[0] == "admin" # disallow 'admin' operations... input.source_network == "external" # from external networks. } # ... more rules ``` ```live:eg/else:query:hidden authorize ``` In the example below, evaluation stops immediately after the first rule even though the input matches the second rule as well. ```live:eg/else/1:input:merge_down { "path": [ "admin", "exec_shell" ], "source_network": "external", "user": "superuser" } ``` ```live:eg/else/1:output ``` In the next example, the input matches the second rule (but not the first) so evaluation continues to the second rule before stopping. ```live:eg/else/2:input:merge_down { "path": [ "admin", "exec_shell" ], "source_network": "external", "user": "alice" } ``` ```live:eg/else/2:output ``` The `else` keyword may be used repeatedly on the same rule and there is no limit imposed on the number of `else` clauses on a rule. {{< info >}} To ensure backwards-compatibility, new keywords (like `in`) are introduced slowly. In the first stage, users can opt-in to using the new keywords via a special import: `import rego.v1` or, alternatively, `import future.keywords.in` introduces the `in` keyword described here. for more information. {{< /info >}} The membership operator `in` lets you check if an element is part of a collection (array, set, or object). It always evaluates to `true` or `false`: ```live:eg/member1:module:merge_down p := [x, y, z] if { x := 3 in [1, 2, 3] # array y := 3 in {1, 2, 3} # set z := 3 in {"foo": 1, "bar":

# object } ``` ```live:eg/member1:output ``` When providing two arguments on the left-hand side of the `in` operator, and an object or an array on the right-hand side, the first argument is taken to be the key (object) or index (array), respectively: ```live:eg/member1c:module:merge_down p := [x, y] if { x := "foo", "bar" in {"foo": "bar"} # key, val with object y := 2, "baz" in ["foo", "bar", "baz"] # key, val with array } ``` ```live:eg/member1c:output ``` Note that in list contexts, like set or array definitions and function arguments, parentheses are required to use the form with two left-hand side arguments -- compare: ```live:eg/member1d:module:merge_down p := x if { x := { 0, 2 in [2] } } q := x if { x := { (0, 2 in [2]) } } w := x if { x := g((0, 2 in [2])) } z := x if { x := f(0, 2 in [2]) } f(x, y) := sprintf("two function arguments: %v, %v", [x, y]) g(x) := sprintf("one function argument: %v", [x]) ``` ```live:eg/member1d:output ``` Combined with `not`, the operator can be handy when asserting that an element is not member of an array: ```live:eg/member1a:module:merge_down deny if not "admin" in input.user.roles test_deny if { deny with input.user.roles as ["operator", "user"] } ``` ```live:eg/member1a:output ``` Note that expressions using the `in` operator always return `true` or `false`, even when called in non-collection arguments: ```live:eg/member1b:module:merge_down q := x if { x := 3 in "three" } ``` ```live:eg/member1b:output ``` Using the `some` variant, it can be used to introduce new variables based on a collections' items: ```live:eg/member2:module:merge_down p contains x if { some x in ["a", "r", "r", "a", "y"] } q contains x if { some x in {"s", "e", "t"} } r contains x if { some x in {"foo": "bar", "baz": "quz"} } ``` ```live:eg/member2:output ``` Furthermore, passing a second argument allows you to work with object keys and array indices: ```live:eg/member3:module:merge_down p contains x if { some x, "r" in ["a", "r", "r", "a", "y"] # key variable, value constant } q[x] := y if { some x, y in ["a", "r", "r", "a", "y"] # both variables } r[y] := x if { some x, y in {"foo": "bar", "baz": "quz"} } ``` ```live:eg/member3:output ``` Any argument to the `some` variant can be a composite, non-ground value: ```live:eg/member4:module:merge_down p[x] = y if { some x, {"foo": y} in [{"foo": 100}, {"bar": 200}] } p[x] = y if { some {"bar": x}, {"foo": y} in {{"bar": "b"}: {"foo": "f"}} } ``` ```live:eg/member4:output ``` Rego supports three kinds of equality: assignment (`:=`), comparison (`==`), and unification `=`. We recommend using assignment (`:=`) and comparison (`==`) whenever possible for policies that are easier to read and write. The assignment operator (`:=`) is used to assign values to variables. Variables assigned inside a rule are locally scoped to that rule and shadow global variables. ```live:eg/assignment1:module:read_only x := 100 p if { x := 1 # declare local variable 'x' and assign value 1 x != 100 # true because 'x' refers to local variable } ``` Assigned variables are not allowed to appear before the assignment in the query. For example, the following policy will not compile: ```live:eg/assignment2:module:merge_down p if { x != 100 x := 1 # error because x appears earlier in the query. } q if { x := 1 x := 2 # error because x is assigned

} ``` ```live:eg/assignment2:output:expectassignedabove,expectreferencedabove ``` A simple form of destructuring can be used to unpack values from arrays and assign them to variables: ```live:eg/assignment3:module:read_only address := ["3 Abbey Road", "NW8 9AY", "London", "England"] in_london if { [, , city, country] := address city == "London" country == "England" } ``` ```live:eg/assignment3:output ``` Comparison checks if two values are equal within a rule. If the left or right hand side contains a variable that has not been assigned a value, the compiler throws an error. ```live:eg/comparison1:module:merge_down p if { x := 100 x == 100 # true because x refers to the local variable } ``` ```live:eg/comparison1:output ``` ```live:eg/comparison2:module:merge_down y := 100 q if { y == 100 # true because y refers to the global variable } ``` ```live:eg/comparison2:output ``` ```live:eg/comparison3:module:merge_down r if { z == 100 # compiler error because z has not been assigned a value } ``` ```live:eg/comparison3:output:expectunsafevar ``` Unification (`=`) combines assignment and comparison. Rego will assign variables to values that make the comparison true. Unification lets you ask for values for variables that make an expression true. ```live:eg/unification1:query:merge_down [x, "world"] = ["hello", y] ``` ```live:eg/unification1:output ``` ```live:eg/data/unification2:query:merge_down sites[i].servers[j].name = apps[k].servers[m] ``` ```live:eg/data/unification2:output ``` As opposed to when assignment (`:=`) is used, the order of expressions in a rule does not affect the documents content. ```live:eg/expression_order:module s if { x > y y = 41 x = 42 } ``` Here is a comparison of the three forms of equality. ``` Equality Applicable Compiler Errors Use Case -- -- - - := Everywhere Var already assigned Assign variable == Everywhere Var not assigned Compare values = Everywhere Values cannot be computed Express query ``` Best practice is to use assignment `:=` and comparison `==` wherever possible. The additional compiler checks help avoid errors when writing policy, and the additional syntax helps make the intent clearer when reading policy. Under the hood `:=` and `==` are syntactic sugar for `=`, local variable creation, and additional compiler checks. The following comparison operators are supported: ```live:comparisonoperators:module:readonly a == b # `a` is equal to `b`. a != b # `a` is not equal to `b`. a < b # `a` is less than `b`. a <= b # `a` is less than or equal to `b`. a > b # `a` is greater than `b`. a >= b # `a` is greater than or equal to `b`. ``` None of these operators bind variables contained in the expression. As a result, if either operand is a variable, the variable must appear in another expression in the same rule that would cause the variable to be bound, i.e., an equality expression or the target position of a built-in function. In some cases, rules must perform simple arithmetic, aggregation, and so on. Rego provides a number of built-in functions (or built-ins) for performing these tasks. Built-ins can be easily recognized by their syntax. All built-ins have the following form: ``` <name>(<arg-1>, <arg-2>, ..., <arg-n>) ``` Built-ins usually take one or more input values and produce one output value. Unless stated otherwise, all built-ins accept values or variables as output arguments. If a built-in function is invoked with a variable as input, the variable must be safe, i.e., it must be assigned elsewhere in the query. Built-ins can include "." characters in the name. This allows them to be namespaced. If you are adding custom built-ins to OPA, consider namespacing them to avoid naming conflicts, e.g., `org.example.special_func`. See the document for details on each built-in function. By default, built-in function calls that encounter runtime errors evaluate to undefined (which can usually be treated as `false`) and do not halt policy evaluation. This ensures that built-in functions can be called with invalid inputs without causing the entire policy to stop evaluating. In most cases, policies do not have to implement any kind of error handling

If error handling is required, the built-in function call can be negated to test for undefined. For example: ```live:eg/errors:module:merge_down allow if { io.jwt.verify_hs256(input.token, "secret") [, payload, ] := io.jwt.decode(input.token) payload.role == "admin" } reason contains "invalid JWT supplied as input" if { not io.jwt.decode(input.token) } ``` ```live:eg/errors:input:merge_down { "token": "a poorly formatted token" } ``` ```live:eg/errors:output ``` If you wish to disable this behaviour and instead have built-in function call errors treated as exceptions that halt policy evaluation enable "strict built-in errors" in the caller: API | Flag | `POST v1/data` (HTTP) | `strict-builtin-errors` query parameter `GET v1/data` (HTTP) | `strict-builtin-errors` query parameter `opa eval` (CLI) | `--strict-builtin-errors` `opa run` (REPL) | `> strict-builtin-errors` `rego` Go module | `rego.StrictBuiltinErrors(true)` option Wasm | Not Available The rules below define the content of documents describing a simplistic deployment environment. These documents are referenced in other sections above. ```live:eg/data:module sites := [ { "region": "east", "name": "prod", "servers": [ { "name": "web-0", "hostname": "hydrogen" }, { "name": "web-1", "hostname": "helium" }, { "name": "db-0", "hostname": "lithium" } ] }, { "region": "west", "name": "smoke", "servers": [ { "name": "web-1000", "hostname": "beryllium" }, { "name": "web-1001", "hostname": "boron" }, { "name": "db-1000", "hostname": "carbon" } ] }, { "region": "west", "name": "dev", "servers": [ { "name": "web-dev", "hostname": "nitrogen" }, { "name": "db-dev", "hostname": "oxygen" } ] } ] apps := [ { "name": "web", "servers": ["web-0", "web-1", "web-1000", "web-1001", "web-dev"] }, { "name": "mysql", "servers": ["db-0", "db-1000"] }, { "name": "mongodb", "servers": ["db-dev"] } ] containers := [ { "image": "redis", "ipaddress": "10.0.0.1", "name": "big_stallman" }, { "image": "nginx", "ipaddress": "10.0.0.2", "name": "cranky_euclid" } ] ``` The package and individual rules in a module can be annotated with a rich set of metadata. ```live:rego/metadata:module:read_only allow if { ... } ``` Annotations are grouped within a metadata block, and must be specified as YAML within a comment block that must start with `# METADATA`. Also, every line in the comment block containing the annotation must start at Column 1 in the module/file, or otherwise, they will be ignored. {{< danger >}} OPA will attempt to parse the YAML document in comments following the initial `# METADATA` comment. If the YAML document cannot be parsed, OPA will return an error. If you need to include additional comments between the comment block and the next statement, include a blank line immediately after the comment block containing the YAML document. This tells OPA that the comment block containing the YAML document is finished {{< /danger >}} Name | Type | Description | | scope | string; one of `package`, `rule`, `document`, `subpackages` | The scope on which the `schemas` annotation is applied. Read more . title | string | A human-readable name for the annotation target. Read more . description | string | A description of the annotation target. Read more . related_resources | list of URLs | A list of URLs pointing to related resources/documentation. Read more . authors | list of strings | A list of authors for the annotation target. Read more . organizations | list of strings | A list of organizations related to the annotation target. Read more . schemas | list of object | A list of associations between value paths and schema definitions. Read more . entrypoint | boolean | Whether or not the annotation target is to be used as a policy entrypoint. Read more . custom | mapping of arbitrary data | A custom mapping of named parameters holding arbitrary data. Read more

Annotations can be defined at the rule or package level. The `scope` annotation in a metadata block determines how that metadata block will be applied. If the `scope` field is omitted, it defaults to the scope for the statement that immediately follows the annotation. The `scope` values that are currently supported are: `rule` - applies to the individual rule statement (within the same file). Default, when metadata block precedes rule. `document` - applies to all of the rules with the same name in the same package (across multiple files) `package` - applies to all of the rules in the package (across multiple files). Default, when metadata block precedes package. `subpackages` - applies to all of the rules in the package and all subpackages (recursively, across multiple files) Since the `document` scope annotation applies to all rules with the same name in the same package and the `package` and `subpackages` scope annotations apply to all packages with a matching path, metadata blocks with these scopes are applied over all files with applicable package- and rule paths. As there is no ordering across files in the same package, the `document`, `package`, and `subpackages` scope annotations can only be specified once per path. The `document` scope annotation can be applied to any rule in the set (i.e., ordering does not matter.) ```live:rego/metadata/scope:module:read_only allow if { x == 1 } allow if { x == 2 } ``` The `title` annotation is a string value giving a human-readable name to the annotation target. ```live:rego/metadata/title:module:read_only allow if { x == 1 } allow if { x == 2 } ``` The `description` annotation is a string value describing the annotation target, such as its purpose. ```live:rego/metadata/description:module:read_only allow if { ... } ``` The `relatedresources` annotation is a list of related-resource_ entries, where each links to some related external resource; such as RFCs and other reading material. A related-resource entry can either be an object or a short-form string holding a single URL. When a related-resource entry is presented as an object, it has two fields: `ref`: a URL pointing to the resource (required). `description`: a text describing the resource. When a related-resource entry is presented as a string, it needs to be a valid URL. ```live:rego/metadata/relatedresources1:module:readonly allow if { ... } ``` ```live:rego/metadata/relatedresources2:module:readonly allow if { ... } ``` The `authors` annotation is a list of author entries, where each entry denotes an author. An author entry can either be an object or a short-form string. When an author entry is presented as an object, it has two fields: `name`: the name of the author `email`: the email of the author At least one of the above fields are required for a valid `author` entry. When an author entry is presented as a string, it has the format `{ name } [ "<" email ">"]`; where the name of the author is a sequence of whitespace-separated words. Optionally, the last word may represent an email, if enclosed with `<>`. ```live:rego/metadata/authors1:module:read_only allow if { ... } ``` ```live:rego/metadata/authors2:module:read_only allow if { ... } ``` The `organizations` annotation is a list of string values representing the organizations associated with the annotation target. ```live:rego/metadata/organizations:module:read_only allow if { ... } ``` The `schemas` annotation is a list of key value pairs, associating schemas to data values. In-depth information on this topic can be found . Schema files can be referenced by path, where each path starts with the `schema` namespace, and trailing components specify the path of the schema file (sans file-ending) relative to the root directory specified by the `--schema` flag on applicable

If the `--schema` flag is not present, referenced schemas are ignored during type checking. ```live:rego/metadata/schemasref:module:readonly allow if { access := data.acl["alice"] access[_] == input.operation } ``` Schema definitions can be inlined by specifying the schema structure as a YAML or JSON map. Inlined schemas are always used to inform type checking for the `eval`, `check`, and `test` commands; in contrast to , which require the `--schema` flag to be present in order to be evaluated. ```live:rego/metadata/schemasinline:module:readonly allow if { input.x == 42 } ``` The `entrypoint` annotation is a boolean used to mark rules and packages that should be used as entrypoints for a policy. This value is false by default, and can only be used at `rule` or `package` scope. The `build` and `eval` CLI commands will automatically pick up annotated entrypoints; you do not have to specify them with . {{< info >}} Unless the `--prune-unused` flag is used, any rule transitively referring to a package or rule declared as an entrypoint will also be enumerated as an entrypoint. {{< /info >}} The `custom` annotation is a mapping of user-defined data, mapping string keys to arbitrarily typed values. ```live:rego/metadata/custom:module:read_only allow if { ... } ``` In the example below, you can see how to access an annotation from within a policy. Given the input: ```live:example/metadata/1:input { "number": 11, "subject": { "name": "John doe", "role": "customer" } } ``` The following policy ```live:example/metadata/1:module package example import rego.v1 output := decision if { input.number > 5 annotation := rego.metadata.rule() decision := { "severity": annotation.custom.severity, "message": annotation.description, } } ``` will output ```live:example/metadata/1:output ``` If you'd like more examples and information on this, you can see more here under the policy reference. Annotations can be listed through the `inspect` command by using the `-a` flag: ```shell opa inspect -a ``` The ``ast.AnnotationSet`` is a collection of all ``ast.Annotations`` declared in a set of modules. An ``ast.AnnotationSet`` can be created from a slice of compiled modules: ```go var modules []*ast.Module ... as, err := ast.BuildAnnotationSet(modules) if err != nil { // Handle error. } ``` or can be retrieved from an ``ast.Compiler`` instance: ```go var modules []*ast.Module ... compiler := ast.NewCompiler() compiler.Compile(modules) as := compiler.GetAnnotationSet() ``` The ``ast.AnnotationSet`` can be flattened into a slice of ``ast.AnnotationsRef``, which is a complete, sorted list of all annotations, grouped by the path and location of their targeted package or -rule. ```go flattened := as.Flatten() for _, entry := range flattened { fmt.Printf("%v at %v has annotations %v\n", entry.Path, entry.Location, entry.Annotations) } // Output: // data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]} // data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"} // data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"} // // For modules: // # METADATA // # scope: subpackages // # organizations: // # - Acme Corp. // package foo // // # METADATA // # description: A couple of useful rules // package foo.bar // // # METADATA // # title: My Rule P // p := 7 ``` Given an ``ast.Rule``, the ``ast.AnnotationSet`` can return the chain of annotations declared for that rule, and its path ancestry. The returned slice is ordered starting with the annotations for the rule, going outward to the farthest node with declared annotations in the rule's path ancestry. ```go var rule *ast.Rule ... chain := ast.Chain(rule) for _, link := range chain { fmt.Printf("link at %v has annotations %v\n", link.Path, link.Annotations) } // Output: // data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"} // data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"} // data.foo at

has annotations {"scope":"subpackages","organizations":["Acme Corp."]} // // For modules: // # METADATA // # scope: subpackages // # organizations: // # - Acme Corp. // package foo // // # METADATA // # description: A couple of useful rules // package foo.bar // // # METADATA // # title: My Rule P // p := 7 ``` You can provide one or more input schema files and/or data schema files to `opa eval` to improve static type checking and get more precise error reports as you develop Rego code. The `-s` flag can be used to upload schemas for input and data documents in JSON Schema format. You can either load a single JSON schema file for the input document or directory of schema files. ``` -s, --schema string set schema file path or directory path ``` When a single file is passed, it is a schema file associated with the input document globally. This means that for all rules in all packages, the `input` has a type derived from that schema. There is no constraint on the name of the file, it could be anything. Example: ``` opa eval data.envoy.authz.allow -i opa-schema-examples/envoy/input.json -d opa-schema-examples/envoy/policy.rego -s opa-schema-examples/envoy/schemas/my-schema.json ``` When a directory path is passed, annotations will be used in the code to indicate what expressions map to what schemas (see below). Both input schema files and data schema files can be provided in the same directory, with different names. The directory of schemas may have any sub-directories. Notice that when a directory is passed the input document does not have a schema associated with it globally. This must also be indicated via an annotation. Example: ``` opa eval data.kubernetes.admission -i opa-schema-examples/kubernetes/input.json -d opa-schema-examples/kubernetes/policy.rego -s opa-schema-examples/kubernetes/schemas ``` Schemas can also be provided for policy and data files loaded via `opa eval --bundle` Example: ``` opa eval data.kubernetes.admission -i opa-schema-examples/kubernetes/input.json -b opa-schema-examples/bundle.tar.gz -s opa-schema-examples/kubernetes/schemas ``` Samples provided at: <https://github.com/aavarghese/opa-schema-examples/> Consider the following Rego code, which assumes as input a Kubernetes admission review. For resources that are Pods, it checks that the image name starts with a specific prefix. `pod.rego` ``` package kubernetes.admission import rego.v1 deny contains msg if { input.request.kind.kinds == "Pod" image := input.request.object.spec.containers[_].image not startswith(image, "hooli.com/") msg := sprintf("image '%v' comes from untrusted registry", [image]) } ``` Notice that this code has a typo in it: `input.request.kind.kinds` is undefined and should have been `input.request.kind.kind`. Consider the following input document: `input.json` ``` { "kind": "AdmissionReview", "request": { "kind": { "kind": "Pod", "version": "v1" }, "object": { "metadata": { "name": "myapp" }, "spec": { "containers": [ { "image": "nginx", "name": "nginx-frontend" }, { "image": "mysql", "name": "mysql-backend" } ] } } } } ``` Clearly there are 2 image names that are in violation of the policy. However, when we evaluate the erroneous Rego code against this input we obtain: ``` % opa eval data.kubernetes.admission --format pretty -i opa-schema-examples/kubernetes/input.json -d opa-schema-examples/kubernetes/policy.rego [] ``` The empty value returned is indistinguishable from a situation where the input did not violate the policy. This error is therefore causing the policy not to catch violating inputs appropriately. If we fix the Rego code and change `input.request.kind.kinds` to `input.request.kind.kind`, then we obtain the expected result: ``` [ "image 'nginx' comes from untrusted registry", "image 'mysql' comes from untrusted registry" ] ``` With this feature, it is possible to pass a schema to `opa eval`, written in JSON Schema. Consider the admission review schema provided at: <https://github.com/aavarghese/opa-schema-examples/blob/main/kubernetes/schemas/input.json> We can pass this schema to the evaluator as follows: ``` % opa eval data.kubernetes.admission --format pretty -i opa-schema-examples/kubernetes/input.json -d opa-schema-examples/kubernetes/policy.rego -s

``` With the erroneous Rego code, we now obtain the following type error: ``` 1 error occurred: ../../aavarghese/opa-schema-examples/kubernetes/policy.rego:5: regotypeerror: undefined ref: input.request.kind.kinds input.request.kind.kinds ^ have: "kinds" want (one of): ["kind" "version"] ``` This indicates the error to the Rego developer right away, without having the need to observe the results of runs on actual data, thereby improving productivity. When passing a directory of schemas to `opa eval`, schema annotations become handy to associate a Rego expression with a corresponding schema within a given scope: ``` allow if { ... } ``` See the for general information relating to annotations. The `schemas` field specifies an array associating schemas to data values. Paths must start with `input` or `data` (i.e., they must be fully-qualified.) The type checker derives a Rego Object type for the schema and an appropriate entry is added to the type environment before type checking the rule. This entry is removed upon exit from the rule. Example: Consider the following Rego code which checks if an operation is allowed by a user, given an ACL data document: ``` package policy import rego.v1 import data.acl default allow := false allow if { access := data.acl.alice access[_] == input.operation } allow if { access := data.acl.bob access[_] == input.operation } ``` Consider a directory named `mySchemasDir` with the following structure, provided via `opa eval --schema opa-schema-examples/mySchemasDir` ```$ tree mySchemasDir/ mySchemasDir/ input.json acl-schema.json ``` For actual code samples, see <https://github.com/aavarghese/opa-schema-examples/tree/main/acl>. In the first `allow` rule above, the input document has the schema `input.json`, and `data.acl` has the schema `acl-schema.json`. Note that we use the relative path inside the `mySchemasDir` directory to identify a schema, omit the `.json` suffix, and use the global variable `schema` to stand for the top-level of the directory. Schemas in annotations are proper Rego references. So `schema.input` is also valid, but `schema.acl-schema` is not. If we had the expression `data.acl.foo` in this rule, it would result in a type error because the schema contained in `acl-schema.json` only defines object properties `"alice"` and `"bob"` in the ACL data document. On the other hand, this annotation does not constrain other paths under `data`. What it says is that we know the type of `data.acl` statically, but not that of other paths. So for example, `data.foo` is not a type error and gets assigned the type `Any`. Note that the second `allow` rule doesn't have a METADATA comment block attached to it, and hence will not be type checked with any schemas. On a different note, schema annotations can also be added to policy files part of a bundle package loaded via `opa eval --bundle` along with the `--schema` parameter for type checking a set of `*.rego` policy files. The scope of the `schema` annotation can be controlled through the annotation In case of overlap, schema annotations override each other as follows: ``` rule overrides document document overrides package package overrides subpackages ``` The following sections explain how the different scopes affect `schema` annotation overriding for type checking. In the example above, the second rule does not include an annotation so type checking of the second rule would not take schemas into account. To enable type checking on the second (or other rules in the same file) we could specify the annotation multiple times: ``` allow if { access := data.acl["alice"] access[_] == input.operation } allow if { access := data.acl["bob"] access[_] == input.operation } ``` This is obviously redundant and error-prone. To avoid this problem, we can define the annotation once on a rule with scope `document`: ``` allow if { access :=

access[_] == input.operation } allow if { access := data.acl["bob"] access[_] == input.operation } ``` In this example, the annotation with `document` scope has the same affect as the two `rule` scoped annotations in the previous example. Annotations can be defined at the `package` level and then applied to all rules within the package: ``` package example import rego.v1 allow if { access := data.acl["alice"] access[_] == input.operation } allow if { access := data.acl["bob"] access[_] == input.operation } ``` `package` scoped schema annotations are useful when all rules in the same package operate on the same input structure. In some cases, when policies are organized into many sub-packages, it is useful to declare schemas recursively for them using the `subpackages` scope. For example: ``` package kubernetes.admission ``` This snippet would declare the top-level schema for `input` for the `kubernetes.admission` package as well as all subpackages. If admission control rules were defined inside packages like `kubernetes.admission.workloads.pods`, they would be able to pick up that one schema declaration. JSON Schemas are often incomplete specifications of the format of data. For example, a Kubernetes Admission Review resource has a field `object` which can contain any other Kubernetes resource. A schema for Admission Review has a generic type `object` for that field that has no further specification. To allow more precise type checking in such cases, we support overriding existing schemas. Consider the following example: ``` package kubernetes.admission import rego.v1 deny contains msg if { input.request.kind.kind == "Pod" image := input.request.object.spec.containers[_].image not startswith(image, "hooli.com/") msg := sprintf("image '%v' comes from untrusted registry", [image]) } ``` In this example, the `input` is associated with an Admission Review schema, and furthermore `input.request.object` is set to have the schema of a Kubernetes Pod. In effect, the second schema annotation overrides the first one. Overriding is a schema transformation feature and combines existing schemas. In this case, we are combining the Admission Review schema with that of a Pod. Notice that the order of schema annotations matter for overriding to work correctly. Given a schema annotation, if a prefix of the path already has a type in the environment, then the annotation has the effect of merging and overriding the existing type with the type derived from the schema. In the example above, the prefix `input` already has a type in the type environment, so the second annotation overrides this existing type. Overriding affects the type of the longest prefix that already has a type. If no such prefix exists, the new path and type are added to the type environment for the scope of the rule. In general, consider the existing Rego type: ``` object{a: object{b: object{c: C, d: D, e: E}}} ``` If we override this type with the following type (derived from a schema annotation of the form `a.b.e: schema-for-E1`): ``` object{a: object{b: object{e: E1}}} ``` It results in the following type: ``` object{a: object{b: object{c: C, d: D, e: E1}}} ``` Notice that `b` still has its fields `c` and `d`, so overriding has a merging effect as well. Moreover, the type of expression `a.b.e` is now `E1` instead of `E`. We can also use overriding to add new paths to an existing type, so if we override the initial type with the following: ``` object{a: object{b: object{f: F}}} ``` we obtain the following type: ``` object{a: object{b: object{c: C, d: D, e: E, f: F}}} ``` We use schemas to enhance the type checking capability of OPA, and not to validate the input and data documents against desired

This burden is still on the user and care must be taken when using overriding to ensure that the input and data provided are sensible and validated against the transformed schemas. It is sometimes useful to have different input schemas for different rules in the same package. This can be achieved as illustrated by the following example: ``` package policy import rego.v1 import data.acl default allow := false allow if { access := data.acl[input.user] access[_] == input.operation } whocan contains user if { access := acl[user] access[_] == input.operation } ``` The directory that is passed to `opa eval` is the following: ```$ tree mySchemasDir/ mySchemasDir/ input.json acl-schema.json whocan-input-schema.json ``` In this example, we associate the schema `input.json` with the input document in the rule `allow`, and the schema `whocan-input-schema.json` with the input document for the rule `whocan`. Rego has a gradual type system meaning that types can be partially known statically. For example, an object could have certain fields whose types are known and others that are unknown statically. OPA type checks what it knows statically and leaves the unknown parts to be type checked at runtime. An OPA object type has two parts: the static part with the type information known statically, and a dynamic part, which can be nil (meaning everything is known statically) or non-nil and indicating what is unknown. When we derive a type from a schema, we try to match what is known and unknown in the schema. For example, an `object` that has no specified fields becomes the Rego type `Object{Any: Any}`. However, currently `additionalProperties` and `additionalItems` are ignored. When a schema is fully specified, we derive a type with its dynamic part set to nil, meaning that we take a strict interpretation in order to get the most out of static type checking. This is the case even if `additionalProperties` is set to `true` in the schema. In the future, we will take this feature into account when deriving Rego types. When overriding existing types, the dynamicity of the overridden prefix is preserved. JSON Schema provides keywords such as `anyOf` and `allOf` to structure a complex schema. For `anyOf`, at least one of the subschemas must be true, and for `allOf`, all subschemas must be true. The type checker is able to identify such keywords and derive a more robust Rego type through more complex schemas. Specifically, `anyOf` acts as an Rego Or type where at least one (can be more than one) of the subschemas is true. Consider the following Rego and schema file containing `anyOf`: `policy-anyOf.rego` ``` package kubernetes.admission import rego.v1 deny if { input.request.servers.versions == "Pod" } ``` `input-anyOf.json` ``` { "$schema": "http://json-schema.org/draft-07/schema", "type": "object", "properties": { "kind": {"type": "string"}, "request": { "type": "object", "anyOf": [ { "properties": { "kind": { "type": "object", "properties": { "kind": {"type": "string"}, "version": {"type": "string" } } } } }, { "properties": { "server": { "type": "object", "properties": { "accessNum": {"type": "integer"}, "version": {"type": "string"} } } } } ] } } } ``` We can see that `request` is an object with two options as indicated by the choices under `anyOf`: contains property `kind`, which has properties `kind` and `version` contains property `server`, which has properties `accessNum` and `version` The type checker finds the first error in the Rego code, suggesting that `servers` should be either `kind` or `server`. ``` input.request.servers.versions ^ have: "servers" want (one of): ["kind" "server"] ``` Once this is fixed, the second typo is highlighted, prompting the user to choose between `accessNum` and `version`. ```

^ have: "versions" want (one of): ["accessNum" "version"] ``` Specifically, `allOf` keyword implies that all conditions under `allOf` within a schema must be met by the given data. `allOf` is implemented through merging the types from all of the JSON subSchemas listed under `allOf` before parsing the result to convert it to a Rego type. Merging of the JSON subSchemas essentially combines the passed in subSchemas based on what types they contain. Consider the following Rego and schema file containing `allOf`: `policy-allOf.rego` ``` package kubernetes.admission import rego.v1 deny if { input.request.servers.versions == "Pod" } ``` `input-allof.json` ``` { "$schema": "http://json-schema.org/draft-07/schema", "type": "object", "properties": { "kind": {"type": "string"}, "request": { "type": "object", "allOf": [ { "properties": { "kind": { "type": "object", "properties": { "kind": {"type": "string"}, "version": {"type": "string" } } } } }, { "properties": { "server": { "type": "object", "properties": { "accessNum": {"type": "integer"}, "version": {"type": "string"} } } } } ] } } } ``` We can see that `request` is an object with properties as indicated by the elements listed under `allOf`: contains property `kind`, which has properties `kind` and `version` contains property `server`, which has properties `accessNum` and `version` The type checker finds the first error in the Rego code, suggesting that `servers` should be `server`. ``` input.request.servers.versions ^ have: "servers" want (one of): ["kind" "server"] ``` Once this is fixed, the second typo is highlighted, informing the user that `versions` should be one of `accessNum` or `version`. ``` input.request.server.versions ^ have: "versions" want (one of): ["accessNum" "version"] ``` Because the properties `kind`, `version`, and `accessNum` are all under the `allOf` keyword, the resulting schema that the given data must be validated against will contain the types contained in these properties children (string and integer). It is valid for JSON schemas to reference other JSON schemas via URLs, like this: ```json { "description": "Pod is a collection of containers that can run on a host.", "type": "object", "properties": { "metadata": { "$ref": "https://kubernetesjsonschema.dev/v1.14.0/_definitions.json#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta", "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" } } } ``` OPA's type checker will fetch these remote references by default. To control the remote hosts schemas will be fetched from, pass a capabilities file to your `opa eval` or `opa check` call. Starting from the capabilities.json of your OPA version (which can be found [in the repository](https://github.com/open-policy-agent/opa/tree/main/capabilities)), add an `allow_net` key to it: its values are the IP addresses or host names that OPA is supposed to connect to for retrieving remote schemas. ```json { "builtins": [ ... ], "allow_net": [ "kubernetesjsonschema.dev" ] } ``` To forbid all network access in schema checking, set `allow_net` to `[]` Host names are checked against the list as-is, so adding `127.0.0.1` to `allow_net`, and referencing a schema from `http://localhost/` will fail. Metaschemas for different JSON Schema draft versions are not subject to this constraint, as they are already provided by OPA's schema checker without requiring network access. These are: `http://json-schema.org/draft-04/schema` `http://json-schema.org/draft-06/schema` `http://json-schema.org/draft-07/schema` Currently this feature admits schemas written in JSON Schema but does not support every feature available in this format. In particular the following features are not yet supported: additional properties for objects pattern properties for objects additional items for arrays contains for arrays oneOf, not enum if/then/else A note of caution: overriding is a powerful capability that must be used carefully. For example, the user is allowed to write: ``` ``` In this case, we are overriding the root of all documents to have some schema. Since all Rego code lives under `data` as virtual documents, this in practice renders all of them inaccessible (resulting in type

Similarly, assigning a schema to a package name is not a good idea and can cause problems. Care must also be taken when defining overrides so that the transformation of schemas is sensible and data can be validated against the transformed schema. For more examples, please see <https://github.com/aavarghese/opa-schema-examples> This contains samples for Envoy, Kubernetes, and Terraform including corresponding JSON Schemas. For a reference on JSON Schema please see: <http://json-schema.org/understanding-json-schema/reference/index.html> For a tool that generates JSON Schema from JSON samples, please see: <https://jsonschema.net/home> The Rego compiler supports `strict mode`, where additional constraints and safety checks are enforced during compilation. Compiler rules that will be enforced by future versions of OPA, but will be a breaking change once introduced, are incubated in strict mode. This creates an opportunity for users to verify that their policies are compatible with the next version of OPA before upgrading. Compiler Strict mode is supported by the `check` command, and can be enabled through the `--strict`/`-S` flag. ``` -S, --strict enable compiler strict mode ``` Name | Description | Enforced by default in OPA version |-| Duplicate imports | Duplicate , where one import shadows another, are prohibited. | 1.0 Unused local assignments | Unused arguments or local to a rule, function or comprehension are prohibited | Unused imports | Unused are prohibited. | `input` and `data` reserved keywords | `input` and `data` are reserved keywords, and may not be used as names for rules and variable assignment. | 1.0 Use of deprecated built-ins | Use of deprecated functions is prohibited, and these will be removed in OPA 1.0. Deprecated built-in functions: `any`, `all`, `rematch`, `net.cidroverlap`, `setdiff`, `castarray`, `castset`, `caststring`, `castboolean`, `castnull`, `cast_object` | 1.0 {{< info >}} If the `rego.v1` import is present in a module, all strict mode checks documented above except the unused local assignment and unused imports checks are enforced on the module. Additionally the `rego.v1` import also requires the usage of `if` and `contains` keywords when declaring certain rules. The `if` keyword is required before a rule body and the `contains` keyword is required for partial set rules. {{< /info >}} In the future, when is released, breaking changes will be introduced to the Rego language. The `rego.v1` import is a way to opt-in to these breaking changes early, and ensure that your policies are compatible with OPA v1.0. If a module containing this import is not compatible with OPA v1.0, it will cause a compilation error. When a module imports `rego.v1`, the following features and constraints are implied: all are implied and can be used without import. These imports are mutually exclusive, and it will cause a compilation error to import both `rego.v1` and `future.keywords` in the same module. the keyword is required before a rule body declaration. the keyword is required for partial set (multi-value) rules. most constraints and checks are implied and enforced. See the strict mode for details. The `rego.v1` import only affects the module where it's declared. It does not affect any other modules, even if they are importing, or is imported by, a module where `rego.v1` is declared. In OPA v1.0, the `rego.v1` import will have no semantic impact on the policy, as all its implied features and constraints will be enforced by default. It will however still be a valid statement, and won't cause any compilation errors. Example policy that imports `rego.v1` to be compatible with the future syntax in OPA v1.0: ```live:regov1:module:readonly package example import rego.v1 l := [1, 2, 3] default allow := false allow if { count(violations) == 0 } violations contains msg if { every x in l { x >