Recently, there was an exciting announcement from OPA about "Rego on WebAssembly".
OPA v0.15.1: Rego on WebAssembly. We’re excited to announce that with OPA… | by Torin Sandall | Open Policy Agent
Torin Sandall ・ ・ 5 min read
blog.openpolicyagent.org
As explained in the post above, this will open lots of possibilities to enforce policies to all kinds of places. One out of many I wanted to try was in the Front-end world, especially with a SPA like Vue.js. This post describes how I integrated OPA WebAssembly with Vue.js.
TL;DR
If you want to jump right to a working example, here's the repo for the Proof of Concept.
kenfdev / vue-opa-wasm
PoC OPA WASM integration with Vue.js
Prerequisites
- Basic knowledge about Open Policy Agent and the Rego Language
- Basic knowledge about Vue.js
Overview
What I did for this PoC is listed below:
- Create a Rego file and declare rules
- Create a
wasm
file from Rego files with the OPA CLI - Prepare Vue.js to use
wasm
files - Create and inject a
policy
instance inside Vue.js - Use the
policy
instance inside Vue.js templates
Let's dig into details.
Details
For simplicity, I've followed the nodejs-app example to create the wasm but I'm going to briefly explain about it anyway.
Create a Rego file and declare rules
The Rego for this example is extremely simple. The following is the code:
# example.rego
package example
default hello = false
hello {
x := input.message
x == data.world
}
It means the hello
rule will be true
if "The message
key's value
of the input
equals the value of data.world
".
Create a wasm
file from Rego files with the OPA CLI
After the Rego file has been created, the Wasm needs to be built using the OPA CLI. Compiling Rego files to Wasm is added in v0.15.1 of the OPA CLI so you need to download the version newer than that:
https://github.com/open-policy-agent/opa/releases
The actual command to build the Wasm file is as follows:
opa build -d example.rego 'data.example = x'
One thing to note is that, as explained in the docs, you need to specify a query
to be used for the Wasm at compile time. In this example, I have queried the entire data.example
package.
If the compile succeeds, you will see a policy.wasm
file in the path you executed the command.
Preparing the Wasm to be used in the front-end completes here. Next, we'll dive into how this can be integrated in Vue.js.
Prepare Vue.js to use wasm
files
At the moment, OPA provides a light-weight sdk to use Wasm wih JavaScript called @open-policy-agent/opa-wasm. In this library, you can pass a Wasm ArrayBuffer
to load Rego policies to be used by JavaScript.
For simplicity, I decided to load the Wasm at build time of the Vue.js app, and have chosen to use the arraybuffer-loader to load the wasm with the import
syntax via webpack.
To add a loader for the Vue.js app, you'll need to create and modify a vue.config.js
file as follows:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('arraybuffer')
.type('javascript/auto')
.test(/\.wasm$/)
.use('arraybuffer-loader')
.loader('arraybuffer-loader');
},
};
With this configuration, you can load the wasm like this in your JavaScript files:
import wasm from './assets/policy.wasm'
Note: Be sure to add the
type('javascript/auto')
line or else loading the wasm file will fail. This issue helped me solve it.
Create and inject a policy
instance inside Vue.js
Now that we have everything prepared, let's load the Wasm before we instantiate the Vue application.
// ...
import Rego from '@open-policy-agent/opa-wasm';
import wasm from './assets/policy.wasm';
// ...
const rego = new Rego();
rego.load_policy(wasm).then(policy => {
// add the policy instance to the Vue.prototype
Vue.prototype.$policy = policy;
new Vue({
router,
render: h => h(App),
}).$mount('#app');
});
Fortunately, the opa-wasm
SDK let's us easily load the Wasm as a policy instance by calling the rego.load_policy
method. The promise returns a policy
instance so I've added this instance to the Vue.prototype
in order for it to be used in the Vue templates.
Use the policy
instance inside Vue.js templates
Here's an example on how we can use the $policy
instance inside the Vue templates.
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<pre>{{ $policy.evaluate({ message: "world" }) }}</pre>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
created() {
// set the data.world to "world"
this.$policy.set_data({ world: "world" });
}
};
</script>
I've set the data.world
to "world"
at the created
life cycle of the component. And in the template itself, I'm calling $policy.evaluate
with input.message
set to "world"
. Since both data.world
and input.world
have the same value "world"
, the hello
rule evaluates to true
. You can check it out in the following screen capture.
Wrap up
In this post, I've shown you how to build an OPA Wasm from a Rego file and use it inside a Vue application. I used Vue.js in this post but anything JavaScript should be able to use the policy
instance in the same way.
Being able to use the policy
instance in the front-end is going to be extremely powerful. I can enforce policies nearly the same way in the front-end as I would in the back-end using the OPA server.
The application above is just a PoC to show you that using Rego policies in the front-end is possible. I can think of few things to improve at the moment such as:
Dynamically fetch wasm
I've imported the Wasm using the import
statement but this won't scale. Perhaps one would like to prepare a Wasm per logged in user. It would probably be better to fetch the Wasm dynamically on the fly, and then load and convert it to a policy
instance to be used.
Built-ins
At the time of writing, built-ins cannot be used in the Rego files unless you implement them by yourself. built-ins are extremely powerful and makes your Rego files more readable and performant. It would be nice to implement these in JavaScript to be used.
Stay tuned!
The OPA Wasm is still in its early stages but is actively being developed. Let's keep an eye on the official docs and the Slack community!
Top comments (1)
Nice example, a little deprecated since a lot of stuff has changed. I could not manage to use imports so resorted to using fetch.
Maybe update the setup a bit, like fixing cli commands and such and add a workaround with fetch or so.