Skip to content

Instantly share code, notes, and snippets.

@srenatus
Last active May 31, 2023 18:08
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save srenatus/29256c1d2b5f8c1f4589caf6816da6dc to your computer and use it in GitHub Desktop.
Save srenatus/29256c1d2b5f8c1f4589caf6816da6dc to your computer and use it in GitHub Desktop.

OPA, oso, oh no!

You may have gotten accustomed to Keep Calm and Use OPA, and then suddenly, there's this new thing that you feel strangely pulled towards.

In what follows, I'd like to start comparing Open Policy Agent (OPA) and oso, two general purpose policy engines that can be embedded in your application or used in your infrastructure.

Caveat

I've been using, and contributing to, OPA for a few years now. Objectivity still is the goal of what follows, I don't want either solution to appear in a bad light.

They're both great open source projects, and their respective developers, as far as I had a chance to interact with them, are fabulous people. You can find them in the OPA slack and the oso slack, respectively.

Maturity

OPA is the older project, and can safely be said to be maturer. In practice, that means you'll find more documentation and training material, as well as blog posts or stack overflow answers. There's a playground, similar to what can be found for other programming languages, and a lively Slack community. It's got a VScode plugin that can evaluate policy code, trace it, run your tests and show their code coverage per line. There are many integrations readily available, such as using OPA as an external auth server with Envoy (and derivative products), or using it inside conftest for verifying that any kind if config, json, terraform, and whatnot files satisfy your policies. Furthermore, its use in Kubernetes via the gatekeeper project has proven to fulfill an urgent need well. A good share of management features make OPA instances possible in different setups (check the bundle API, status API, decision logs, for more details).

In OPA's shadow, oso has been made public and open source as developer preview earlier this year. The main focus so far has been the application integration use case: embedded oso into an application to use high-level logic code to determine authorization decisions. It's written in Rust, and comes with libraries for a handful of popular languages. As of this moment, the supported languages are Python, Ruby, Java, NodeJS, Rust; there are SDKs for Flask and Django. The editor integration so far only does syntax highlighting. The other features of OPA's VScode plugin don't really apply to oso, see the notes on its interface below.

There are companies backing the projects, Styra for OPA, and oso for oso. Both projects have not had a 1.0 release, although OPA is considered "close" (see these open issues).

Interface

A unique feature of oso is its tight integration with the host application. Any policy written in Polar, its policy language, can reference constants and classes of the host application, given that they've been registered with oso. Classes can be used to have specializing rules corresponding to different classes of the rule arguments. (It's powerful enough to equip Ruby with multiple-dispatch, see this little example.)

This neat approach is a two-edged sword: any methods defined on, say Ruby's String class, are available to policy code. Under the hood, the FFI with the Rust library exchanges JSON messages, and these allow the policy engine to call host application methods, retrieve constants, and figure out class hierarchies. Besides giving you all that you could wish for as builtin String (Number, Array, ...) methods practically for free, you can register your own classes and methods to for example have a database lookup done during your policy evaluation.

What can be considered a downside of this is that your policy code that is used with a Ruby app can't necessarily be used with a Python app without changes. So far it doesn't seem like this has been a strong concern for the community, though.

Oso provides a REPL, or rather, one REPL per host language, too.

OPA, on the other hand, can be understood as having a JSON-in/JSON-out interface. It can be embedded as a Golang library, or used as a sidecar or centralized service, exposing an HTTP API. The instance manages in-memory data and policy code, and takes changing input with any policy evaluation request, returning the result of the evaluation. A myriad of built-ins are available, and new ones can be added through Golang code. This last bit is not as simple as you may want it to be, due to Golang's rather challenging plugin story.

WebAssembly

Both projects support WASM, but in very different ways:

In OPA, you take policy code, a query, and compile it into a WASM module. OPA will create an imperative intermediate representation of your query evaluation, and translate that into WASM. A subset of the OPA built-ins is available in WASM, and others have to be provided by the host application.

Oso, on the other hand, exports its policy engine as WASM module. Rust's excellent support for WASM makes this feasible, and it's also what the project uses to provide the NodeJS oso library.

I think oso's approach, while perhaps slower in practice (no benchmarks done, don't trust me on this), allows for a more straightforward way to create custom policy-enabled WASM modules. If you use Rust for them, and import said crate, you may be able to export a WASM module that satisfies some other interface, like proxy-wasm.

Policy language

The languages of OPA and oso, Rego and Polar respectively, both have logic programming semantics. Anyone who's ever dealt with Prolog will feel right at home in either one. You may feel more at home in Polar, since it features Horn clauses -- but you have to use if instead of :- and get used to writing and instead of putting a , in. (There's even a cut, also written out and not !.)

Rego is leaning more towards Datalog in its semantics, forbidding recursion. However, built-ins are present to allow for circumventing this restriction if your data calls for it (I'm thinking about the graph.reachable built-in for hierarchical org structures). Multiple rules of the same name function as implicit OR in both languages; Rego implicitly puts an AND between all statements in one rule body.

Next Steps

On the surface, it's difficult to compare the languages' expressive power. It would be interesting to create a "rosetta stone" type of resource where different constructs of both languages are translated into each other. Specifically object and array comprehensions as supported by Rego seem not entirely obvious to me in Polar.

With something like that in place, we would be able to come up with a a set of benchmark problems, and have a way to compare their performance more realistically. For oso, a part of the problem with performance measurements lies in the deep integration with the host language: it's not clear at the outset that JSON encoding/decoding performance of the host environment is irrelevant for benchmarking policy evaluation.

@jneo8
Copy link

jneo8 commented Jul 5, 2021

Nice sharing

@srenatus
Copy link
Author

srenatus commented Jul 5, 2021

@jneo8 I'm afraid not all bits are up to date -- I've become much better acquainted with OPA's Wasm now 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment