Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add webassembly component model #236

Merged
merged 1 commit into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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