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

160 lines
5.2 KiB
Rust

//! A faux model runs real bash and curl with inherited conversation grants through the host hook.
use anyhow::{Result, ensure};
use sausage::{
api::Api,
cred::{Caller, Cred, grants::Grant, types::Subject},
world::World,
};
use serde_json::json;
use std::{
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
fn components() -> Result<PathBuf> {
let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland");
let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap();
if !userland.join("cwd-init/init.o").is_file() {
crate::common::cwd_init();
}
ensure!(
std::process::Command::new("cargo")
.args([
"build",
"--release",
"--locked",
"--target",
"wasm32-wasip2",
"-j",
"2",
"-p",
"curl",
"--target-dir"
])
.arg(target)
.current_dir(&userland)
.status()?
.success(),
"curl fixture build failed"
);
let release = target.join("wasm32-wasip2/release");
if !release.join("brush.wasm").is_file() {
ensure!(
crate::common::build_brush()?.success(),
"shell fixture build failed"
);
}
Ok(release)
}
#[tokio::test(flavor = "current_thread")]
async fn ancestry_reaches_the_real_hook() -> Result<()> {
let components = components()?;
let temp = tempfile::tempdir()?;
let world = World::open(temp.path())?;
let disk = temp.path().join("world");
std::fs::create_dir_all(disk.join("bin"))?;
for (source, name) in [("curl.wasm", "curl.wasm"), ("brush.wasm", "sh.wasm")] {
std::fs::copy(components.join(source), disk.join("bin").join(name))?;
}
let server = Arc::new(
tiny_http::Server::http("127.0.0.1:0")
.map_err(|_| anyhow::anyhow!("fixture bind failed"))?,
);
let port = server.server_addr().to_ip().unwrap().port();
let cred = Arc::new(Cred::open(&world.host_dir)?);
let caller = Caller::default();
let token = uuid::Uuid::new_v4().to_string();
let api = Arc::new(Api::open(
cred.clone(),
&world.host_dir,
"localhost",
"http://localhost:8081",
)?);
let runtime = Arc::new(sausage::process::Runtime::new_with_hooks(
World {
root: world.root.clone(),
host_dir: world.host_dir.clone(),
processes: world.processes.clone(),
},
cred.clone(),
sausage::process::default_script(),
Some(api),
)?);
sausage::harness::run_module_with_credentials(
World {
root: world.root.clone(),
host_dir: world.host_dir.clone(),
processes: world.processes.clone(),
},
runtime.clone(),
cred.clone(),
include_str!("../../js/chain-test-bundle.js"),
)
.await?;
let records: serde_json::Value =
serde_json::from_slice(&std::fs::read(disk.join("chain-ids"))?)?;
for (path, subject) in [
("/token", records["parent"]["id"].to_string()),
("/unrelated", records["unrelated"]["id"].to_string()),
] {
let item = cred.create_item(serde_json::from_value(json!({"name":path,"rules":[{"scheme":"http","hosts":["127.0.0.1"],"port":port,"path_prefixes":[path]}],"transform_js":sausage::cred::summary::script("bearer")?,"fields":[{"name":"token","field_type":"text"}]}))?, &caller, cred.unlocked()?)?;
cred.set_field(
&item.id,
&item.fields[0].field_id,
&token,
&caller,
cred.unlocked()?,
)?;
cred.grant_create(
Grant {
id: String::new(),
items: vec![item.id],
subject: Subject::Conversation(subject),
exp: None,
max_uses: None,
uses: 0,
revoked: false,
validator: None,
note: None,
},
&caller,
cred.unlocked()?,
)?;
}
let serving = server.clone();
let expected = format!("Bearer {token}");
let thread = std::thread::spawn(move || {
let mut accepted = 0;
while let Ok(request) = serving.recv() {
let valid = request.headers().iter().any(|header| {
header.field.equiv("Authorization") && header.value.as_str() == expected
});
if valid {
accepted += 1;
}
let _ = request.respond(tiny_http::Response::from_string(if valid {
"accepted"
} else {
"missing"
}));
}
accepted
});
let source = format!(
"globalThis.chainReady = true; globalThis.chainPort = {port};\n{}",
include_str!("../../js/chain-test-bundle.js")
);
let result = tokio::time::timeout(
Duration::from_secs(90),
sausage::harness::run_module_with_credentials(world, runtime, cred, &source),
)
.await;
server.unblock();
let accepted = thread.join().unwrap();
result??;
ensure!(accepted == 3, "unexpected number of injected requests");
Ok(())
}