Skip to main content

Open Policy Agent graduates in the Cloud Native Computing Foundation

OPA graduates in the Cloud Native Computing Foundation banner

We're excited to announce that Open Policy Agent (OPA) is now a graduated project in the Cloud Native Computing Foundation (CNCF)! Graduation reflects the maturity of the project in terms of adoption, diversity of contributions, community and overall quality.

The growth over the last year has been phenomenal. The number of users on slack.openpolicyagent.org has grown by 3x (to over 3,600 users) and the number of Docker image downloads surpassed 39M (a 1000% increase!) We attribute much of this growth to the need for a robust policy-as-code solution in the cloud native ecosystem.

In the last year, the project shipped a number of powerful new features such as signed bundles, improved data fetching capabilities, parser and evaluator optimizations, as well as expanded support for WebAssembly-based execution environments. OPA Gatekeeper reached GA and added several features including multi-pod scalability, semantic logging, fine-grained metrics, dry-run support, and more. The popular Conftest tool (which helps you write tests against structured configuration files) became an official OPA subproject. Finally, support for IntelliJ users landed with the OPA IDEA plugin.

Graduation is a huge milestone for the project, and we wanted to take a moment to thank everyone involved in making OPA a successful, graduated project:

  • First, we'd like to thank the CNCF for their partnership and for all their support over the years. We would particularly like to thank Chris Aniszczyk, Amye Scavarda Perrin, Ihor Dvoretskyi for the excellent support they provided along the way.
  • We'd also like to thank all of the maintainers and core contributors to the OPA project: Ash Narkar, Boran Seref, Craig Tabita, Gareth Rushgrove, John Reese, Lennard Eijsackers, Max Smythe, Oren Shomron, Patrick East, Rita Zhang, Sertaç Özercan, and Stephan Renatus.
  • Lastly, we'd like to send a HUGE thank you to the entire OPA community. All new OPA features and partnerships were driven by your feedback and contributions. We also want to recognize certain people whose feedback, contribution, and support has been invaluable: Anders Eknert, Jasper Van der Jeugt, Joe Searcy, and Vincent Gramer.

Going forward we'll continue to focus on improving all aspects of OPA while solving real problems around policy and authorization in the cloud native ecosystem. We look forward to continuing to work with the amazing OPA community that started nearly five years ago. But, for today, let's all celebrate (socially distanced, of course) together on this great milestone!

Introducing OPA for IntelliJ

OPA Plugin for IntelliJ IDEA brings Rego language support and an IDE experience to your OPA workflow!

IntelliJ IDEA is one of the most popular IDEs for developers, offering built-in support for many programming languages such as Java, Kotlin, and Python. A new OPA plugin extends this support to policies written in the Rego query language, and it also works with GoLand for Go developers. The plugin lets users write and evaluate policies directly inside the IDE.

OPA Plugin on the JetBrains marketplace

Syntax Highlighting

The plugin includes support for rule heads, Rego keywords, function calls, strings, and comments.

Rego Syntax Highlighting

OPA Actions Menu

From the OPA Actions Menu, users can run several key OPA actions on the workspace or the current open file:

OPA Actions Menu

  • Install the OPA binary, if it isn't already installed (the plugin prompts for this when running other actions)
  • Format Document: formats the open .rego file using opa fmt
  • Check Document: checks the open .rego file using opa check
  • Test the workspace: finds and runs all tests in the project (rules whose heads are prefixed with test_)
  • View test coverage for the workspace
  • Display the trace of selected code, using an input.json file found in the project root

Run Configurations

In .rego files, every line containing a rule head or package includes a "Run Configuration" launch button in the gutter, allowing users to run opa eval or opa test directly.

OPA Rules show Launch button next to them

Evaluate Rules

Running a configuration from a rule head line shows opa eval results for that package/rule, using the bundle directory and input file set in the configuration.

OPA eval results panel for a rule

Tip: you can use the shortcut shift + F10 on Windows ^r on Mac to re-run the last executed Run Configuration. It allows you to quickly check how changes affect your policies.

Evaluate Packages

Running a configuration from a package line similarly shows opa eval results for the package, using the configured bundle directory and input file.

Test Rules and Packages

Running a configuration from a test_-prefixed rule head runs opa test, with equivalent functionality available at the package level. Test results are displayed within the IDE.

OPA test results displayed inside the IDE

Coming Soon

Eval and Partial Eval results for selected code

Future updates will let users highlight Rego code and view eval or partial evaluation results directly in the editor, using an input.json file in the project root. Results will appear as formatted JSON, and a profiling action will also be added to the menu.

Who is it for?

The plugin aims to help newcomers to OPA/Rego with language support and quick access to core features, while giving experienced OPA developers a smoother in-IDE workflow.

How To Contribute

The plugin is open source, hosted at https://github.com/open-policy-agent/opa-idea-plugin. Contributors can file issues or pick up existing ones.

Where To Start

The plugin is built with Kotlin and Gradle. Newcomers to Kotlin are pointed to the official Language Guide, and the bundled J2K Compiler (tutorial link) can help convert Java boilerplate to Kotlin. The IntelliJ Platform SDK DevGuide is recommended for learning plugin development.

Project Structure

A condensed overview of the source tree (full version at the project's architecture page):

opa-idea-plugin/
├── gradle.build.kts
│ …
├── plugin # module to build/run/publish opa-ida-plugin plugin
│ ...
└── src/main/resources/META-INF/plugin.xml
└── plugin.xml
├── idea # source code of features only available for IntelliJ IDEA
│ ...
└── src/main/kotlin/resources/META-INF
└── idea-only.xml
├── src # source code common to all IDEs
├── main
├── grammar
└── Rego.bnf
├── kotlin/.../ideaplugin/
├── ide
│ ...
├── actions
└── extensions
│ ...
└── opa/tool
└── OpaActions
└── resources
└── META-INF
└── opa-core.xml
└── test
├── kotlin/.../ideaplugin/
│ ...
├── ide
└── lang
└── resources/.../ideaplugin/

Note of Thanks

The author credits core contributors Vincent Gramer (vgramer), Frankie Cerkvenik (frankiecerk), and Igor Rodzik (irodzik) for seeding the project, with a full contributor list at https://github.com/open-policy-agent/opa-idea-plugin/graphs/contributors. The plugin draws inspiration from the IntelliJ Rust project's reference implementation.

Open Policy Agent 2020, year in review

Open Policy Agent 2020 year in review banner image

Introduction

To call 2020 an eventful year would be an understatement. With a pandemic raging globally, we saw the tech community quickly adapt by moving largely online. Working from home became the new norm as meetings, conferences and other events also moved to digital channels. With so much of our lives now taking place online, the importance of our online platforms became overwhelmingly apparent. As businesses and organizations worked hard to accommodate an ever increasing number of users, both interest and investments in securing their platforms followed.

2020 saw a big uptake in the adoption of OPA. As the de facto standard for cloud-native authorization, OPA has also found its way into many new and interesting domains. The list of integrations has been constantly growing during the year, and many more are being worked on. Hundreds of new open source projects, businesses and organizations have come to rely on OPA as their open source unified policy framework across the stack. And, with the increasing adoption of OPA, we've seen the community grow with it — from the number of contributors to active users on Slack. The trajectory for next year has been set, and it truly looks great — for OPA and its community. Before we look ahead though, let's take some time to look back at what we achieved in 2020.

OPA 2020 in numbers

  • ~29 million downloads
  • ~1500 new Github stars
  • ~1700 new Slack users, doubled in size!
  • ~750 commits
  • 90 contributors
  • 9 major versions released, 17 point releases
  • 100th release pushed!
  • 1000th issue closed!
  • 10000 VS Code OPA plug-in installs!

Notable features and enhancements

Management

Several management capabilities were added. The bundle signing feature allows verifying the integrity of any bundle processed by OPA. Moreover, the new bundle persistence feature enables OPA to store a local copy of bundles received, which may be used in case the server is forced to restart while the configured bundle endpoints are unreachable. To authenticate at remote endpoints, several new credential providers were added, including:

For example, the following configuration instructs OPA to use the GCP Metadata Server to obtain credentials for downloading bundles from a remote endpoint (which happens to be hosted on Cloud Run):

services:
cloudrun:
url: <BUNDLE_SERVICE_URL>
response_header_timeout_seconds: 5
credentials:
gcp_metadata:
audience: <BUNDLE_SERVICE_URL>
bundles:
authz:
service: cloudrun
resource: bundles/http/example/authz.tar.gz
polling:
min_delay_seconds: 60
max_delay_seconds: 120

Lastly, the decision logger now supports mutating masks so that administrators can inject or overwrite (as opposed to just removing) fields inside decision logs.

Tooling

The OPA binary had several new flags and features added. The most notable additions are probably the remodeled opa build command for creating bundles and the new opa bench/opa test --bench for benchmarking policy evaluation.

WebAssembly (Wasm)

Many improvements for Wasm were incorporated this year. Among the bigger ones we saw the opa build command get support for building Wasm modules from Rego policies, and the addition of an SDK for Javascript. Plenty of built-ins were implemented natively, and the status for Wasm support is now listed for each on the policy reference page.

Performance

In the performance department we saw many improvements. The comprehension indexing, which allows O(n) runtime complexity on "group-by" operations, is probably the one to stand out the most. Less visible, but of no less importance, a new parser was introduced, improving the internals of OPA and resulting in a 100x speedup on most .rego files!

Rego & Built-in Functions

Built-in functions error handling was remodeled to allow policy authors to gracefully deal with errors like garbled input, which would previously halt policy evaluation in certain cases.

The new caching options for http.send introduced this year vastly increases its utility, allowing it to be used for things like OAuth2 and OpenID Connect metadata retrieval. The raise_error option allows policy authors to deal with errors in communication rather than having policy evaluation halt. Given the number of improvements made to the HTTP client this year — and the number of policies in where it is used — it makes the previous "experimental" label it carried less relevant, and the feature should be considered stable moving forward.

More than 30 new built-ins were added to OPA in 2020. These include functions for:

  • JSON manipulation: json.patch, json.remove.
  • Bitwise operations: bits.or, bits.and, bits.negate, bits.xor, bits.lsh, bits.rsh.
  • Hex encoding: hex.encode, hex.decode.
  • Hashing: crypto.md5, crypto.sha1, crypto.sha256.
  • And a whole bunch of functions for validating various formats: json.is_valid, yaml.is_valid, base64.is_valid, regex.is_valid, semver.is_valid.
  • Other useful built-ins added include graph.reachable, object.get, numbers.range and semver.compare.

The wide variety of these help push OPA as a true general purpose policy engine, further increasing the number of domains where Rego may be used. In addition to all the above, hundreds of bugs were fixed and tons of improvements to documentation were made. It's been an incredible year for the project.

Ecosystem and integrations

Gatekeeper

The Gatekeeper project took some great leaps forward this year. In terms of new features this meant, among other things, granular namespace exclusions (narrowing the scope of resources to present for audit, webhooks and sync), Helm 3 support and the Gatekeeper Pod Security Policies being referenced as a serious contender to the Kubernetes provided PSPs.

In terms of stability, Gatekeeper gained support for multi-pod deployments, completed a CNCF security review and had their first stable non-beta release pushed. Other notable enhancements include semantic logging (getting cluster wide resources in violation of policy from logs), standalone auditing and the dependency on finalizers removed.

Still in early alpha, we're seeing much anticipated support for mutating webhooks. Surely one thing to look out for in 2021!

If you haven't already, make sure to check out the gatekeeper-library repo, which was moved out from the Gatekeeper repository this year and has seen continuous additions and improvements since then.

Conftest

2020 was an exciting year in the development of the Conftest project. Having previously been maintained independently, this year we saw the project included in the larger OPA family of projects.

As for features, the already versatile tool gained support for a number of new input formats (VCL, XML, EDN, TOML, HOCON, Jsonnet) and a new output format (JUnit). Using conftest for Terraform policies became a common use case, and many improvements for Terraform and the HCL2 format got implemented this year. A new system for native plug-ins was added, as was support for loading data files alongside policies. Keeping conftest up to date on Linux was made easier than ever with both RPM and deb packages being made available. A new documentation site was launched at https://www.conftest.dev/

OPA Envoy plugin

The OPA Envoy plugin now also supports the v3 transport API, as well as decoding gRPC payloads. Oh, and the project changed its name too — while Istio is very much still supported it is not limited to that implementation, and OPA Envoy plugin was deemed a better name than the OPA Istio plugin.

IntelliJ IDEA plugin

Of all new integrations and projects worked on in the larger OPA ecosystem, the OPA plug-in for IntelliJ IDEA was probably the most anticipated one. After some time in development, a first release was announced this fall and proved to be well worth waiting for. Not only does it cover the necessities, like syntax highlighting, but integrates features for both evaluating rules as well as for running tests right from inside of the policy editor. If you haven't already, make sure to check it out in the IntelliJ plug-in marketplace or at the project GitHub page.

OPA Plugin for IntelliJ IDEA

Credits

To all who engaged with or contributed to OPA and its ecosystem in 2020 — whether as maintainers, contributors, integrators, adopters, users, or by helping others on Slack: You have all contributed not only to OPA but just as much to making this community be such a fun and rewarding place to be. Who knows what 2021 has in store for us? One thing however is for certain — with all the knowledge, skills and creative energy found in the OPA community, it will be no less eventful.

Thank you!

Open Policy Agent Survey Summary (Spring 2020)

OPA Spring 2020 user survey summary banner

Last month we surveyed the OPA community to learn more about user adoption and help us plan and improve the OPA project. We received 204 responses (up 175% from the last survey in April 2019) from over 150 organizations, with 91% of respondents indicating they are in some stage of OPA adoption (i.e., experimentation, pre-production, production.) This post highlights what we learned from the survey results.

Use Cases and Adoption

Most organizations use OPA for multiple use cases

OPA's general-purpose, domain-agnostic architecture is paying off — 51% of respondent organizations use OPA for at least two use cases, with 29% using it for three or more use cases, such as Kubernetes Admission Control, Microservice API Authorization, Application Authorization, Cloud Security and so on. Similarly, only 15% of respondent organizations indicated they only use OPA for Kubernetes Admission Control.

This data supports our own experience from working with users — the typical adoption path involves identifying OPA as a good solution for a specific problem. From there, users realize they can apply it elsewhere, across the stack. Going forward we will continue to focus on features and improvements that help solve a broad set of use cases.

# of Use Cases% of Orgs Using OPA
139%
221%
317%
47%
5 or more6%

Application Authorization is becoming a dominant use case for OPA

43% of respondent organizations indicated they are in some stage of OPA adoption for Application Authorization. In the next survey, we will dedicate a larger section to this use case as it's clearly becoming another pillar for OPA (the other two being Kubernetes Admission Control and Microservice API Authorization). It's unsurprising that OPA is quickly being adopted for Application Authorization because the policies you need to enforce at the top of the stack are typically more sophisticated than those you enforce at the service/transport layer. Going forward, we will continue to add features that improve support for Application Authorization like data fetching, data filtering and UI preflight checks.

Use Case% of Orgs Using OPA for X
Kubernetes54%
Application Authorization43%
Microservices36%
Terraform25%
Data Stores7%
Other17%

Production usage continues to grow

56% of respondent organizations use OPA in production for Kubernetes Admission Control and 47% use OPA in production for Microservice API Authorization. This is not particularly new, but it reinforces the fact that OPA is being used to solve policy, authorization and security use cases across the stack, in production. Going forward we will continue to focus on work that improves stability and performance. We also plan to define a support policy that clarifies what to expect in terms of backporting (e.g., we will guarantee to backport fixes to N-M releases when asked; backports to older requests will be best-effort.)

Stage% of Orgs Using OPA
Production47%
Pre-production20%
Experimenting24%

Microservice API Authorization requires scalable policy authoring

35% of respondent organizations use OPA to enforce (or plan to enforce) policies across more than 100 distinct microservices. This makes sense given that microservice architectures often align with organization boundaries. This implies that policy authoring and distribution need to be scaled across many teams. Going forward we will continue to develop tooling that helps scale the authoring and distribution process (e.g., code generating Rego boilerplate from Open API specifications, the new "opa build" command for producing bundles, etc.).

# of Microservices% of Orgs Using OPA for Microservices
1-1013%
11-2524%
26-5021%
51-1006%
more than 10035%

Policy and runtime portability are important. The survey results showed that 57% of respondents use OPA for more than one Microservice API Authorization use case category (i.e., ingress, egress, or service-to-service) and 45% of respondents use more than one kind of OPA deployment architecture (e.g., library, sidecar, service). Moving forward, we will continue to invest in providing strong support for multiple deployment architectures.

Microservices Use Case% of Orgs Using OPA for Microservices
egress40%
ingress68%
service to service81%
OPA Deployment Type% of Orgs Using OPA for Microservices
Go Library37%
Service42%
Sidecar65%

OPA Feedback

In addition to soliciting use case feedback, we also asked users to provide feedback on OPA features, Rego, gaps in the OPA ecosystem and so on. The results were positive and reinforce our effort to improve content on openpolicyagent.org.

The Rego playground and testing support should be more prominent

65% of respondents said they use the Rego playground or features like policy testing that accelerate policy authoring. 10% were not aware that features like policy testing and coverage exist. Ideally, 100% of users would leverage the test framework, so moving forward, we'll focus on making testing more prominent and drive more users to try out playground and VS Code features, like interactive evaluation, that are invaluable during debugging.

Rego's learning curve

52% of respondents said they are comfortable with Rego or "okay" with their skill level. 27% said they need occasional help. 16% say they struggle. 68% say they were able to learn Rego in under a week. Given that Rego is based on programming language paradigms that are foreign to most developers, these numbers are understandable.

Going forward, we will continue to invest in answering questions on Stack Overflow and improving examples and documentation on the website. Interestingly, the number of people who struggle with Rego was twice as high among those that use OPA for Kubernetes and Terraform compared to Application Authorization. Perhaps this is (at least partially) due to the complex deeply-nested data structures that policies have to be expressed over within those environments.

Examples are the biggest gap in the docs

The survey asked users: "how can we improve the tutorials and documentation in OPA?" By far, the most common request is for more policy examples. Going forward we plan to focus on curating and organizing examples across a range of use cases, as well as building out dedicated sections for specific use cases like application authorization and IAM.

Wrap Up

In the future, we plan to run the survey on a more regular basis with more consistent questions, so that we can compare historical results and observe trends over time. If you filled out the survey, thank you! We know you're excited to receive the t-shirts, and we're working on sending them your way! Due to the current situation, it might take a bit longer than we had expected. If you have not yet filled out the survey, you can still fill out the survey. As always, if you have any questions or additional feedback, we're available on the Slack, GitHub, etc.

…even doggos love OPA [credit: @webbergs, idea: @the_dvorkin]

Open Policy Agent v0.19 Release

Open Policy Agent v0.19 release announcement banner

Last week we released OPA v0.19, containing 63 commits from 12 contributors (of which, 9 were external.) This release includes many important fixes and enhancements, as well as a new Rego parser written in Go that speeds up parsing time by ~100x in most cases. You can find more details on the GitHub releases page.

Community Updates

Since many in-person events have gone virtual due to the COVID-19 crisis, there have been several virtual events, webinars and podcasts featuring OPA over the last few weeks. Here's a quick roundup:

Since KubeCon 2019 in Barcelona, we have asked users to post Q&A style queries on Stack Overflow instead of slack.openpolicyagent.org. The reason is that most answers posted on Slack are not discoverable! If you have Q&A style questions (e.g., "How to test not deny?"), try posting on Stack Overflow and tagging with open-policy-agent.

Faster Parsing & Better Errors

The largest change in v0.19 is the new Rego parser, which is written from scratch in Go. Previously, OPA relied on a generated parser that was defined using PEG (Parsing Expression Grammar [wikipedia]). Over the years, as the grammar has grown, and larger inputs have been thrown at it, the generated parser became a bottleneck (e.g., it could take about 10x longer to parse an input than compile and evaluate the query.

Inside OPA we were able to workaround the performance problems with caching, using Go's "encoding/json" package and manually converting to AST ("Abstract Syntax Tree") values when possible, etc. However, new users embedding OPA as a library would (understandably) make mistakes and wonder why performance was poor. The majority of the performance problems in the generated parser were due to a significant amount of heap allocations required to parse any input.

In addition to performance, we also struggled with usability around parser error messages. If the parser was not able to match an input, you would be presented with an error like "policy.rego:19: no match found". No match? Tell me more!

Rather than attempt to continue incrementally improving the existing parser, we decided to rewrite it from scratch in Go. The result is a new parser that allocates significantly less memory (which improves performance by approximately 100x in most cases) and has better error messages. One important requirement for the new parser was backwards compatibility — the new parser could not break existing policies OR programs that embed OPA as a library (e.g., the parser APIs and the AST types also had to remain the same). To ensure we did not break existing (valid) policies, we checked for differences in the output of the old and new parser for hundreds of thousands of Rego snippets (which deserves another blog post in the future.) Lastly, we also applied the wonderful go-fuzz project to the parser to help catch crashes and other bugs.

Since we no longer have a declarative representation of the language grammar in Go, please refer to the ENBF grammar in the OPA documentation as the authoritative source.

The chart below shows the difference in performance between the old (v0.18 and earlier) and new (v0.19 and later) parser (log scale):

The chart below shows the difference in performance between the old (v0.18 and earlier) and new (v0.19 and later) parser (log scale)

Overall, we are happy with the process. In the future we plan to continue optimizing performance in the parser and looking for ways to improve error messaging and usability.

man(1) pages, http.send, and Emacs support

In addition to the new parser, v0.19 includes dozens of bugfixes and feature enhancements. @olivierlemasle contributed code to generate OPA man pages from the OPA CLI definitions. The man pages are automatically available if you:

brew install opa

Install OPA with homebrew and use &#39;man opa&#39; to learn about it.

Install OPA with homebrew and use 'man opa' to learn about it.

@jpeach submitted a number of patches that improve testing and support for the http.send built-in function. For example, policies can now explicitly set TLS server names as well as certificates and keys when invoking the built-in function (previously they could only come from the environment or local files). This is useful if you want to specify those values in data or as local variables inside the policy itself.

Lastly, the release also includes a pointer to the new rego-mode Emacs package developed by @psibi. The package provides syntax highlighting, formatting and more. In the future, the package could be extended to support many of the same features as the OPA extension for VS Code.

WebAssembly Update

At KubeCon 2019 in San Diego we announced support for compiling OPA policies into WebAssembly (Wasm). Wasm enables OPA policies to execute in new environments like CDNs, service proxies and more without requiring an out-of-process RPC call to query OPA.

This week we are excited to release further support for Wasm in OPA with the new golang-opa-wasm project! This project wraps the wasmerio/go-ext-wasm runtime library to provide convenient APIs for policy execution and more. The golang-opa-wasm SDK is still work-in-progress but feedback and contributions are welcome.

Rego Design Principle #3: Optimize Performance Automatically

Banner for Rego design principle on automatic performance optimization

This is the third part of a blog series on the design principles behind Open Policy Agent's (OPA's) policy language Rego. Previously, we described how Rego's syntax was designed to mirror the structure of real-world policies and how Rego embraces hierarchical data. In this part of the series, we look at Rego/OPA's commitment to automated performance optimization so that policy authors can focus on writing readable, maintainable policies, and OPA can shoulder the burden of evaluating those policies efficiently.

Performance is Important, but Policy Authors Should Be Able to Ignore It

The goal of OPA is to take policies that people write and automatically enforce, monitor and remediate them. No more relying on people to remember the policies, to understand them or to correctly apply them. OPA helps everyone follow the rules and protects the infrastructure, applications, etc. from security, compliance and operations problems.

The scale and speed of modern, cloud-native systems means that OPA policies are routinely applied to millions of objects and actions. Pinterest, at the last Kubecon, described using OPA to make 400k decisions per second (8M with caching), globally across all infrastructure. At that scale, how long it takes to evaluate an OPA policy, how much memory it uses, and how many requests can happen in parallel all matter a great deal.

Despite the importance of performance one of OPA's design principles is to minimize how much a policy author worries about performance. The policy author should write the logic of her policy so that it is easy to read, maintain, extend, and combine with other peoples' policies. The author shouldn't be forced to complicate the logic of her policy in order to make it perform well.

The policy author handles correctness. OPA handles performance.

In the end, this design principle is all about usability. Policies are easier to write because you only think about correctness and not performance. Policies are easier to read because they're written closer to the way people think about them in the real world. Policies are easier to maintain and extend because you write them each individually and leave global optimizations to OPA. Upgrade OPA and your policies get faster.

As a point of comparison every programming language aims for good performance too, but OPA's goal is qualitatively more ambitious in the sense that even the global asymptotic runtime of policy evaluation is something that the policy author should be able to ignore. If the clearest way to write/read your policy looks like an O(n¹⁰) algorithm in a traditional language even though it can be done with 10 linear scans, OPA aims to analyze that policy, reformulate it, set up the proper indexing and evaluate it in linear time.

How OPA Automates Performance Optimization

Our approach to realizing this design principle was to base Rego on database query languages and leverage 50 years of R&D on automated performance optimization. In particular:

Rego has no side-effects. Rego always produces the same outputs given the same inputs, and there are no side-effects before, during or after evaluation. Side-effects would include writing to a file or modifying the value of an in-memory object. Side-effects greatly complicate the design of optimization algorithms because those algorithms need to understand how to preserve those side-effects.

Rego rules are unordered. A Rego policy means the same thing regardless of how the rules are ordered. Since ordering is irrelevant, the meaning of each statement stands on its own and can be optimized on its own.

In contrast, in languages where ordering matters, every optimization must respect that ordering. Firewall rules are a perennial example, where to understand the impact that the 1000th firewall rule has, you need to understand the impact of the first 999. (Rego does support the 'else' keyword for ordering, but we advise using it sparingly.)

# Both allow and deny are true.
# Order of the statements does not matter.
allow = true {
input.method == "GET"
input.path == "/"
}
deny = true {
input.method == "GET"
input.path == "/"
}

Rego rules are simple. Just about every statement in Rego is a conditional variable assignment. A Rego statements assigns a variable to a value if some conditions are true. This uniformity and simplicity help ensure that optimizations apply across all Rego policy statements.

allow = true { # allow is assigned true if ...
input.method == "GET" # method is "GET" AND
input.path == "/" # path is "/"
}

Rego is designed in layers. Rego was designed like an onion: the core is syntactically quite simple and highly efficient; the layer above that is syntactically more expressive at the cost of some performance; and so on. The outer layer today is quite expressive but not Turing complete (though in the future we could relax some of the restrictions on the language if we wanted that escape hatch). Below is a diagram showing some of the layers of the language.

Performance and Expressiveness layers for Rego

Of course there are limits to what can be done at all and what has been implemented, but it's a clear design principle — that the language itself should be amenable to deep, automated analysis and global optimizations. At the time of writing, OPA has the following features either built out (GA), in progress (WIP), or planned.

Rego has automatic, multi-dimensional indexing (GA). OPA analyzes rules and automatically constructs a trie that finds the minimal set of applicable rules. For example, the following rules will be represented as the trie shown below.

# Rules as they appear in a Rego file
allow = true {
input.method == "POST"
input.path == "/pets"
something_complex
}
allow = true {
input.method == "GET"
input.path == "/pets/dogs"
}

Rules are represented in memory for fast evaluation

Partial evaluation (GA). Partial evaluation attempts to compile a policy from one layer into a lower layer (sometimes at the cost of creating additional rules). This could, for example, convert a linear-time policy into a constant-time policy.

# Before partial evaluation, a data-driven policy that is easy
# for people unfamiliar with Rego to contribute to.
allow {
op = allowed_operations[_]
input.method == op.method
input.resource == op.resource
}
allowed_operations = [
{"method": "PUT", "resource": "air-conditioner"},
{"method": "GET", "resource": "security-camera"},
{"method": "POST", "resource": "garage-door"},
]

After partial evaluation runs we get rules that the indexer can more easily analyze and organize in a trie.

# After partial evaluation
allow {
input.method == "PUT"
input.resource == "air-conditioner"
}
allow {
input.method == "GET"
input.resource == "security-camera"
}
allow {
input.method == "POST"
input.resource == "garage-door"
}

Compilation to WebAssembly (WIP). WebAssembly is a popular general-purpose virtual machine that has implementations in a growing number of languages like Go, Node, JavaScript, and Java. The Rego WebAssembly compiler takes a Rego policy and generates custom WebAssembly code that implements that policy, and like most compiler technology eliminates overhead for the interpreter and provides the opportunity for deep, automated performance tuning.

Automated asymptotic analysis (WIP). While still a work in progress, the goal of this tool is to identify the run-time complexity of a policy. Which of these three layers does it belong to? If it is a linear-time policy what slice of JSON data is it iterating over?

For example, in Kubernetes a common policy is to check if all images come from a trusted registry, say hooli.com. The complexity analysis will tell you that this policy has complexity O(input.request.object.spec.containers).

deny {
input.request.kind.kind == "Pod"
some i
image := input.request.object.spec.containers[i].image
not startswith(image, "hooli.com/")
}

Query optimization (Planned). As use cases like audit that require deeper searches over significant amounts of data become more popular, we plan to include optimizations that analyze dependencies, reorder rule evaluation, reorder conditions within rules, shift evaluation of conditions between rules, and the like.

Summary

One of OPA and Rego's design principles is that Rego policy authors should focus on correctness, maintainability and composability. They should not complicate their policy logic to make policy evaluation more efficient. More specifically:

  • OPA aims to optimize both local operations and the global asymptotic runtime
  • While Rego's syntax looks like a programming language, its semantics is based on database query languages.
  • The automated optimizations implemented in OPA continue to grow over time.

While OPA and Rego today have made terrific progress in terms of realizing the goal of automated performance optimization, there's still a lot to do. If you want to help, hop onto Slack or Github and contribute!

If you want to know more, check out the other blog posts in the series:

Rego Playground: New Features

This time last year we launched the Rego Playground. The playground provides an online interactive environment where users can experiment with and share OPA policies.

Today, we are excited to release features in the playground that will help new users get up and running with OPA even quicker than before. Let's have a look.

Feature: Examples for Kubernetes, Envoy and more

Anyone who designs user interfaces (or perhaps any software project/product) is probably familiar with the "blank slate" problem: all of the designs assume the system is loaded with data. However, when new users arrive that data does not exist and the system feels empty.

Since the beginning of OPA we have focused on providing detailed documentation so that new users (a) have something to look at and (b) can figure out whether OPA will solve their problems. Of course, this assumes people want (or have time) to read the docs! Ain't nobody got time for that.

Rather than trying to tell everyone to RTFM, we have decided to preload the playground with a catalogue of examples for common use cases like Kubernetes admission control, API authorization with Envoy and more:

Rego Playground examples catalogue

The catalogue is searchable and filterable. Over time we plan to continue to curate the catalogue to ensure they demonstrate common use cases and patterns in policy language.

Feature: Kick the tires with OPA bundles

Once you have written a couple policies or modified existing ones, the next thing you often want to see is how OPA can be deployed and have policies distributed to it.

OPA supports a feature called "bundles" that enable policy discovery and distribution. Bundles are just gzipped tarballs containing policy and data files. When bundles are enabled, OPA continually tries to download and activate the latest version of policy and data that control its decision-making. Bundles are designed to be CDN compatible so that policy distribution can scale easily. All you have to do is set up a webserver to host your bundles (or rely on services like AWS S3), however, this is often more work than people want to embark on while they kick the tires.

To help users get up and running with bundles, we have extended the playground to serve published policies as bundles. All you have to do is click "Publish".

Rego Playground publish bundle workflow

Once you publish your policy, the playground displays the steps to:

  • Download and run OPA locally
  • Configure OPA to use your published policy
  • Test the policy with the input from the playground

Any edits to the policy that are published from the same browser window will propagate to OPAs configured to use the playground bundle. This lets you exercise OPA's dynamic policy update capability (aka "hot reloading").

Feature: Improved support for context-aware policies

When software systems query OPA for policy decisions they can supply arbitrary JSON data as input. This data drives policy decision-making, however, in many cases, it's not sufficient — more information about the state of the world is required to render a decision. In OPA, we often refer to this information as "context". There are various ways to load context into OPA, however, one of the most common ways is to cache data in-memory alongside the policies.

When context is cached in-memory it's referenced under the data global variable. If you have used OPA for Kubernetes admission control, you may have seen policies that refer to the cached state of the Kubernetes cluster maintained inside of OPA in admission controller deployments (e.g., data.kubernetes.ingresses).

In the initial version of the playground, we did not provide support for loading arbitrary external JSON values under data. This was primarily to keep the UI as simple as possible and also because technically you can just define any JSON values you want inside of the policy itself — references to JSON defined in the policy are identical to references to raw JSON that would be cached in OPA.

So while it was possible to experiment with context-aware policies inside the playground, it was a bit non-obvious. In the latest release, there is now an empty "Data" panel (along with "Input" and "Output") that lets you load arbitrary JSON values under data:

Rego Playground Data panel

Rego Design Principle #2: Embrace hierarchical data

Diagram of OPA making decisions from query, data, and policy

This is the second part of a blog series on the design principles behind Open Policy Agent's (OPA's) policy language Rego. Previously we described how Rego's syntax was designed to mirror the structure of real-world policies. In this part of the series, we look at why and how Rego exclusively uses hierarchical data (e.g. JSON and YAML) to represent the raw information it uses to make decisions and to represent the decisions themselves. In the next part of the series, we discuss why and how OPA aims to optimize the performance of policy evaluation automatically.

Quick OPA Refresher

OPA is designed to offload policy decisions from a broad range of software services. You typically run OPA on the same server as the software needing policy decisions and cajole that software into asking OPA for a policy decision whenever it needs to. As shown in the diagram below, OPA makes a decision using the following pieces of information:

  • Policy query. An arbitrary JSON document provided by the service needing a policy decision. Think of the policy query as the concrete information (e.g. user-action-resource) that OPA needs to make a decision about.
  • External data. Any number of JSON documents injected into OPA out-of-band of the policy query that represent what's happening in the real world (e.g. the current resources in a k8s cluster or resource attributes like owner, size, etc.) and that are kept up to date as the world changes.
  • Rego policy. One or more Rego policies. Rego is a custom language purpose-built for expressing policy across any domain.

The focus of this blog post is to explain why and how we chose to use JSON to represent the policy query, the external data, and even the policy decision itself.

JSON is Everywhere

JSON (or more generally hierarchically structured data) is pervasive throughout the cloud-native ecosystem. Public clouds, kubernetes clusters, No-SQL (and even SQL) databases, service meshes, microservice APIs, and application configuration all ingest and export their state in JSON. Hierarchical data (as opposed to say relational data stored in classic SQL databases) is here to stay, arguably because it is well-suited for modeling many different aspects of software applications and the infrastructure they run on. And further, the prevalence of HTTP/JSON APIs makes JSON a pervasive format for exchanging information.

What this means for OPA is that it's a near certainty that when a service is asking OPA for a policy decision, it will have some hierarchical data that OPA needs to make the decision.

For example, maybe it's a JSON Web Token (JWT) that represents the user and her attributes:

{
"sub": "1234567890",
"name": "Alice Smithsonian",
"iat": 1516239022,
"groups": ["employee", "billing-manager"]
}

Or, maybe it's information about the attributes for a pet at a pet store:

{
"id": "i0779921",
"name": "Lassie",
"breed": "collie",
"owners": [{
"first": "Rudd",
"last": "Weatherwax"
}]
}

It could also be a description of the configuration for an application running on Kubernetes (here shown in the usual k8s YAML that converts easily to JSON):

apiVersion: admission.k8s.io/v1beta1
kind: AdmissionReview
request:
kind:
group: extensions
kind: Ingress
version: v1beta1
object:
metadata:
name: prod
labels:
costcenter: retail
spec:
rules:
- host: initech.com
http:
paths:
- path: /finance
backend:
serviceName: banking
servicePort: 443
- path: /retail
backend:
serviceName: storefront
servicePort: 8080

All across the stack, from infrastructure to microservices to the business data stored by an application, JSON is pervasive for representing information. Moreover, even in those areas where JSON data is not pervasive like SQL databases, it is straightforward to convert flat, non-hierarchical data into JSON; whereas, converting JSON into a non-hierarchical data format while possible presents many usability challenges.

How OPA interacts with the outside world

Remember that OPA can consume two sources of data to make policy decisions:

  • the data that the service provides as the policy query
  • the external data that gets injected into OPA that represents the state of the outside world

Both of those are arbitrary JSON. OPA does NOT impose any kind of schema or data model on those JSON documents. All OPA knows is that it's a chunk of JSON; it is up to the policy author to understand what that JSON represents in the world and write the policy that makes the appropriate decision.

We could have designed OPA differently. We could have designed OPA to have a schema or data model for each domain (e.g. k8s, service mesh, databases, applications) and required the outside world to adapt its data to OPA's model.

For example, suppose OPA required every policy query to have three fields:

  • username: a string that represents the user taking an action
  • action: a string that names the action of the user is trying to take
  • resource: a string identifying the resource being acted upon

This would mean that every application asking OPA for an authorization decision would need to supply exactly those three fields. If the application had the user information stored in the JWT as shown below, it could not just hand that JWT to OPA — it would need to extract the sub (subject) value and include it as the username value.

{
"sub": "1234567890",
"name": "Alice Smithsonian",
"iat": 1516239022,
"groups": ["employee", "billing-manager"]
}

Imposing a schema or data model would have made building OPA easier because it shifts the burden for integration to the outside world. Every system in the world that wants to integrate with OPA would need to include OPA-specific code that transforms the data to meet OPA's requirements.

Moreover, the same is true for the external data that OPA uses to make decisions. If OPA imposed a data model on all external data, the system pushing that data into OPA would need to understand OPA's data-model and transform the data from the outside world to match that model.

Instead, OPA was designed to ingest arbitrary JSON data for both the policy query and external data. This makes integrating with OPA easy: just convert the information into JSON (which every programming language has standard libraries for) and send it across. No need to ETL your data to get it into OPA — any webhook will suffice to integrate OPA. In short…

OPA should adapt to data in the outside world, not the other way around

Ingesting JSON data in whatever form is natural for the outside world is easy, but it does mean that the policy language Rego needs to be flexible enough that people can write policies that adapt to that format. The policy language can't rely on a fixed location for the username or the action, for example. It must be expressive enough that people can write policy that bridges the gap between the world's data model and the format that is best for expressing policy.

Rego support for JSON

The starting point for a Rego policy is (i) an arbitrary JSON object representing the policy query (a.k.a. input) provided by the external software (e.g. an API call, a configuration file, a data element, etc.) and (ii) some number of arbitrary JSON objects representing the state of the world. Neither OPA nor Rego understand what that data means in the real world, but the policy author does. The policy author writes Rego to encode the logic that navigates through those JSON documents and compares them to hard-coded values or other bits of JSON in order to make a decision.

For example, for a simple HTTP API the input JSON object could be:

{
"method": "GET",
"path": "/dogs/dog123",
"user": "alice",
"roles": ["customer", "guest"]
}

As a policy author, I know that this JSON object represents an HTTP API, but Rego doesn't. If I want to allow all GET requests to the root path, I write a simple rule with conditions on the input document (a global variable in Rego representing the policy query provided to OPA):

allow {
input.method == "GET"
input.path == "/"
}

This example shows simple equality checks with strings, but in general you might need to break a path like /dogs/dog123 into multiple pieces, manipulate numbers, inspect the internals of a JWT, etc. The scalar values in JSON often contain information that needs to be extracted or manipulated.

Rego must manipulate JSON scalar types: booleans, numbers, strings, and null

To that end, Rego has 50+ built in functions documented at openpolicyagent.org that provide all kinds of basic functionality needed to inspect and construct the scalar JSON types.

Of course, the whole point of supporting JSON is not the scalar types — it's the composite types: arrays and objects. Without those, there's no hierarchy at all.

There are two key requirements that arise from supporting JSON arrays and objects: the ability to drill down through a hierarchy (which you've already seen via dot notation) and the ability to iterate over elements of a collection (elements of an array or key/value pairs of an object).

Rego must navigate through deeply-nested arrays and objects

Navigating through arrays and objects when you know the exact path is straightforward in Rego. It uses same syntax used by many programming languages: dot-notation and bracket notation.

For example, suppose the following JSON object is the input.

{
"id": "i0779921",
"name": "Lassie",
"breed": "collie",
"owners": [{
"first": "Rudd",
"last": "Weatherwax"
}]
}

You can write all of the following expressions to navigate through this JSON document.

input.name # "Lassie"
input["name"] # "Lassie" x.y is syntactic sugar for x["y"]
input.owners[0] # First element of owner's array
input.owners[0].first # "Rudd"

More interesting is iteration. 99% of Rego statements are simple if statements, and iteration is primarily used as a condition in one of those if statements.

For example, say you want to allow an admin to perform any operation, and you're given an input that lists all the user's roles.

{
"method": "GET",
"path": "/dogs/dog123",
"user": "alice",
"roles": ["customer", "guest"]
}

You need to write a policy that says the request should be allowed if there is some element of the roles array that equals "admin".

Iteration in Rego uses the keyword some. You write an expression testing whether a condition is true and apply some to the variables in that expression that you want to iterate over.

In the admin example, you write the following Rego to check if there is some index i of the roles array where input.roles[i] equals "admin".

allow {
some i
input.roles[i] == "admin"
}

You can apply some to many variables at once. For Kubernetes policies this happens all the time. Here is an object that is roughly what Kubernetes hands over for admission control — notice how deeply nested the data is.

kind:
kind: Ingress
group: extensions
metadata:
name: prod
labels:
costcenter: retail
spec:
rules:
- host: initech.com
http:
paths:
- path: /finance
backend:
serviceName: banking
servicePort: 443
- path: /retail
backend:
serviceName: storefront
servicePort: 8080

If you want to deny the creation of this resource whenever there is some servicePort that is not 443, you would write the following Rego.

deny {
input.kind.kind == "Ingress"
some i,j
input.spec.rules[i].http.paths[j].backend.servicePort != 443
}

While that path to servicePort is somewhat long, it is simply the nature of the data. Seeing the path written out in a single line makes it relatively easy to map it back to the real data, which can help the reader understand the intent of the rule.

In contrast, in traditional programming languages, you need to decompose that JSON path into chunks and dictate exactly the range over which you want to iterate one variable at a time. Here would be the same example in Python.

function deny():
return input.kind.kind == "Ingress" and deny_aux()

function deny_aux():
for rule in input.spec.rules:
for path in rule.http.paths:
if path.backend.servicePort != 443:
return true

As a reader, to understand what the Python says in terms of the data, you need to reconstruct the JSON path by composing the paths in the for loops and if statements. The decomposed path approach shown in Python is closer to an implementation of policy than the policy itself.

Of course, Rego is flexible enough that you can decompose paths if you want.

deny {
input.kind.kind == "Ingress"
some i, j
rule := input.spec.rules[i]
path := rule.http.paths[j]
path.backend.servicePort != 443
}

Having had Rego's ability to iterate in different ways for the last few years, we find that sometimes we decompose paths and sometimes not. Personally, I typically avoid decomposing paths as I find them easier to read when I come back weeks or even days later because I can compare the policy statement more directly to the documentation for that JSON data; and often I don't even need the documentation because the path itself is self-explanatory.

Summary

Rego was designed to express policy over JSON data natively.

  • Why JSON? JSON is pervasive in cloud-native environments, which means that the external data and inputs that OPA uses to make policy decisions is easy to come by.
  • Rego is designed to adapt to the world around it — not the other way around. This leads to a low barrier for integrating with OPA, often requiring no OPA-specific code.
  • Rego has first-class support for inspecting JSON values. It has 50+ built in functions for string manipulation, JWT manipulation, network CIDR math, etc. And Rego has first-class support for navigating through deeply-nested arrays and dictionaries.

OPA was designed to be integrated into a wide array of software systems, and as such ease-of-integration is paramount. Rego's flexibility makes it applicable to a wide variety of use cases, and moreover makes it easy to integrate OPA across the cloud-native stack.

If you want to know more, check out the other blog posts in the series:

Rego design principle #1: Syntax should reflect real-world policies

Banner image for Rego design principle number one blog post

Sometimes people ask why Rego, OPA's policy language, looks or behaves the way it does. Part of the answer is that Rego came about after having built two other general-purpose policy languages, and lessons learned from that process shaped this one. This multi-part post lays out the results of that journey — the key design principles for Rego, why they're important, and how they influenced the language.

Related posts in the series:

The first design principle holds that Rego's syntax "should NOT be designed as a general-purpose programming language that reads from disk, writes to network sockets, supports multi-threading, defines custom datastructures, etc."

Refresher on OPA

OPA is a general purpose policy engine that separates policy decisions from the software services that enforce them. It bases decisions on input data, a Rego policy, and optionally external data reflecting real-world state (e.g., on-call schedules or resource ownership).

These functional requirements ensure that OPA has enough flexibility and generality to make context-aware decisions across a broad range of use-cases: admission control, API authorization, risk-analysis, data-filtering. OPA runs as a lightweight agent or library on the same server as the software service, thereby achieving both the availability and performance needed for policy decision-making in modern, cloud-native computing environments. It has been integrated with over 20 popular software systems and is at the time of writing an incubating project within the Cloud Native Computing Foundation.

Natural encoding of real-world policies

The goal is that Rego should map closely onto plain-language rules and regulations. A useful litmus test: reading Rego aloud should sound close to the source documentation.

This readability test is especially important because of the broad range of stakeholders who are responsible for policy: developers, operations, security, and compliance. The less of a translation there is from the PDFs and wikis the easier it is to believe that your Rego policies are correct, that operationally you're on solid ground, that your auditors will be convinced the policies do what they should, and that your security vulnerabilities are properly mitigated.

A Rego policy is a collection of if statements

If statements in Rego

Nearly every Rego statement functions as an if-statement, but with different proportions than typical programming languages:

"Programming languages typically have small if conditions and relatively large then blocks... in Rego the if condition is a potentially large block of expressions, and the then part is a single expression."

# Rego example.
# An API call is allowed if the method is a GET
allow {
input.method == "GET"
}

Multiple statements inside a rule are ANDed; ORs are expressed via multiple rules:

# Rego example.
# An API call is allowed if the method is a GET
allow {
input.method == "GET"
}

# An API call is allowed if the method is POST and
# the user is an admin
allow {
# Only admins can create new objects
input.method == "POST"
input.user_is_admin == true
}

An equivalent JavaScript version, given for contrast:

// Not a Rego example. A JavaScript example.
function allow() {
return allow1() || allow2();
}

function allow1() {
return input.method == "GET";
}

function allow2() {
return input.method == "POST"
&& input.is_admin == true;
}

Rego "has no need for explicit ANDs and ORs" within a rule, and includes "an explicit NOT operator." A self-documenting example:

# Rego example.
allow {
operation_is_create
user_is_admin
}

Rich policy decisions

Real-world policies sometimes make a decision as simple as allow or deny but often they go far beyond that. What about warn or error? Or what if the decision is a rate-limit (number), a permitted hostname (a string), or the clusters to deploy an application (an array). Since OPA is a general-purpose policy engine (not an authorization engine) it needs to handle a rich collection of policy decisions.

Because inputs are typically JSON, decisions in Rego can likewise be any JSON type—not just booleans.

A Rego decision is a JSON document

Rich Policy Decisions in Rego

allow and deny are plain variables, not keywords, defaulting to true:

# Rego example.
allow = true {
operation_is_create
user_is_admin
}

Non-boolean decisions work the same way:

risk = 100 {
input.method == "DELETE"
}

Partial sets can build up collections, such as error messages:

deny[msg] {
input.method == "DELETE"
not user_is_resource_owner
msg := "only the owner of a resource may delete it"
}

Collaboration

Real world policies are decided upon by multiple individuals and even teams. The security team might put global requirements in place across all application development teams and each application development team might put policy in place for their app. The k8s cluster administrator puts global policies in place but also delegates policy responsibilities to namespace-level admins. Policy is by its nature a collaborative endeavor, and Rego should recognize and support that.

At some level collaboration is supported simply because Rego is a text-based policy language (aka "policy-as-code"). Teams can check Rego policies into source control, and use peer-review to manage changes to it. We knew, however, that there are all too many examples where teams want to work more independently than that, e.g. putting global policies in place for an entire cluster and empowering team leads to manage policy for their portion of the cluster. That means that different teams should be able to write their policies independently from each other, and then combine those policies after the fact.

If different teams write different policies independently, they will inevitably end up with conflicts from time to time (e.g. one allows the decision and the other denys it). And so there must be a way to resolve those conflicts, based on a variety of different factors. Resolving conflicts is not always easy. It may depend on the teams involved, the kind of decision being made, the resource and its attributes, the time of day, and many other factors. Ergo the conflict resolution mechanism must be tantamount to a policy itself. Sometimes languages are designed to avoid the problem of conflict resolution by designing the language to not express conflicts at all. But this approach inevitably leads to problems because when two teams disagree in the real world and there is no way for them to express that disagreement in the policy language, they simply can't write the policy that they truly mean — the language is ambiguous in terms of the author's intent.

Rego policies are composable; conflict resolution is a policy itself.

Collaboration in Rego

Each policy lives in a package:

package microservice.authorization

Separate teams can define their own packages:

package developer
allow {}

package security
allow {}

A combining policy can reference other packages via the data keyword:

package main
allow {
data.developer.allow
data.security.allow
}

A more nuanced conflict-resolution example, where security's decision takes precedence when it has an opinion:

package main
# allow if the security team allows (and does not deny)
allow {
data.security.allow
not data.security.deny
}
# allow if the security team has no opinion and
# the developer team allows (and does not deny)
allow {
not data.security.allow
not data.security.deny
data.developer.allow
not data.developer.deny
}

Summary

The post recaps three requirements: policies are mostly if-statements and should read naturally; decisions can be any JSON value rather than just booleans; and composition/conflict-resolution use the same rule mechanics as ordinary policy logic.

Further reading:

KubeCon US 2019 Recap

Banner image for the KubeCon US 2019 recap post

A few weeks ago San Diego hosted the largest KubeCon ever with nearly 12,000 attendees. Let's take a look at some OPA highlights!

OPA Summit 2019

OPA Summit 2019

Day-0 (the day before the Kubecon main event) marked a big milestone for the project: we held our first-ever OPA summit!

Tim Hinrichs shows off the new website to 100+ attendees at the event.

The goal of this summit was to showcase a variety of use cases from companies running OPA in production. We received around 20 submissions to the CFP and then selected a handful of talks that demonstrated impressive scale or new applications for OPA in production.

During the talk about how Pinterest uses OPA, Jeremy Krach and William Fu shared how they architected policy distribution and management for authorization on bare EC2 instances and multi-tenant Kubernetes clusters at Pinterest. Their talk walks through the entire policy lifecycle from authoring to distribution to enforcement. After covering the pipeline, they explained exactly how they offload policy decision-making from services. In terms of volume, their Kafka integration sees the highest traffic. At peak, OPA serves ~450K decisions/second across their clusters (and with caching that increases to ~8.5M decisions/second globally).

William Fu and Jeremy Krach from Pinterest share details on authorization scale at Pinterest.

William Fu and Jeremy Krach from Pinterest share details on authorization scale at Pinterest.

Michael Sorens from Chef spoke about how they use OPA to implement IAM in Chef Automate. Michael highlighted OPA's TDD-based approach to policy authoring and explained how they use OPA to implement pre-flight authorization checks that control which UI components are rendered based on user permissions. This use case is becoming more common as OPA is increasingly used higher up in the stack.

Michael speaks about applying TDD to policy.

Michael speaks about applying TDD to policy.

You can find other great OPA Summit talks from Atlassian, Trip Advisor, and Capital One on YouTube. We are looking forward to hosting the next OPA Summit in Boston at KubeCon US 2020!

Kubernetes: the guardrail enforcement point

At KubeCon after the day-0 summit, engineers from companies like Yelp, Goldman Sachs, Reddit, Adobe, Google, and Microsoft spoke about how they use OPA.

There were several excellent sessions about OPA Gatekeeper and admission control use cases in Kubernetes. OPA is used extensively to enforce guardrails over compute, network, and storage resources in Kubernetes and this was reflected at KubeCon.

Two talks that highlight how Kubernetes is becoming the defacto standard for managing desired state were from Rita Zhang (@ritazzhang) and Ivan Sim (@ihcsim) from Microsoft and Buoyant (respectively) and Sandeep Parikh (@crcsmnky) from Google. This is great for platform administrators because it means they can leverage OPA Gatekeeper to enforce guardrails across not just native Kubernetes resources (e.g., Pods, Services, etc.) but also service mesh resources (e.g., Linkerd, Istio), CI/CD resources (e.g., tekton.dev), cloud resources (e.g., crossplane.io), and more.

Miguel Uzcategui (Goldman Sachs) and OPA co-founder Tim Hinrichs (CTO of Styra) spoke about how Goldman Sachs uses OPA to do policy-based provisioning in Kubernetes. They explain how Goldman Sachs implemented Kubernetes controllers that offload decision-making to OPA so that when namespaces are created, resources like quotas, roles, and persistent volumes (and claims) are automatically instantiated (and then re-converged if something changes.) This is important for maintaining strict requirements around security and availability at Goldman Sachs. They also show why OPA is a good fit for this problem (e.g., easier testing, ability to use external context in decision-making, etc.) and how it has performed in production for nearly a year.

Miguel shares results from running the OPA in production for over a year.

Miguel shares results from running the OPA in production for over a year.

Outside Kubernetes: App configuration and Microservices

@garethr's talk about applying OPA policies earlier in application lifecycles highlighted OPA's general-purpose nature. His talk was filled with examples and demos that show how to use OPA and conftest to validate configuration files (e.g., Pipfiles, Dockerfiles, etc.) and plug into CI/CD systems.

Finally, on Thursday, Daniel Popescu and Ben Plotnick talked about how Yelp evolved their security infrastructure (using OPA and Envoy) as the company transitioned away from a monolith. Their talk highlights how perimeter-based security does not scale to microservice architectures and how development of custom policy languages is challenging. The talk provides a deep dive on how they leverage Envoy and OPA to implement mTLS and access control across a fleet of microservices. They also discuss the gradual migration off their custom policy language by transpiling to OPA.

Authorization - Service Mesh architecture diagram showing Service A and B, Envoy, ext_authz, and OPA

This post only highlights a few of the excellent talks from KubeCon about OPA so if you want to watch more check out this playlist on YouTube.

Conclusions

KubeCon San Diego demonstrated how many companies run OPA in production for a variety of use cases. After starting the project nearly four years ago it is very exciting to see it fulfilling the original goal of modernizing and enabling policy enforcement across the stack.

Last year we saw rapid growth in end-user adoption, the launch of OPA Gatekeeper, and promotion to the CNCF Incubating tier. In 2020 we plan to continue investing in performance and usability for the core of the project, new integrations leveraging the recent WebAssembly compiler feature, and better documentation of reference architectures.

See you all in Amsterdam and Boston!