Skip to content

Commit

Permalink
feat: add webassembly component model
Browse files Browse the repository at this point in the history
This commit adds a webassembly component module for the policy engine.

The motivation for this is to be able to run the policy engine in any
webassembly runtime that supports the new WebAssembly Component Model.

This commit include examples of running the policy engine in JavaScript,
Python, and Rust.

Closes: #227
Signed-off-by: Daniel Bevenius <[email protected]>
  • Loading branch information
danbev committed Jul 4, 2023
1 parent 238e4dc commit 9916c59
Show file tree
Hide file tree
Showing 31 changed files with 4,632 additions and 471 deletions.
1,072 changes: 607 additions & 465 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ nav:

- modules/policies/nav.adoc

- modules/component-model/nav.adoc

- modules/examples/nav.adoc

- modules/faq/nav.adoc
1 change: 1 addition & 0 deletions docs/modules/component-model/nav.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
** xref:component_model.adoc[`Component Model`]
79 changes: 79 additions & 0 deletions docs/modules/component-model/pages/component_model.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
= WebAssembly Component Model

This is a work in progress with the goal being to provide a WebAssembly
component for the policy engine.

This example uses wit-bindgen macros to generate the Rust types, but this can
also be done manually using the wit-bindgen-cli. First we need to install
wit-bindgen-cli:
[listing]
$ cargo install --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-cli

== WebAssembly Interface Types
The main interface is defined in link:https://github.com/seedwing-io/seedwing-policy/tree/main/engine/wit/engine-world.wit[wit/engine-world.wit]
which contains on the exposed functions. The data types are defined in
link:https://github.com/seedwing-io/seedwing-policy/tree/main/engine/wit/engine-types.wit[wit/engine-types.wit].

== Building
To build the WebAssembly component:
[listing]
$ make wit-compile
cargo b -p seedwing-policy-engine --target=wasm32-wasi --no-default-features --features=""

Note that `wit` stands for `WebAssembly Interface Types`.

The above compilation will generate a core WebAssembly module which can be found
in the `target` directory in the root of the checked out github repository.

The next step is to create the WebAssembly component using this core WebAssembly
module:
[listing]
$ make wit-component
wasm-tools component new -v ../target/wasm32-wasi/debug/seedwing_policy_engine.wasm --adapt wasi_snapshot_preview1.wasm -o seedwing_policy-engine-component.wasm

The above will build an optimized release build which is needed or the execution
of the Rust example will be very slow. But is can be nice to build an
unopptimized version:
[listing]
$ make wit-compile Build=debug

Just make sure to also use `Build=debug` for the Rust targets or it will default
to a release build and you might get an runtime error depending on what changes
that have been made to the source code.

== JavaScript example
The directory link:https://github.com/seedwing-io/seedwing-policy/tree/main/engine/wit-examples/javascript/README.md[javascript]
contains a JavaScript example of using the webassembly component. There are more
details in the readme, but the example can be run directly using:
[listing]
$ make wit-java-bindings
$ make wit-java-run

== Python example
The directory link:https://github.com/seedwing-io/seedwing-policy/tree/main/engine/wit-examples/python/README.md[python] contains a Python example of using
the webassembly component. There are more details in the readme, but the example
can be run directly
using:
[listing]
$ make wit-python-bindings
$ make wit-python-run

== Rust example
The directory link:https://github.com/seedwing-io/seedwing-policy/tree/main/engine/wit-examples/rust/README.md[rust] contains
a Rust example of using the webassembly component. There are more details in the
readme, but the example can be run directly
using:
[listing]
$ make wit-rust-bindings
$ make wit-rust-run

This example uses wit-bindgen macros so the bindings step is not required here
which is done for the other examples.

== Go example
There is project named https://github.com/bytecodealliance/wasmtime-go[wasmtime-go]
which looked like it would be able to to do the same/simlar thing as the other
examples. But it turns out that it does not support the WebAssembly Component
Model yet as mentioned in
https://github.com/bytecodealliance/wasmtime-go/issues/170[issue-170].

2 changes: 2 additions & 0 deletions engine/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.wasm32-wasi]
rustflags = ["--cfg=tokio_unstable"]
1 change: 1 addition & 0 deletions engine/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine.rs
11 changes: 9 additions & 2 deletions engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@ cidr = "0.2.1"
http = "0.2.8"
iref = "2.2.3"
chrono = { version = "0.4.23", features = ["serde"] }
reqwest = { version = "0.11.14", features = ["json"] }
uuid = { version = "1.3.0", features = ["v4"] }
openvex = "0.1.0"
spdx = "0.10.0"
csaf = { version = "0.4.0", default-features = false }
guac-rs = { git = "https://github.com/dejanb/guac-rs.git", rev = "67a70e98ffaa17200d256e1afc5c88756b56d5e2" }
semver = "1.0"


Expand All @@ -59,6 +57,12 @@ prometheus = { version = "0.13.3", optional = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
home = "0.5.4"
reqwest = { version = "0.11.14", features = ["json"] }
guac-rs = { git = "https://github.com/dejanb/guac-rs.git", rev = "67a70e98ffaa17200d256e1afc5c88756b56d5e2" }

[target.'cfg(all(target_arch = "wasm32", target_os = "wasi"))'.dependencies]
wit-bindgen = { version = "0.8.0", default-features = true, features = ['macros'] }
futures = "0.3"

[features]
default = ["sigstore", "monitor", "showcase", "intoto"]
Expand All @@ -78,3 +82,6 @@ env_logger = { version = "0.10.0", default-features = false }
[[bench]]
name = "engine"
harness = false

[lib]
crate-type = ["cdylib", "rlib"]
76 changes: 76 additions & 0 deletions engine/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@

ifeq ($(Build), debug)
BUILD_TYPE = debug
else
BUILD_TYPE = release
BUILD = "--$(BUILD_TYPE)"
endif

corewasm=../target/wasm32-wasi/${BUILD_TYPE}/seedwing_policy_engine.wasm
component=../target/seedwing-policy-engine-component.wasm


# Seedwing WebAssembly Component targets
wit-compile:
cargo b ${BUILD} -p seedwing-policy-engine --target=wasm32-wasi --no-default-features --features=""

# Can be used to inspect the custom section "component-type::engine-world"
objdump-core-module:
@wasm-tools objdump $(corewasm)

test_from_engine_runtime:
cargo t -p seedwing-policy-engine --target=wasm32-wasi \
--no-default-features --features="" \
--lib core::wit::test::from_engine_runtime -- --exact --show-output

wit-component:
wasm-tools component new \
-v $(corewasm) \
--adapt wasi_snapshot_preview1=wit-lib/wasi_preview1_component_adapter.wasm \
-o $(component)
@wasm-tools strip $(component) -o $(component)

wit-print-wat:
wasm-tools print $(component)

wit-print:
@wasm-tools component wit $(component)

objdump-component:
@wasm-tools objdump $(component)

# Can be used to generate the Rust source for that the bindgen::generate
# macro will produce.
wit-bindgen:
wit-bindgen rust wit/

# Can be useful to inspect the expanded wit-bindgen macros
cargo-expand:
cargo expand -p seedwing-policy-engine --target=wasm32-wasi --no-default-features --features=""

# JavaScript targets
wit-javascript-bindings:
cd wit-examples/javascript && npm run bindings

wit-javascript-run:
cd wit-examples/javascript && npm start

wit-javascript-all: wit-compile wit-component wit-js-build

# Python targets
wit-python-bindings:
@cd wit-examples/python && make --no-print-directory bindings

wit-python-run:
@cd wit-examples/python && make --no-print-directory run

wit-python-all: wit-compile wit-component wit-python-bindings wit-python-run

# Rust targets
wit-rust-bindings:
cd wit-examples/rust && cargo b ${BUILD}

wit-rust-run:
cd wit-examples/rust && cargo r ${BUILD}

wit-rust-all: wit-rust-run
5 changes: 5 additions & 0 deletions engine/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ pub mod cyclonedx;
pub mod data;
#[cfg(feature = "debug")]
pub mod debug;
#[cfg(not(target_arch = "wasm32"))]
pub mod external;
#[cfg(not(target_arch = "wasm32"))]
pub mod guac;
#[cfg(feature = "intoto")]
pub mod intoto;
Expand All @@ -31,9 +33,12 @@ pub mod lang;
pub mod list;
pub mod maven;
pub mod net;
#[cfg(not(target_arch = "wasm32"))]
pub mod openvex;
#[cfg(not(target_arch = "wasm32"))]
pub mod osv;
pub mod pem;
#[cfg(not(target_arch = "wasm32"))]
pub mod rhsa;
pub mod semver;
#[cfg(feature = "showcase")]
Expand Down
32 changes: 32 additions & 0 deletions engine/src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//! A data source is a way to provide mostly static data available to the engine to use during evaluation.
use crate::runtime::RuntimeError;
use crate::value::RuntimeValue;
use std::collections::HashMap;

use std::fmt::Debug;
use std::fs::File;
Expand All @@ -15,6 +16,37 @@ pub trait DataSource: Send + Sync + Debug {
fn get(&self, path: &str) -> Result<Option<RuntimeValue>, RuntimeError>;
}

#[derive(Debug)]
pub enum MemDataSourceType {
String(String),
Bytes(Vec<u8>),
}

/// A source of data read from a strings.
///
#[derive(Debug)]
pub struct MemDataSource {
map: HashMap<String, MemDataSourceType>,
}

impl MemDataSource {
pub fn new(map: HashMap<String, MemDataSourceType>) -> Self {
Self { map }
}
}

impl DataSource for MemDataSource {
fn get(&self, path: &str) -> Result<Option<RuntimeValue>, RuntimeError> {
match self.map.get(path) {
Some(dst) => match dst {
MemDataSourceType::String(string) => Ok(Some(string.as_str().into())),
MemDataSourceType::Bytes(bytes) => Ok(Some(RuntimeValue::Octets(bytes.clone()))),
},
None => Err(RuntimeError::NoSuchPath(path.to_string())),
}
}
}

/// A source of data read from a directory.
///
/// The path parameter is used to locate the source file within the root directory.
Expand Down
5 changes: 5 additions & 0 deletions engine/src/lang/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,11 +387,14 @@ impl World {
world.add_package(crate::core::kafka::package());
world.add_package(crate::core::pem::package());
world.add_package(crate::core::net::package());
#[cfg(not(target_arch = "wasm32"))]
world.add_package(crate::core::openvex::package());
#[cfg(not(target_arch = "wasm32"))]
world.add_package(crate::core::osv::package());
world.add_package(crate::core::uri::package());
world.add_package(crate::core::timestamp::package());
world.add_package(crate::core::csaf::package());
#[cfg(not(target_arch = "wasm32"))]
world.add_package(crate::core::rhsa::package());
world.add_package(crate::core::slsa::package());
#[cfg(feature = "intoto")]
Expand All @@ -401,7 +404,9 @@ impl World {
world.add_package(crate::core::debug::package());

world.add_package(crate::core::maven::package());
#[cfg(not(target_arch = "wasm32"))]
world.add_package(crate::core::external::package());
#[cfg(not(target_arch = "wasm32"))]
world.add_package(crate::core::guac::package());
world.add_package(crate::core::semver::package());

Expand Down
3 changes: 3 additions & 0 deletions engine/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#![doc = include_str!("../../README.md")]
//#![deny(warnings)]
//#![warn(missing_docs)]
#[cfg(all(target_arch = "wasm32", target_os = "wasi"))]
mod wit;

#[cfg(not(target_arch = "wasm32"))]
pub mod client;
mod core;
pub mod data;
Expand Down
3 changes: 3 additions & 0 deletions engine/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,12 @@ pub enum RuntimeError {
#[error("error reading file: {0}")]
FileUnreadable(PathBuf),
#[error("remote client failed: {0}")]
#[cfg(not(target_arch = "wasm32"))]
RemoteClient(#[from] crate::client::Error),
#[error("recursion limit reached: {0}")]
RecursionLimit(usize),
#[error("no such path: {0}")]
NoSuchPath(String),
}

#[derive(Clone, Debug)]
Expand Down
Loading

0 comments on commit 9916c59

Please sign in to comment.