Compare commits

...

4 Commits

Author SHA1 Message Date
zach
5ff515d3a0 feat: allow readonly paths in manifest 2024-02-26 16:10:33 -08:00
zach
10ee81952b chore: update to latest wasmtime 2024-02-26 16:06:10 -08:00
zach
fb3c131a1e fix: require wasmtime 17 or greater 2024-02-26 16:06:10 -08:00
zach
9e586a36d9 feat: use wasi preview2, support wasi tcp/udp for allowed_hosts 2024-02-26 16:06:09 -08:00
6 changed files with 84 additions and 34 deletions

View File

@@ -249,7 +249,13 @@ pub struct Manifest {
#[serde(default)]
pub allowed_paths: Option<BTreeMap<PathBuf, PathBuf>>,
/// The plugin timeout, by default this is set to 30s
/// Specifies which paths should be made for reading only when using WASI. This is a mapping from
/// the path on disk to the path it should be available inside the plugin.
/// For example, `".": "/tmp"` would mount the current directory as `/tmp` inside the module
#[serde(default)]
pub allowed_paths_readonly: Option<BTreeMap<PathBuf, PathBuf>>,
/// The plugin timeout
#[serde(default)]
pub timeout_ms: Option<u64>,
}

View File

@@ -9,8 +9,8 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = ">= 14.0.0, < 18.0.0"
wasmtime-wasi = ">= 14.0.0, < 18.0.0"
wasmtime = ">= 18.0.0, < 19.0.0"
wasmtime-wasi = ">= 18.0.0, < 19.0.0"
anyhow = "1"
serde = {version = "1", features = ["derive"]}
serde_json = "1"

View File

@@ -1,3 +1,6 @@
use wasmtime::component::ResourceTable;
use wasmtime_wasi::preview2::{preview1::WasiPreview1Adapter, DirPerms, FilePerms};
use crate::*;
/// CurrentPlugin stores data that is available to the caller in PDK functions, this should
@@ -18,6 +21,7 @@ pub struct CurrentPlugin {
}
unsafe impl Send for CurrentPlugin {}
unsafe impl Sync for CurrentPlugin {}
pub(crate) struct MemoryLimiter {
bytes_left: usize,
@@ -289,24 +293,62 @@ impl CurrentPlugin {
) -> Result<Self, Error> {
let wasi = if wasi {
let auth = wasmtime_wasi::ambient_authority();
let mut ctx = wasmtime_wasi::WasiCtxBuilder::new();
for (k, v) in manifest.config.iter() {
ctx.env(k, v)?;
}
let mut ctx = wasmtime_wasi::preview2::WasiCtxBuilder::new();
ctx.allow_ip_name_lookup(true);
ctx.allow_tcp(true);
ctx.allow_udp(true);
if let Some(a) = &manifest.allowed_paths {
for (k, v) in a.iter() {
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth)?;
ctx.preopened_dir(d, v)?;
if k.as_path().is_dir() {
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth)?;
ctx.preopened_dir(
d,
DirPerms::READ | DirPerms::MUTATE,
FilePerms::READ | FilePerms::WRITE,
v.to_string_lossy(),
);
}
}
}
if let Some(a) = &manifest.allowed_paths_readonly {
for (k, v) in a.iter() {
if k.as_path().is_dir() {
let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth)?;
ctx.preopened_dir(d, DirPerms::READ, FilePerms::READ, v.to_string_lossy());
}
}
}
if let Some(h) = &manifest.allowed_hosts {
let h = h.clone();
ctx.socket_addr_check(move |addr, _kind| {
for host in h.iter() {
let addrs = std::net::ToSocketAddrs::to_socket_addrs(&host);
if let Ok(addrs) = addrs {
for a in addrs.into_iter() {
if addr == &a {
return true;
}
}
}
}
false
});
}
// Enable WASI output, typically used for debugging purposes
if std::env::var("EXTISM_ENABLE_WASI_OUTPUT").is_ok() {
ctx.inherit_stdout().inherit_stderr();
}
Some(Wasi { ctx: ctx.build() })
Some(Wasi {
ctx: ctx.build(),
preview2_table: ResourceTable::new(),
preview1_adapter: WasiPreview1Adapter::new(),
})
} else {
None
};

View File

@@ -3,7 +3,29 @@ use crate::*;
/// WASI context
pub struct Wasi {
/// wasi
pub ctx: wasmtime_wasi::WasiCtx,
pub ctx: wasmtime_wasi::preview2::WasiCtx,
pub preview2_table: wasmtime::component::ResourceTable,
pub preview1_adapter: wasmtime_wasi::preview2::preview1::WasiPreview1Adapter,
}
impl wasmtime_wasi::preview2::WasiView for CurrentPlugin {
fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
&mut self.wasi.as_mut().unwrap().preview2_table
}
fn ctx(&mut self) -> &mut wasmtime_wasi::preview2::WasiCtx {
&mut self.wasi.as_mut().unwrap().ctx
}
}
impl wasmtime_wasi::preview2::preview1::WasiPreview1View for CurrentPlugin {
fn adapter(&self) -> &wasmtime_wasi::preview2::preview1::WasiPreview1Adapter {
&self.wasi.as_ref().unwrap().preview1_adapter
}
fn adapter_mut(&mut self) -> &mut wasmtime_wasi::preview2::preview1::WasiPreview1Adapter {
&mut self.wasi.as_mut().unwrap().preview1_adapter
}
}
/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values

View File

@@ -307,9 +307,7 @@ impl Plugin {
// If wasi is enabled then add it to the linker
if with_wasi {
wasmtime_wasi::add_to_linker(&mut linker, |x: &mut CurrentPlugin| {
&mut x.wasi.as_mut().unwrap().ctx
})?;
wasmtime_wasi::preview2::preview1::add_to_linker_sync(&mut linker)?;
}
for f in &mut imports {
@@ -805,12 +803,8 @@ impl Plugin {
}
let wasi_exit_code = e
.downcast_ref::<wasmtime_wasi::I32Exit>()
.map(|e| e.0)
.or_else(|| {
e.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0)
});
.downcast_ref::<wasmtime_wasi::preview2::I32Exit>()
.map(|e| e.0);
if let Some(exit_code) = wasi_exit_code {
debug!(
plugin = self.id.to_string(),

View File

@@ -389,20 +389,6 @@ pub unsafe extern "C" fn extism_plugin_config(
}
};
let wasi = &mut plugin.current_plugin_mut().wasi;
if let Some(Wasi { ctx, .. }) = wasi {
for (k, v) in json.iter() {
match v {
Some(v) => {
let _ = ctx.push_env(k, v);
}
None => {
let _ = ctx.push_env(k, "");
}
}
}
}
let id = plugin.id;
let config = &mut plugin.current_plugin_mut().manifest.config;
for (k, v) in json.into_iter() {