Implement fail-closed ignored workspace routing
This commit is contained in:
parent
ba6a075839
commit
95104fa4e7
1 changed files with 6024 additions and 6 deletions
|
|
@ -3,6 +3,7 @@ use std::fs::{self, File, OpenOptions};
|
|||
use std::net::{Ipv4Addr, SocketAddrV4, TcpListener, TcpStream, ToSocketAddrs};
|
||||
use std::os::linux::net::SocketAddrExt;
|
||||
use std::os::unix::ffi::OsStringExt;
|
||||
use std::os::unix::fs::symlink;
|
||||
use std::os::unix::net::{SocketAddr, UnixListener, UnixStream};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
|
|
@ -46,6 +47,10 @@ const SKIP: u8 = 77;
|
|||
const HOST_COORDINATOR_DRIVER: &str = "--runtime-composition-host-coordinator";
|
||||
const SANDBOX_NETWORK_PROBE: &str = "--sandbox-network-probe";
|
||||
const NETWORK_MATRIX: &str = "--network-matrix";
|
||||
const BENCHMARK_DRIVER: &str = "--runtime-composition-benchmark-command";
|
||||
const BENCHMARK_GUEST: &str = "--runtime-composition-benchmark-guest";
|
||||
const BENCHMARK_COMPLETE: &str = "benchmark-complete";
|
||||
const BENCHMARK_ERROR: &str = "benchmark-error";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let arguments = std::env::args_os().skip(1).collect::<Vec<_>>();
|
||||
|
|
@ -55,6 +60,12 @@ fn main() -> ExitCode {
|
|||
if let Some(result) = dispatch_private_runtime_role(&arguments) {
|
||||
return exit(result);
|
||||
}
|
||||
if arguments.first().and_then(|argument| argument.to_str()) == Some(BENCHMARK_GUEST) {
|
||||
return exit(run_benchmark_guest(&arguments[1..]));
|
||||
}
|
||||
if arguments.first().and_then(|argument| argument.to_str()) == Some(BENCHMARK_DRIVER) {
|
||||
return exit(run_benchmark_driver(&arguments[1..]));
|
||||
}
|
||||
if arguments.first().and_then(|argument| argument.to_str()) == Some(HOST_COORDINATOR_DRIVER) {
|
||||
return exit(run_host_coordinator_driver(&arguments[1..]));
|
||||
}
|
||||
|
|
@ -93,6 +104,89 @@ fn main() -> ExitCode {
|
|||
}
|
||||
}
|
||||
|
||||
fn run_benchmark_driver(arguments: &[std::ffi::OsString]) -> std::io::Result<()> {
|
||||
let separator = arguments
|
||||
.iter()
|
||||
.position(|argument| argument == "--")
|
||||
.ok_or_else(|| std::io::Error::other("benchmark command separator is missing"))?;
|
||||
if separator != 8 || arguments.len() == separator + 1 {
|
||||
return Err(std::io::Error::other(
|
||||
"benchmark composition arguments are malformed",
|
||||
));
|
||||
}
|
||||
let paths = arguments[..separator]
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.collect::<Vec<_>>();
|
||||
let fixture = Fixture::build_benchmark(
|
||||
&paths[0],
|
||||
&paths[1],
|
||||
&paths[2],
|
||||
&paths[3],
|
||||
&paths[4],
|
||||
&paths[5],
|
||||
&paths[6],
|
||||
&paths[7],
|
||||
&arguments[separator + 1..],
|
||||
)
|
||||
.map_err(matrix_io)?;
|
||||
let coordinator = SandboxCoordinator::new(&fixture.store);
|
||||
let runtime = fixture.launch_committed().map_err(matrix_io)?;
|
||||
let results = fixture
|
||||
.benchmark_results
|
||||
.as_ref()
|
||||
.ok_or_else(|| std::io::Error::other("benchmark composition result authority is absent"))?;
|
||||
let complete = results.join(BENCHMARK_COMPLETE);
|
||||
let error = results.join(BENCHMARK_ERROR);
|
||||
for _ in 0..24_000 {
|
||||
if complete.is_file() || error.is_file() {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(5));
|
||||
}
|
||||
let outcome = if complete.is_file() {
|
||||
Ok(())
|
||||
} else if error.is_file() {
|
||||
Err(std::io::Error::other(fs::read_to_string(&error)?))
|
||||
} else {
|
||||
Err(std::io::Error::other("benchmark command timed out"))
|
||||
};
|
||||
runtime
|
||||
.stop(&coordinator)
|
||||
.map_err(|error| std::io::Error::other(error.to_string()))?;
|
||||
outcome
|
||||
}
|
||||
|
||||
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||
fn run_benchmark_guest(arguments: &[std::ffi::OsString]) -> std::io::Result<()> {
|
||||
if arguments.len() < 3 {
|
||||
return Err(std::io::Error::other(
|
||||
"benchmark guest arguments are malformed",
|
||||
));
|
||||
}
|
||||
let results = PathBuf::from(&arguments[0]);
|
||||
let output = std::process::Command::new(&arguments[1])
|
||||
.args(&arguments[2..])
|
||||
.output();
|
||||
match output {
|
||||
Ok(output) if output.status.success() => {
|
||||
fs::write(results.join(BENCHMARK_COMPLETE), b"ok")?;
|
||||
}
|
||||
Ok(output) => fs::write(
|
||||
results.join(BENCHMARK_ERROR),
|
||||
format!(
|
||||
"benchmark command exited with {}: {}",
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
),
|
||||
)?,
|
||||
Err(error) => fs::write(results.join(BENCHMARK_ERROR), error.to_string())?,
|
||||
}
|
||||
loop {
|
||||
thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
fn exit(result: std::io::Result<()>) -> ExitCode {
|
||||
match result {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
|
|
@ -412,6 +506,7 @@ struct Fixture {
|
|||
store: HostCoordinatorDomainStore,
|
||||
bundle: SandboxBundle,
|
||||
activity: PathBuf,
|
||||
benchmark_results: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Fixture {
|
||||
|
|
@ -425,6 +520,111 @@ impl Fixture {
|
|||
Self::build(root, None)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_benchmark(
|
||||
root: &Path,
|
||||
workspace: &Path,
|
||||
host_root: &Path,
|
||||
output: &Path,
|
||||
repository: &Path,
|
||||
cargo_home: &Path,
|
||||
rustup_home: &Path,
|
||||
uv_bin: &Path,
|
||||
sample_arguments: &[std::ffi::OsString],
|
||||
) -> Result<Self, MatrixError> {
|
||||
let configuration_directory = root.join("configuration");
|
||||
let runtime = root.join("runtime");
|
||||
for directory in [&configuration_directory, &runtime] {
|
||||
fs::create_dir_all(directory)?;
|
||||
}
|
||||
let configuration_path = configuration_directory.join("lazaret.toml");
|
||||
let paths = [
|
||||
root,
|
||||
workspace,
|
||||
host_root,
|
||||
output,
|
||||
repository,
|
||||
cargo_home,
|
||||
rustup_home,
|
||||
uv_bin,
|
||||
];
|
||||
if paths.iter().any(|path| {
|
||||
path.to_str()
|
||||
.is_none_or(|value| value.contains(['\n', '\r', '"']))
|
||||
}) {
|
||||
return Err(failure("benchmark composition path is not TOML-safe"));
|
||||
}
|
||||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/runtime-fixture:v1\"\npersistent_dirs = [\"/tooling\", \"/fixtures\"]\nenv = {{ PATH = \"env:BENCHMARK_PATH\", CARGO_HOME = \"env:BENCHMARK_CARGO_HOME\", RUSTUP_HOME = \"env:BENCHMARK_RUSTUP_HOME\", CARGO_BUILD_JOBS = \"env:BENCHMARK_CARGO_BUILD_JOBS\" }}\nvolumes = [\n {{ host = \"{}\", container = \"/tooling/usr\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/fixtures/host\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/fixtures/output\", kind = \"directory\", access = \"read-write\" }},\n {{ host = \"{}\", container = \"/fixtures/repository\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/cargo\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/rustup\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/uv-bin\", kind = \"directory\", access = \"read-only\" }},\n]\n",
|
||||
root.join("data").display(),
|
||||
Path::new("/usr").display(),
|
||||
host_root.display(),
|
||||
output.display(),
|
||||
repository.display(),
|
||||
cargo_home.display(),
|
||||
rustup_home.display(),
|
||||
uv_bin.display(),
|
||||
),
|
||||
)?;
|
||||
let environment = BTreeMap::from([
|
||||
(
|
||||
"BENCHMARK_PATH".into(),
|
||||
"/tooling/cargo/bin:/tooling/uv-bin:/usr/bin:/bin".into(),
|
||||
),
|
||||
("BENCHMARK_CARGO_HOME".into(), "/tooling/cargo".into()),
|
||||
("BENCHMARK_RUSTUP_HOME".into(), "/tooling/rustup".into()),
|
||||
("BENCHMARK_CARGO_BUILD_JOBS".into(), "2".into()),
|
||||
]);
|
||||
let configuration = load_trusted_configuration(ConfigurationLoadRequest {
|
||||
explicit_configuration_path: Some(configuration_path),
|
||||
selected_profile: "default".into(),
|
||||
workspace: workspace.to_path_buf(),
|
||||
home: root.join("home"),
|
||||
xdg_config_home: None,
|
||||
xdg_data_home: None,
|
||||
xdg_runtime_dir: runtime,
|
||||
environment,
|
||||
sandbox_layout: production_sandbox_layout().map_err(failure)?,
|
||||
})
|
||||
.map_err(failure)?;
|
||||
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label());
|
||||
let results = output.to_path_buf();
|
||||
stage_benchmark_rootfs_and_control(&runtime_domain)?;
|
||||
let mut guest_arguments = vec![
|
||||
BENCHMARK_GUEST.to_owned(),
|
||||
"/fixtures/output".to_owned(),
|
||||
"/usr/bin/python3".to_owned(),
|
||||
"/fixtures/repository/tools/ignored_workspace_benchmark.py".to_owned(),
|
||||
"sample".to_owned(),
|
||||
];
|
||||
guest_arguments.extend(
|
||||
sample_arguments
|
||||
.iter()
|
||||
.map(|argument| argument.to_string_lossy().into_owned()),
|
||||
);
|
||||
let root_process =
|
||||
RootProcess::new("/bin/runtime-composition", guest_arguments).map_err(failure)?;
|
||||
let namespace =
|
||||
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
|
||||
let bundle = build_sandbox_bundle(SandboxBuildRequest {
|
||||
configuration: &configuration,
|
||||
container_id: ContainerId::new([6; 32]),
|
||||
expected_network_namespace: namespace,
|
||||
root_process,
|
||||
terminal: None,
|
||||
})
|
||||
.map_err(failure)?;
|
||||
Self::finish_build(
|
||||
None,
|
||||
configuration,
|
||||
bundle,
|
||||
workspace.join("activity"),
|
||||
Some(results),
|
||||
)
|
||||
}
|
||||
|
||||
fn build(root: &Path, tree: Option<TempDir>) -> Result<Self, MatrixError> {
|
||||
let configuration_directory = root.join("configuration");
|
||||
let workspace = root.join("workspace");
|
||||
|
|
@ -473,6 +673,24 @@ impl Fixture {
|
|||
terminal: None,
|
||||
})
|
||||
.map_err(failure)?;
|
||||
Self::finish_build(
|
||||
tree,
|
||||
configuration,
|
||||
bundle,
|
||||
workspace.join("activity"),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn finish_build(
|
||||
tree: Option<TempDir>,
|
||||
configuration: ResolvedDomainConfiguration,
|
||||
bundle: SandboxBundle,
|
||||
activity: PathBuf,
|
||||
benchmark_results: Option<PathBuf>,
|
||||
) -> Result<Self, MatrixError> {
|
||||
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label());
|
||||
let container_id = ContainerId::new([6; 32]);
|
||||
let bundle_path = runtime_domain
|
||||
.join("bundles")
|
||||
.join(hex(container_id.as_bytes()));
|
||||
|
|
@ -500,8 +718,7 @@ impl Fixture {
|
|||
durable_decision: DurableDecision::None,
|
||||
})])
|
||||
.map_err(failure)?;
|
||||
let coordinator = SandboxCoordinator::new(&store);
|
||||
coordinator
|
||||
SandboxCoordinator::new(&store)
|
||||
.begin_intent(
|
||||
GenerationId::new(7),
|
||||
container_id,
|
||||
|
|
@ -515,7 +732,8 @@ impl Fixture {
|
|||
configuration,
|
||||
store,
|
||||
bundle,
|
||||
activity: workspace.join("activity"),
|
||||
activity,
|
||||
benchmark_results,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -864,6 +1082,37 @@ fn stage_rootfs_and_control(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn stage_benchmark_rootfs_and_control(runtime_domain: &Path) -> Result<(), MatrixError> {
|
||||
let rootfs = runtime_domain.join("state-anchor/merged");
|
||||
for directory in [
|
||||
"bin",
|
||||
"tooling/usr",
|
||||
"tooling/cargo",
|
||||
"tooling/rustup",
|
||||
"tooling/uv-bin",
|
||||
"fixtures/host",
|
||||
"fixtures/output",
|
||||
"fixtures/repository",
|
||||
] {
|
||||
fs::create_dir_all(rootfs.join(directory))?;
|
||||
}
|
||||
fs::copy(
|
||||
std::env::current_exe()?,
|
||||
rootfs.join("bin/runtime-composition"),
|
||||
)?;
|
||||
symlink("tooling/usr", rootfs.join("usr"))?;
|
||||
symlink("usr/lib", rootfs.join("lib"))?;
|
||||
symlink("usr/lib64", rootfs.join("lib64"))?;
|
||||
symlink("usr/sbin", rootfs.join("sbin"))?;
|
||||
|
||||
let control = runtime_domain.join("sandbox-control").join(hex(&[6; 32]));
|
||||
for directory in ["credentials", "mailbox/requests", "mailbox/results", "bin"] {
|
||||
fs::create_dir_all(control.join(directory))?;
|
||||
}
|
||||
fs::copy("/bin/true", control.join("bin/lazaret-escalate"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sandbox_participant(store: &HostCoordinatorDomainStore) -> lazaret_core::records::Participant {
|
||||
store
|
||||
.snapshot()
|
||||
|
|
|
|||
Loading…
Reference in a new issue