Files
sausage/host/tests/it/net.rs
T

126 lines
4.1 KiB
Rust

//! Real components can accept TCP connections. Host port registration and host-only config deny access.
use super::hook_guest;
use sausage::{
exec::{Command, Exec, Options},
process::{Runtime, net::NetPolicy},
world::World,
};
use std::{sync::Arc, time::Duration};
#[test]
fn host_policy() {
let temp = tempfile::tempdir().unwrap();
std::fs::write(
temp.path().join("network.toml"),
"deny = ['127.0.0.1:9001', '[::1]:9002']",
)
.unwrap();
let policy = NetPolicy::open(temp.path()).unwrap();
policy.deny_host_port(9000);
for address in [
"127.0.0.1:9000",
"0.0.0.0:9000",
"[::1]:9000",
"[::ffff:127.0.0.1]:9000",
"127.0.0.1:9001",
"[::1]:9002",
] {
assert!(!policy.allows(address.parse().unwrap()));
}
assert!(policy.allows("0.0.0.0:8080".parse().unwrap()));
for interface in if_addrs::get_if_addrs().unwrap() {
assert!(!policy.allows(std::net::SocketAddr::new(interface.ip(), 9000)));
}
assert!(!policy.allows("[::ffff:127.0.0.1]:9001".parse().unwrap()));
}
async fn run(runtime: &Arc<Runtime>, mode: &str, address: String) -> String {
let mut output = Vec::new();
let code = runtime
.exec(
Command::Argv(vec!["/bin/guest.wasm".into(), mode.into(), address]),
Options {
cwd: "/".into(),
timeout: Some(Duration::from_secs(30)),
..Default::default()
},
&mut |_, bytes| output.extend_from_slice(bytes),
)
.await
.unwrap();
assert_eq!(code, 0);
String::from_utf8(output).unwrap()
}
#[tokio::test]
async fn guest_tcp_and_host_denial() {
let guest = hook_guest::component();
let temp = tempfile::tempdir().unwrap();
let world = World::open(temp.path()).unwrap();
std::fs::create_dir_all(temp.path().join("world/bin")).unwrap();
std::fs::copy(guest, temp.path().join("world/bin/guest.wasm")).unwrap();
let configured = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
std::fs::write(
world.host_dir.join("network.toml"),
format!("deny = ['{}']", configured.local_addr().unwrap()),
)
.unwrap();
let runtime = Runtime::new(world).unwrap();
assert!(
run(
&runtime,
"connect",
configured.local_addr().unwrap().to_string()
)
.await
.contains("denied")
);
let native = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let denied = native.local_addr().unwrap();
runtime.network.deny_host_port(denied.port());
assert!(
run(&runtime, "connect", denied.to_string())
.await
.contains("denied")
);
let ephemeral = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let address = ephemeral.local_addr().unwrap();
drop(ephemeral);
let server_runtime = runtime.clone();
let (tx, rx) = tokio::sync::oneshot::channel();
let server = tokio::spawn(async move {
let mut tx = Some(tx);
let mut bytes = Vec::new();
server_runtime
.exec(
Command::Argv(vec![
"/bin/guest.wasm".into(),
"listen".into(),
format!("0.0.0.0:{}", address.port()),
]),
Options {
cwd: "/".into(),
timeout: Some(Duration::from_secs(30)),
..Default::default()
},
&mut |_, chunk| {
bytes.extend_from_slice(chunk);
if bytes.windows(5).any(|w| w == b"ready")
&& let Some(tx) = tx.take()
{
let _ = tx.send(());
}
},
)
.await
.unwrap()
});
tokio::time::timeout(Duration::from_secs(30), rx)
.await
.unwrap()
.unwrap();
assert!(
run(&runtime, "connect", format!("localhost:{}", address.port()))
.await
.contains("ping")
);
assert_eq!(server.await.unwrap(), 0);
}