docs/blog/2024-03-15-opa-1-0-is-coming-heres-what-you-need-to-know-c8fb0d258368.md
Towards Open Policy Agent 1.0
December 28th marked the 8th anniversary of the first commit in the Open Policy Agent project. 5000+ commits from more than 400 contributors later, we're starting to prepare for OPA 1.0.
Following the rules of semantic versioning, one would be excused to think of 1.0 as the first "stable" version. That's not really the case for this project. Since the first public releases of OPA, great care has been taken to ensure new changes don't break existing policy. Thousands of organizations have come to rely on OPA for policy enforcement across the whole stack — often for critical production use cases.
While many features have been added to OPA and the Rego language since 2015, few have ever been removed. All this means that almost any policy written eight, five or three years ago still evaluates using the very latest version OPA, just as it did when it was written! But to keep adding features without ever being able to remove things that might not have worked out as we imagined — or simply didn't age well — also comes at a price. An increased cost of maintenance for sure, but more importantly, old ideas, language constructs and built-in functions all add to the cost of learning Rego, compared to having policy look and act consistently using only modern equivalent features.
With OPA 1.0, we're aiming to fix this.
If you have a busy day ahead and want to get right to something actionable — here's what you can do. From OPA v0.59.0 and onwards, you can start to prepare for the changes in the upcoming 1.0 release following these steps:
We'll get back to practical concerns by the end of the blog, but before that, let's see what changes are planned for the first major OPA version.
Note: Below follows a non-comprehensive list subject to change. While these features are planned — and most of them even implemented already! — updates may be made before the final 1.0 release. I'll do my best to keep this blog post up to date, as we will with the OPA 1.0 documentation.
Several keywords (in, every, if and contains*)* have been added to OPA since the start. Introducing new keywords means there's always a risk that existing policy might break in case identifiers, like rule names or variables, have been named in a way that clashes with the new keywords. In order to prevent this, access to these keywords have required an import of future.keywords. We now live in that future.
OPA v1.0 makes import future.keywords a no-op, as all keywords are now made available everywhere. In the time before OPA 1.0 is released, the new rego.v1 should be used in place of future.keywords imports.
The if keyword helps explain the "inverted if … then" nature of Rego rules, and makes rules easier to read. Additionally, any rule body with only a single expression, like:
allow {
"admin" in user.roles
}
May with the help of if be expressed as a one-liner, with the curly brackets removed:
allow if "admin" in user.roles
OPA 1.0 makes if a natural — and mandatory — part of every rule's anatomy.
Tip: Use the opa fmt --rego-v1 utility in OPA v0.59.0+ to automatically rewrite all of your rules with if added to the rule head.
The contains keyword helps express multi-value (or as they're often called, partial) rules — i.e. rules that build a set of values. It also helps avoid ambiguities around certain classes of rules, like the fairly recently introduced "nested" rule type:
users.names contains name if {
# ...
name := sprintf("%s, %s", [first_name, last_name])
}
The contains keyword was already mandatory for nested rules. OPA v1.0 makes the use of this consistent across all multi-value rules by making contains a requirement.
Tip: Use the opa fmt --rego-v1 utility in OPA v0.59.0+ to automatically rewrite all of your multi-value rules with contains added to the rule head.
Most of the rules that have existed in OPA strict mode will be made the default in OPA 1.0. This will help users catch mistakes early, and have them fixed right away. If you have been running opa check --strict as part of your policy build pipeline, you're already in the clear here.
The rules from strict mode that will be made default in OPA 1.0 are:
Duplicate imports should realistically not be a problem in any repo, and the fix is simply to have them removed.
package policy
import data.authz
import data.authz # this is now an error
Deprecated built-in functions will be removed in OPA 1.0, and most of them are trivial to replace using a single line of Rego, or a different built-in function.
package policy
# simply change to use `true in {input.foo, input.bar}`
# using `in` additionally has the benefit that it can be used
# to check for any type of value, and not just boolean "true"
a := any([input.foo, input.bar])
# change to use `every` keyword, e.g.
# every x in [input.foo, input.bar] {
# x == true
# }
# just like `in` may be used for much more, `every` can be
# used to evaluate complex expressions
e := all([input.foo, input.bar])
# simply use the minus (`-`) operator instead, e.g.
# s3 := s1 - s2
s3 := set_diff(s1, s2)
# simply change to use regex.match instead
r := re_match(..)
# simply change to use net.cidr_intersects
n := net.cidr_overlap(..)
# cast_array, cast_set, cast_string, cast_boolean, cast_null, cast_object
# use the "is_x" equivalent built-in function in their place
s := is_string("yes")
OPA 1.0 prohibits the use of input or data as identifiers:
# this is not allowed in OPA 1.0
input := "overloaded"
# and neither is this
data := {}
Overloading input has mostly been common in tests. Do note that with input as {..} remains valid. If you're using assignment to input however, (input := {..}), you'll just need to change the name to something like inp instead.
See the 1.0 tag in the OPA backlog for a list of all issues related to OPA 1.0. Do note though that not all issues marked 1.0 might be picked for inclusion, and new issues may pop up before the release!
Other notable changes include:
As previously mentioned — beginning with OPA v0.59.0 a new handy import to help with the 1.0 transition is made available. By adding import rego.v1 to a Rego policy, you can tell OPA to treat the policy just as it will handle it once version 1.0 is released.
In OPA 1.0, the server will bind to the localhost interface by default, and not 0.0.0.0 (all interfaces). This change is needed in order to avoid accidentally exposing OPA to the internet, which while uncommon (as OPA normally runs behind firewalls and gateways) still happens, and we should aim to provide a secure default. Should you still want to bind against 0.0.0.0, or some other interface, you can use the --addr flag of the opa run command, like opa run --server --addr 0.0.0.0:8181. The impact of this change is expected to be small, but good to keep in mind.
The OPA docs have been updated to cover much of what's mentioned in this blog in greater detail. It also covers more of the technical reasons some of these changes are needed. See the docs on OPA 1.0 for more information.
As we covered in the TL;DR section, we're providing a number of tools to help with the transition starting from OPA v0.59.0 already. These tools will likely be extended and improved in following releases, but starting to use them today will ensure as smooth transition as possible.
Also worth pointing out — following guides like the Rego Style Guide, and using tools like Regal, is an excellent way to ensure not just compliance with future changes to Rego, but that your current policy repo is continuously kept in the best possible condition.
If you have any questions, concerns or would like to provide feedback around the upcoming 1.0 release, or the tools made available to help you transition smoothly — don't hesitate to reach out using any of the below channels: