What is WebAssembly (Wasm)?
OPA is able to compile Rego policies into executable Wasm modules that can be evaluated with different inputs and external data. This is not running the OPA server in Wasm, nor is this just cross-compiled Golang code. The compiled Wasm module is a planned evaluation path for the source policy and query.
The core language is supported fully but there are a number of built-in functions that are not, and probably won’t be natively supported in Wasm (e.g., ). Built-in functions that are not natively supported can be implemented in the host environment (e.g., JavaScript).
You can compile Rego policies into Wasm modules using the opa build
subcommand.
For example, the opa build
command below compiles the example.rego
file into a Wasm module and packages it into an OPA bundle. The wasm
target requires at least one entrypoint rule (specified by -e
).
The output of a Wasm module built this way contain the result
of evaluating the entrypoint rule. For example:
[
"result": <value of data.example.allow>
}
]
The output of policy evaluation is a set of variable assignments. The variable assignments specify values that satisfy the expressions in the policy query (i.e., if the variables in the query are replaced with the values from the assignments, all of the expressions in the query would be defined and not false.)
When policies are compiled into Wasm, the user provides the path of the policy decision that should be exposed by the Wasm module. The policy decision is assigned to a variable named result
. The policy decision can be ANY JSON value (boolean, string, object, etc.) but there will be at-most-one assignment. This means that callers should first check if the set of variable assignments is empty (indicating an undefined policy decision) otherwise they should select the "result"
key out of the variable assignment set.
For more information on
opa build
runopa build --help
.
You can also compile Rego policies into Wasm modules from Go using the lower-level rego API that produces raw Wasm executables and the higher-level API that produces OPA bundle files. The compile API is recommended.
There is a JavaScript SDK available that simplifies the process of loading and evaluating compiled policies. If you want to evaluate Rego policies inside JavaScript we recommend you use the JavaScript SDK.
See https://github.com/open-policy-agent/npm-opa-wasm for more details.
There is an example NodeJS application located .
Instantiating the Wasm Module
Before you can evaluate Wasm compiled policies you need to instantiate the Wasm module produced by the compilation process described earlier on this page.
To load the compiled Wasm module refer the documentation for the Wasm runtime that you are using. At a high-level you must provide a memory buffer and a set of import functions. The memory buffer is a contiguous, mutable byte-array that allows you to pass data to the policy and receive output from the policy. The import functions are dependencies of the compiled policies.
ABI Versions
Wasm modules built using OPA 0.27.0 onwards contain a global variable named opa_wasm_abi_version
that has a constant i32 value indicating the ABI version this module requires. Described below you find ABI version 1.
There’s another i32 constant exported, opa_wasm_abi_minor_version
, used to track backwards-compatible changes.
Using tools like wasm-objdump
(wasm-objdump -x policy.wasm
), the ABI version can be found here:
Note the i32=1
of global[1]
, exported by the name of opa_wasm_abi_version
.
Exports
The primary exported functions for interacting with policy modules are:
The addresses passed and returned by the policy modules are 32-bit integer offsets into the shared memory region. The value_addr
parameters and return values refer to OPA value data structures: null
, boolean
, number
, string
, array
, object
, and set
.
Error codes:
OPA WASM Error codes are int32 values defined as:
Imports
Policy modules require the following function imports at instantiation-time:
The policy module also requires a shared memory buffer named env.memory
.
Memory Buffer
A shared memory buffer must be provided as an import for the policy module with the name env.memory
. The buffer must be large enough to accommodate the input, provided data, and result of evaluation.
Built-in Functions
For example:
const memory = new WebAssembly.Memory({ initial: 5 });
const policy_module = await WebAssembly.instantiate(byte_buffer, /* import object */);
const addr = policy_module.instance.exports.builtins();
const str_addr = policy_module.instance.exports.opa_json_dump(addr);
const builtin_map = deserialize_null_terminated_JSON_string(memory, str_addr);
The built-in function mapping will contain all of the built-in functions that may be required during evaluation. For example, the following query refers to the http.send
built-in function which is not included in the policy module:
If this query was compiled to Wasm the built-in map would contain a single element:
{
}
When the evaluation runs, the opa_builtin1
callback would invoked with builtin_id
set to 0
.
Evaluation
Once instantiated, the policy module is ready to be evaluated. Use the opa_eval_ctx_new
exported function to create an evaluation context. Use the opa_eval_ctx_set_input
and opa_eval_ctx_set_data
exported functions to specify the values of the input
and base data
documents to use during evaluation.
To evaluate, call to the exported eval
function with the eval context address as the only parameter.
Input
The (optional) input
document for a policy can be provided by loading a JSON string into the shared memory buffer. Use the opa_malloc
exported function to allocate a buffer the size of the JSON string and copy the contents in at the returned address. After the raw string is loaded into memory you will need to call the opa_json_parse
exported method to get an address to the parsed input document for use in evaluations. Set the address via the opa_eval_ctx_set_input
exported functoin supplying the evaluation context address and parsed input document address.
External Data
External data can be loaded for use in evaluation. Similar to the input
this is done by loading a JSON string into the shared memory buffer. Use opa_malloc
and opa_json_parse
followed by opa_eval_ctx_set_data
to set the address on the evaluation context.
Data can be updated by using the opa_value_add_path
and opa_value_remove_path
and providing the same value address as the base. Similarly, use opa_malloc
and opa_json_parse
for the updated value and creating the path.
After loading the external data use the opa_heap_ptr_get
exported method to save the current point in the heap before evaluation. After evaluation this should be reset by calling opa_heap_ptr_set
to ensure that evaluation restarts back at the saved data and re-uses heap space. This is particularly important if re-evaluating many times with the same data.
Entrypoints
The compiled policy may have one or more entrypoints. If no entrypoint is set on the evaluation context the default entrypoint (0
) will be evaluated. SDKs can call entrypoints()
after instantiating the module to retrieve the entrypoint name to entrypoint identifier mapping. SDKs can set the entrypoint to evaluate by calling opa_eval_ctx_set_entrypoint
on the evaluation context. If an invalid entrypoint identifier is passed, the eval
function will invoke opa_abort
.
Results
After evaluation results can be retrieved via the exported opa_eval_ctx_get_result
function. Pass in the evaluation context address. The return value is an address in the shared memory buffer to the structured result. To access the JSON result use the opa_json_dump
exported function to retrieve a pointer in shared memory to a null terminated JSON string.
The result of evaluation is the set variable bindings that satisfy the expressions in the query. For example, the query x = 1; y = 2; y > x
would produce the following result set: