Skip to content

burdzwastaken/zig-opa-wasm

Repository files navigation

zig-opa-wasm

Zig Release CI

A Zig library to use OPA policies compiled to WASM.

Installation

Add to your build.zig.zon:

.dependencies = .{
    .zig_opa_wasm = .{
        .url = "git+https://github.com/burdzwastaken/zig-opa-wasm#v0.0.7",
        .hash = "...",
    },
},

Then in build.zig:

const opa = b.dependency("zig_opa_wasm", .{
    .target = target,
    .optimize = optimize,
});
exe.root_module.addImport("opa", opa.module("opa"));

Build

# build with Wasmer (default)
zig build

# build with zware (pure Zig)
zig build -Dbackend=zware

# build for wasm32-freestanding
zig build -Dbackend=freestanding

CLI

# evaluate a policy
zig-out/bin/opa-zig eval -m policy.wasm -e "example/allow" -i '{"user":"admin"}'

# evaluate with data
zig-out/bin/opa-zig eval -m policy.wasm -e "authz/allow" -d '{"roles":["admin"]}' -i '{"user":"alice"}'

# load from OPA bundle
zig-out/bin/opa-zig eval -m bundle.tar.gz -e "main/decision" -i '{"action":"read"}'

# inspect a WASM module
zig-out/bin/opa-zig info policy.wasm

# benchmark evaluation
zig-out/bin/opa-zig bench -m policy.wasm -e "example/allow" -i '{}' -n 10000

API

Basic Evaluation

const opa = @import("opa");

var wasm_backend = try opa.WasmerBackend.init(allocator);
defer wasm_backend.deinit();

var be = wasm_backend.asBackend();
var policy = try opa.Policy.load(allocator, &be, wasm_bytes);
defer policy.deinit();

var instance = try opa.Instance.create(allocator, &policy);
defer instance.deinit();

const result = try instance.evaluate("example/allow", "{\"user\":\"admin\"}");
defer allocator.free(result);

With Data Document

var instance = try opa.Instance.create(allocator, &policy);
defer instance.deinit();

try instance.setData("{\"roles\":{\"admin\":[\"read\",\"write\"]}}");

const result = try instance.evaluate("authz/allow", "{\"user\":\"alice\",\"role\":\"admin\"}");
defer allocator.free(result);

Typed Evaluation

// OPA returns results as [{"result": <value>}]
const OpaResult = struct { result: bool };

const results = try instance.evaluateTyped([]OpaResult, "authz/allow", .{ .user = "admin" });
if (results.len > 0 and results[0].result) {
    // allowed
}

Instance Pooling

var pool = try opa.InstancePool.init(allocator, &policy, 4);
defer pool.deinit();

const inst = try pool.acquire();
defer pool.release(inst);

const result = try inst.evaluate("example/allow", "{}");
defer allocator.free(result);

Bundle Loading

var bundle = try opa.Bundle.fromFile(allocator, "policy.tar.gz");
defer bundle.deinit();

var policy = try opa.Policy.load(allocator, &be, bundle.wasm);
defer policy.deinit();

Policy Validation

var policy = try opa.Policy.load(allocator, &be, wasm_bytes);
defer policy.deinit();

var result = try policy.validate(allocator);
defer result.deinit(allocator);

if (!result.valid) {
    for (result.unsupported_builtins) |name| {
        std.debug.print("unsupported builtin: {s}\n", .{name});
    }
}

Debug Logging

var wasm_backend = try opa.WasmerBackend.init(allocator);
defer wasm_backend.deinit();

wasm_backend.setLogCallback(struct {
    fn log(level: opa.LogLevel, msg: []const u8) void {
        std.debug.print("[{s}] {s}\n", .{ @tagName(level), msg });
    }
}.log, .debug);

Architecture

Pluggable Backend

The library uses an abstract backend interface allowing different WASM runtimes to be swapped without changing application code.

Backends:

Backend Runtime Use Case
WasmerBackend Wasmer Native builds, JIT compilation, best performance
ZwareBackend zware Pure Zig interpreter, no C dependencies
FreestandingBackend zware wasm32-freestanding target for browser/JS embedding

Select backend at build time with -Dbackend=wasmer (default), -Dbackend=zware, or -Dbackend=freestanding. The freestanding backend compiles zig-opa-wasm itself to WebAssembly.

Supported Builtins

100+ builtins implemented depending on the backend.

License

MIT

About

OPA policy evaluation for Zig with pluggable WASM backends

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors