Skip to content

Commit 3268e6c

Browse files
committed
factors: Add configure_app hook
Signed-off-by: Lann Martin <[email protected]>
1 parent d28f96c commit 3268e6c

File tree

11 files changed

+393
-44
lines changed

11 files changed

+393
-44
lines changed

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/app/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,14 @@ impl<'a, L> AppComponent<'a, L> {
296296
&self.locked.source
297297
}
298298

299+
/// Returns an iterator of environment variable (key, value) pairs.
300+
pub fn environment(&self) -> impl IntoIterator<Item = (&str, &str)> {
301+
self.locked
302+
.env
303+
.iter()
304+
.map(|(k, v)| (k.as_str(), v.as_str()))
305+
}
306+
299307
/// Returns an iterator of [`ContentPath`]s for this component's configured
300308
/// "directory mounts".
301309
pub fn files(&self) -> std::slice::Iter<ContentPath> {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "spin-factor-outbound-networking"
3+
version = { workspace = true }
4+
authors = { workspace = true }
5+
edition = { workspace = true }
6+
7+
[dependencies]
8+
anyhow = "1"
9+
ipnet = "2.9.0"
10+
spin-factor-wasi = { path = "../factor-wasi" }
11+
spin-factors = { path = "../factors" }
12+
# TODO: merge with this crate
13+
spin-outbound-networking = { path = "../outbound-networking" }
14+
15+
[lints]
16+
workspace = true
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::{collections::HashMap, sync::Arc};
2+
3+
use anyhow::Context;
4+
use spin_factor_wasi::WasiFactor;
5+
use spin_factors::{Factor, FactorInstancePreparer, Result, SpinFactors};
6+
use spin_outbound_networking::{AllowedHostsConfig, HostConfig, PortConfig, ALLOWED_HOSTS_KEY};
7+
8+
pub struct OutboundNetworkingFactor;
9+
10+
impl Factor for OutboundNetworkingFactor {
11+
type AppConfig = AppConfig;
12+
type InstancePreparer = InstancePreparer;
13+
type InstanceState = ();
14+
15+
fn configure_app<Factors: SpinFactors>(
16+
&self,
17+
app: &spin_factors::App,
18+
_ctx: spin_factors::ConfigureAppContext<Factors>,
19+
) -> Result<Self::AppConfig> {
20+
let mut cfg = AppConfig::default();
21+
// TODO: resolve resolver resolution
22+
let resolver = Default::default();
23+
for component in app.components() {
24+
if let Some(hosts) = component.get_metadata(ALLOWED_HOSTS_KEY)? {
25+
let allowed_hosts = AllowedHostsConfig::parse(&hosts, &resolver)?;
26+
cfg.component_allowed_hosts
27+
.insert(component.id().to_string(), Arc::new(allowed_hosts));
28+
}
29+
}
30+
Ok(cfg)
31+
}
32+
}
33+
34+
#[derive(Default)]
35+
pub struct AppConfig {
36+
component_allowed_hosts: HashMap<String, Arc<AllowedHostsConfig>>,
37+
}
38+
39+
pub struct InstancePreparer {
40+
allowed_hosts: Arc<AllowedHostsConfig>,
41+
}
42+
43+
impl InstancePreparer {
44+
pub fn allowed_hosts(&self) -> &Arc<AllowedHostsConfig> {
45+
&self.allowed_hosts
46+
}
47+
}
48+
49+
impl FactorInstancePreparer<OutboundNetworkingFactor> for InstancePreparer {
50+
fn new<Factors: SpinFactors>(
51+
_factor: &OutboundNetworkingFactor,
52+
app_component: &spin_factors::AppComponent,
53+
mut ctx: spin_factors::PrepareContext<Factors>,
54+
) -> Result<Self> {
55+
let allowed_hosts = ctx
56+
.app_config::<OutboundNetworkingFactor>()?
57+
.component_allowed_hosts
58+
.get(app_component.id())
59+
.context("missing component")?
60+
.clone();
61+
62+
// Update Wasi socket allowed ports
63+
let wasi_preparer = ctx.instance_preparer_mut::<WasiFactor>()?;
64+
match &*allowed_hosts {
65+
AllowedHostsConfig::All => wasi_preparer.inherit_network(),
66+
AllowedHostsConfig::SpecificHosts(configs) => {
67+
for config in configs {
68+
if config.scheme().allows_any() {
69+
match (config.host(), config.port()) {
70+
(HostConfig::Cidr(ip_net), PortConfig::Any) => {
71+
wasi_preparer.socket_allow_ports(*ip_net, 0, None)
72+
}
73+
_ => todo!(), // TODO: complete and validate against existing Network TriggerHooks
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
Ok(Self { allowed_hosts })
81+
}
82+
83+
fn prepare(self) -> Result<<OutboundNetworkingFactor as Factor>::InstanceState> {
84+
Ok(())
85+
}
86+
}

crates/factor-wasi/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ authors = { workspace = true }
55
edition = { workspace = true }
66

77
[dependencies]
8-
anyhow = "1.0"
8+
anyhow = "1"
9+
cap-primitives = "3.0.0"
910
spin-factors = { path = "../factors" }
1011
wasmtime-wasi = { workspace = true }
1112

crates/factor-wasi/src/lib.rs

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
pub mod preview1;
22

3+
use std::path::Path;
4+
5+
use anyhow::ensure;
6+
use cap_primitives::{ipnet::IpNet, net::Pool};
37
use spin_factors::{
4-
Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors,
8+
AppComponent, Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors,
59
};
610
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};
711

8-
pub struct WasiFactor;
12+
pub struct WasiFactor {
13+
files_mounter: Box<dyn FilesMounter>,
14+
}
15+
16+
impl WasiFactor {
17+
pub fn new(files_mounter: impl FilesMounter + 'static) -> Self {
18+
Self {
19+
files_mounter: Box::new(files_mounter),
20+
}
21+
}
22+
}
923

1024
impl Factor for WasiFactor {
25+
type AppConfig = ();
1126
type InstancePreparer = InstancePreparer;
1227
type InstanceState = InstanceState;
1328

@@ -44,28 +59,107 @@ impl Factor for WasiFactor {
4459
}
4560
}
4661

62+
pub trait FilesMounter {
63+
fn mount_files(&self, app_component: &AppComponent, ctx: MountFilesContext) -> Result<()>;
64+
}
65+
66+
pub struct DummyFilesMounter;
67+
68+
impl FilesMounter for DummyFilesMounter {
69+
fn mount_files(&self, app_component: &AppComponent, _ctx: MountFilesContext) -> Result<()> {
70+
ensure!(
71+
app_component.files().next().is_none(),
72+
"DummyFilesMounter can't actually mount files"
73+
);
74+
Ok(())
75+
}
76+
}
77+
78+
pub struct MountFilesContext<'a> {
79+
wasi_ctx: &'a mut WasiCtxBuilder,
80+
}
81+
82+
impl<'a> MountFilesContext<'a> {
83+
pub fn preopened_dir(
84+
&mut self,
85+
host_path: impl AsRef<Path>,
86+
guest_path: impl AsRef<str>,
87+
writable: bool,
88+
) -> Result<()> {
89+
use wasmtime_wasi::{DirPerms, FilePerms};
90+
let (dir_perms, file_perms) = if writable {
91+
(DirPerms::all(), FilePerms::all())
92+
} else {
93+
(DirPerms::READ, FilePerms::READ)
94+
};
95+
self.wasi_ctx
96+
.preopened_dir(host_path, guest_path, dir_perms, file_perms)?;
97+
Ok(())
98+
}
99+
}
100+
47101
pub struct InstancePreparer {
48102
wasi_ctx: WasiCtxBuilder,
103+
socket_allow_ports: Pool,
49104
}
50105

51106
impl FactorInstancePreparer<WasiFactor> for InstancePreparer {
107+
// NOTE: Replaces WASI parts of AppComponent::apply_store_config
52108
fn new<Factors: SpinFactors>(
53-
_factor: &WasiFactor,
109+
factor: &WasiFactor,
110+
app_component: &AppComponent,
54111
_ctx: PrepareContext<Factors>,
55112
) -> Result<Self> {
113+
let mut wasi_ctx = WasiCtxBuilder::new();
114+
115+
// Apply environment variables
116+
for (key, val) in app_component.environment() {
117+
wasi_ctx.env(key, val);
118+
}
119+
120+
// Mount files
121+
let mount_ctx = MountFilesContext {
122+
wasi_ctx: &mut wasi_ctx,
123+
};
124+
factor.files_mounter.mount_files(app_component, mount_ctx)?;
125+
56126
Ok(Self {
57-
wasi_ctx: WasiCtxBuilder::new(),
127+
wasi_ctx,
128+
socket_allow_ports: Default::default(),
58129
})
59130
}
60131

61-
fn prepare(mut self) -> Result<InstanceState> {
132+
fn prepare(self) -> Result<InstanceState> {
133+
let Self {
134+
mut wasi_ctx,
135+
socket_allow_ports,
136+
} = self;
137+
138+
// Enforce socket_allow_ports
139+
wasi_ctx.socket_addr_check(move |addr, _| socket_allow_ports.check_addr(addr).is_ok());
140+
62141
Ok(InstanceState {
63-
ctx: self.wasi_ctx.build(),
142+
ctx: wasi_ctx.build(),
64143
table: Default::default(),
65144
})
66145
}
67146
}
68147

148+
impl InstancePreparer {
149+
pub fn inherit_network(&mut self) {
150+
self.wasi_ctx.inherit_network();
151+
}
152+
153+
pub fn socket_allow_ports(&mut self, ip_net: IpNet, ports_start: u16, ports_end: Option<u16>) {
154+
self.socket_allow_ports.insert_ip_net_port_range(
155+
ip_net,
156+
ports_start,
157+
ports_end,
158+
cap_primitives::ambient_authority(),
159+
);
160+
}
161+
}
162+
69163
pub struct InstanceState {
70164
ctx: WasiCtx,
71165
table: ResourceTable,

crates/factor-wasi/src/preview1.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use spin_factors::{
2-
Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors,
2+
AppComponent, Factor, FactorInstancePreparer, InitContext, PrepareContext, Result, SpinFactors,
33
};
44
use wasmtime_wasi::{preview1::WasiP1Ctx, WasiCtxBuilder};
55

66
pub struct WasiPreview1Factor;
77

88
impl Factor for WasiPreview1Factor {
9+
type AppConfig = ();
910
type InstancePreparer = InstancePreparer;
1011
type InstanceState = WasiP1Ctx;
1112

@@ -21,6 +22,7 @@ pub struct InstancePreparer {
2122
impl FactorInstancePreparer<WasiPreview1Factor> for InstancePreparer {
2223
fn new<Factors: SpinFactors>(
2324
_factor: &WasiPreview1Factor,
25+
_app_component: &AppComponent,
2426
_ctx: PrepareContext<Factors>,
2527
) -> Result<Self> {
2628
Ok(Self {

0 commit comments

Comments
 (0)