Bind sandbox handoff authority and restore verifier gates
This commit is contained in:
parent
cf914bdf51
commit
49d5def1a9
18 changed files with 951 additions and 170 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -1282,13 +1282,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazaret-network-sidecar"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"lazaret-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazaret-prepared-lower"
|
name = "lazaret-prepared-lower"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|
@ -1320,6 +1313,7 @@ dependencies = [
|
||||||
"rustix",
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sha2 0.11.0",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ members = [
|
||||||
"crates/lazaret-host-coordinator",
|
"crates/lazaret-host-coordinator",
|
||||||
"crates/lazaret-ignored-workspace",
|
"crates/lazaret-ignored-workspace",
|
||||||
"crates/lazaret-linux",
|
"crates/lazaret-linux",
|
||||||
"crates/lazaret-network-sidecar",
|
|
||||||
"crates/lazaret-prepared-lower",
|
"crates/lazaret-prepared-lower",
|
||||||
"crates/lazaret-runtime-supervisor",
|
"crates/lazaret-runtime-supervisor",
|
||||||
"crates/lazaret-state-anchor",
|
"crates/lazaret-state-anchor",
|
||||||
|
|
@ -18,7 +17,7 @@ exclude = [".lazaret/vendor/fuser-0.17.0"]
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.90"
|
rust-version = "1.97"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -151,8 +151,7 @@ fn independently_observed_namespace_refuses_attestation_mutation() {
|
||||||
lazaret_core::identity::InodeId::new(attestation.enforcement_namespace.inode.get() + 1);
|
lazaret_core::identity::InodeId::new(attestation.enforcement_namespace.inode.get() + 1);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
NetworkCoordinator::new(&fixture.store).validate_attestation(&policy, &attestation),
|
NetworkCoordinator::new(&fixture.store).validate_attestation(&policy, &attestation),
|
||||||
Err(NetworkCoordinatorError::InvalidState(_))
|
Err(NetworkCoordinatorError::InvalidState(_) | NetworkCoordinatorError::InvalidEvidence(_),)
|
||||||
| Err(NetworkCoordinatorError::InvalidEvidence(_))
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use lazaret_core::records::{
|
||||||
use lazaret_linux::ipc::private_seqpacket_pair;
|
use lazaret_linux::ipc::private_seqpacket_pair;
|
||||||
use lazaret_linux::process::{RetainedProcess, child_broker, current_process_identity};
|
use lazaret_linux::process::{RetainedProcess, child_broker, current_process_identity};
|
||||||
use lazaret_runtime_supervisor::network::NetworkProbeRequest;
|
use lazaret_runtime_supervisor::network::NetworkProbeRequest;
|
||||||
|
use lazaret_runtime_supervisor::sandbox::SandboxHandoffAuthority;
|
||||||
use lazaret_runtime_supervisor::supervision::{
|
use lazaret_runtime_supervisor::supervision::{
|
||||||
AttachedRunscSignal, CoordinatorChannel, RuntimeSupervisorLaunch, SupervisorEvent,
|
AttachedRunscSignal, CoordinatorChannel, RuntimeSupervisorLaunch, SupervisorEvent,
|
||||||
};
|
};
|
||||||
|
|
@ -365,6 +366,7 @@ pub struct AwaitingAttachedRunsc {
|
||||||
staged: Option<StagedProcessOwnership>,
|
staged: Option<StagedProcessOwnership>,
|
||||||
helper_staged: Option<StagedProcessOwnership>,
|
helper_staged: Option<StagedProcessOwnership>,
|
||||||
network_evidence: Option<NetworkReadinessEvidence>,
|
network_evidence: Option<NetworkReadinessEvidence>,
|
||||||
|
runsc_command: lazaret_runtime_supervisor::sandbox::RunscCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AwaitingAttachedRunsc {
|
impl AwaitingAttachedRunsc {
|
||||||
|
|
@ -450,6 +452,7 @@ impl AwaitingAttachedRunsc {
|
||||||
staged: None,
|
staged: None,
|
||||||
helper_staged: None,
|
helper_staged: None,
|
||||||
network_evidence: None,
|
network_evidence: None,
|
||||||
|
runsc_command: launch.runsc_command().clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -621,12 +624,23 @@ impl AwaitingAttachedRunsc {
|
||||||
pub fn acknowledge(
|
pub fn acknowledge(
|
||||||
mut self,
|
mut self,
|
||||||
staged: StagedProcessOwnership,
|
staged: StagedProcessOwnership,
|
||||||
|
handoff: &SandboxHandoffAuthority<'_>,
|
||||||
) -> Result<AwaitingSandboxReadiness, SandboxCoordinatorError> {
|
) -> Result<AwaitingSandboxReadiness, SandboxCoordinatorError> {
|
||||||
if self.staged != Some(staged) {
|
if self.staged != Some(staged) {
|
||||||
return Err(SandboxCoordinatorError::InvalidState(
|
return Err(SandboxCoordinatorError::InvalidState(
|
||||||
"attached runsc acknowledgment does not match pending stage",
|
"attached runsc acknowledgment does not match pending stage",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if staged.domain_id() != handoff.domain_id()
|
||||||
|
|| !handoff.matches_command(&self.runsc_command)
|
||||||
|
{
|
||||||
|
return Err(SandboxCoordinatorError::InvalidState(
|
||||||
|
"sandbox handoff authority does not match attached runsc",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
handoff
|
||||||
|
.revalidate()
|
||||||
|
.map_err(|error| SandboxCoordinatorError::Io(io::Error::other(error)))?;
|
||||||
self.control
|
self.control
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(SandboxCoordinatorError::InvalidState(
|
.ok_or(SandboxCoordinatorError::InvalidState(
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ use lazaret_linux::process::{
|
||||||
};
|
};
|
||||||
use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget};
|
use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget};
|
||||||
use lazaret_runtime_supervisor::sandbox::{
|
use lazaret_runtime_supervisor::sandbox::{
|
||||||
NETWORK_NAMESPACE_PATH, RootProcess, SandboxBuildRequest, SandboxBundle, build_sandbox_bundle,
|
NETWORK_NAMESPACE_PATH, RootProcess, RunscCommand, SandboxBuildRequest, SandboxBundle,
|
||||||
production_sandbox_layout,
|
build_sandbox_bundle, production_sandbox_layout,
|
||||||
};
|
};
|
||||||
use lazaret_runtime_supervisor::supervision::{
|
use lazaret_runtime_supervisor::supervision::{
|
||||||
AttachedRunscSignal, RuntimeNetworkLaunch, RuntimeSupervisorLaunch,
|
AttachedRunscSignal, RuntimeNetworkLaunch, RuntimeSupervisorLaunch,
|
||||||
|
|
@ -47,6 +47,7 @@ const SKIP: u8 = 77;
|
||||||
const HOST_COORDINATOR_DRIVER: &str = "--runtime-composition-host-coordinator";
|
const HOST_COORDINATOR_DRIVER: &str = "--runtime-composition-host-coordinator";
|
||||||
const SANDBOX_NETWORK_PROBE: &str = "--sandbox-network-probe";
|
const SANDBOX_NETWORK_PROBE: &str = "--sandbox-network-probe";
|
||||||
const NETWORK_MATRIX: &str = "--network-matrix";
|
const NETWORK_MATRIX: &str = "--network-matrix";
|
||||||
|
const HANDOFF_MATRIX: &str = "--handoff-matrix";
|
||||||
const BENCHMARK_DRIVER: &str = "--runtime-composition-benchmark-command";
|
const BENCHMARK_DRIVER: &str = "--runtime-composition-benchmark-command";
|
||||||
const BENCHMARK_GUEST: &str = "--runtime-composition-benchmark-guest";
|
const BENCHMARK_GUEST: &str = "--runtime-composition-benchmark-guest";
|
||||||
const BENCHMARK_COMPLETE: &str = "benchmark-complete";
|
const BENCHMARK_COMPLETE: &str = "benchmark-complete";
|
||||||
|
|
@ -88,6 +89,22 @@ fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if arguments.first().and_then(|argument| argument.to_str()) == Some(HANDOFF_MATRIX) {
|
||||||
|
return match run_handoff_matrix() {
|
||||||
|
Ok(()) => {
|
||||||
|
println!("PASS final handoff replacement refusal");
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
}
|
||||||
|
Err(MatrixError::Skip(reason)) => {
|
||||||
|
println!("SKIP final handoff replacement refusal: {reason}");
|
||||||
|
ExitCode::from(SKIP)
|
||||||
|
}
|
||||||
|
Err(MatrixError::Failure(error)) => {
|
||||||
|
eprintln!("final handoff replacement refusal failed: {error}");
|
||||||
|
ExitCode::FAILURE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
match run_matrix() {
|
match run_matrix() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("PASS foreground runtime composition and causal-loss matrix");
|
println!("PASS foreground runtime composition and causal-loss matrix");
|
||||||
|
|
@ -211,6 +228,7 @@ impl From<std::io::Error> for MatrixError {
|
||||||
fn run_matrix() -> Result<(), MatrixError> {
|
fn run_matrix() -> Result<(), MatrixError> {
|
||||||
require_matrix_prerequisites()?;
|
require_matrix_prerequisites()?;
|
||||||
lost_acknowledgment_refuses_target_work()?;
|
lost_acknowledgment_refuses_target_work()?;
|
||||||
|
replaced_handoff_authority_refuses_target_work()?;
|
||||||
normal_stop_and_stop_resume()?;
|
normal_stop_and_stop_resume()?;
|
||||||
attached_runsc_loss_stops_guest()?;
|
attached_runsc_loss_stops_guest()?;
|
||||||
runtime_supervisor_loss_stops_guest()?;
|
runtime_supervisor_loss_stops_guest()?;
|
||||||
|
|
@ -223,6 +241,12 @@ fn run_network_matrix() -> Result<(), MatrixError> {
|
||||||
run_network_cases()
|
run_network_cases()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_handoff_matrix() -> Result<(), MatrixError> {
|
||||||
|
require_matrix_prerequisites()?;
|
||||||
|
replaced_handoff_authority_refuses_target_work()?;
|
||||||
|
cross_bundle_handoff_refuses_target_work()
|
||||||
|
}
|
||||||
|
|
||||||
fn run_network_cases() -> Result<(), MatrixError> {
|
fn run_network_cases() -> Result<(), MatrixError> {
|
||||||
failed_probe_blocks_sandbox_start()?;
|
failed_probe_blocks_sandbox_start()?;
|
||||||
networked_sandbox_view_isolated()
|
networked_sandbox_view_isolated()
|
||||||
|
|
@ -301,6 +325,54 @@ fn lost_acknowledgment_refuses_target_work() -> Result<(), MatrixError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn replaced_handoff_authority_refuses_target_work() -> Result<(), MatrixError> {
|
||||||
|
let fixture = Fixture::new()?;
|
||||||
|
let handoff = fixture
|
||||||
|
.bundle
|
||||||
|
.capture_handoff_authority(&fixture.configuration)
|
||||||
|
.map_err(failure)?;
|
||||||
|
let mut pending = fixture.launch_pending()?;
|
||||||
|
let staged = pending
|
||||||
|
.receive_staged(&SandboxCoordinator::new(&fixture.store))
|
||||||
|
.map_err(failure)?;
|
||||||
|
let workspace = host_path(fixture.configuration.workspace().canonical_label());
|
||||||
|
let selected = workspace.with_extension("selected");
|
||||||
|
fs::rename(&workspace, &selected)?;
|
||||||
|
fs::create_dir(&workspace)?;
|
||||||
|
let marker = workspace.join("replacement-marker");
|
||||||
|
fs::write(&marker, b"unchanged")?;
|
||||||
|
if pending.acknowledge(staged, &handoff).is_ok() {
|
||||||
|
return Err(failure("replaced handoff authority released sandbox work"));
|
||||||
|
}
|
||||||
|
if fs::read(&marker)? != b"unchanged"
|
||||||
|
|| workspace.join("activity").exists()
|
||||||
|
|| selected.join("activity").exists()
|
||||||
|
{
|
||||||
|
return Err(failure("refused handoff mutated a workspace tree"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cross_bundle_handoff_refuses_target_work() -> Result<(), MatrixError> {
|
||||||
|
let fixture = Fixture::new()?;
|
||||||
|
let handoff = fixture
|
||||||
|
.bundle
|
||||||
|
.capture_handoff_authority(&fixture.configuration)
|
||||||
|
.map_err(failure)?;
|
||||||
|
let alternate = fixture.alternate_bundle()?;
|
||||||
|
if fixture.bundle.command().arguments() != alternate.command().arguments() {
|
||||||
|
return Err(failure("cross-bundle fixture changed runsc argv"));
|
||||||
|
}
|
||||||
|
let mut pending = fixture.launch_pending_with_command(alternate.command())?;
|
||||||
|
let staged = pending
|
||||||
|
.receive_staged(&SandboxCoordinator::new(&fixture.store))
|
||||||
|
.map_err(failure)?;
|
||||||
|
if pending.acknowledge(staged, &handoff).is_ok() || fixture.activity.exists() {
|
||||||
|
return Err(failure("cross-bundle handoff released sandbox work"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn normal_stop_and_stop_resume() -> Result<(), MatrixError> {
|
fn normal_stop_and_stop_resume() -> Result<(), MatrixError> {
|
||||||
let fixture = Fixture::new()?;
|
let fixture = Fixture::new()?;
|
||||||
let runtime = fixture.launch_committed()?;
|
let runtime = fixture.launch_committed()?;
|
||||||
|
|
@ -738,6 +810,13 @@ impl Fixture {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch_pending(&self) -> Result<AwaitingAttachedRunsc, MatrixError> {
|
fn launch_pending(&self) -> Result<AwaitingAttachedRunsc, MatrixError> {
|
||||||
|
self.launch_pending_with_command(self.bundle.command())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn launch_pending_with_command(
|
||||||
|
&self,
|
||||||
|
command: &RunscCommand,
|
||||||
|
) -> Result<AwaitingAttachedRunsc, MatrixError> {
|
||||||
let launch = RuntimeSupervisorLaunch::from_runsc_command(
|
let launch = RuntimeSupervisorLaunch::from_runsc_command(
|
||||||
ProcessOwnershipContext {
|
ProcessOwnershipContext {
|
||||||
domain_id: self.configuration.domain_id(),
|
domain_id: self.configuration.domain_id(),
|
||||||
|
|
@ -745,19 +824,43 @@ impl Fixture {
|
||||||
coordinator_id: CoordinatorId::new([3; 32]),
|
coordinator_id: CoordinatorId::new([3; 32]),
|
||||||
slot: lazaret_core::records::HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
slot: lazaret_core::records::HostProcessOwnershipSlot::SandboxRuntimeSupervisor,
|
||||||
},
|
},
|
||||||
self.bundle.command(),
|
command,
|
||||||
)?;
|
)?;
|
||||||
AwaitingAttachedRunsc::launch(&SandboxCoordinator::new(&self.store), &launch)
|
AwaitingAttachedRunsc::launch(&SandboxCoordinator::new(&self.store), &launch)
|
||||||
.map_err(failure)
|
.map_err(failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn alternate_bundle(&self) -> Result<SandboxBundle, MatrixError> {
|
||||||
|
let namespace =
|
||||||
|
lazaret_linux::filesystem::namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))?;
|
||||||
|
build_sandbox_bundle(SandboxBuildRequest {
|
||||||
|
configuration: &self.configuration,
|
||||||
|
container_id: ContainerId::new([6; 32]),
|
||||||
|
expected_network_namespace: namespace,
|
||||||
|
root_process: RootProcess::new(
|
||||||
|
"/bin/sh",
|
||||||
|
[
|
||||||
|
"-c".to_owned(),
|
||||||
|
"echo cross-bundle >> /workspace/activity".to_owned(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.map_err(failure)?,
|
||||||
|
terminal: None,
|
||||||
|
})
|
||||||
|
.map_err(failure)
|
||||||
|
}
|
||||||
|
|
||||||
fn launch_committed(&self) -> Result<CommittedSandboxRuntime, MatrixError> {
|
fn launch_committed(&self) -> Result<CommittedSandboxRuntime, MatrixError> {
|
||||||
|
let handoff = self
|
||||||
|
.bundle
|
||||||
|
.capture_handoff_authority(&self.configuration)
|
||||||
|
.map_err(failure)?;
|
||||||
let mut pending = self.launch_pending()?;
|
let mut pending = self.launch_pending()?;
|
||||||
let staged = pending
|
let staged = pending
|
||||||
.receive_staged(&SandboxCoordinator::new(&self.store))
|
.receive_staged(&SandboxCoordinator::new(&self.store))
|
||||||
.map_err(failure)?;
|
.map_err(failure)?;
|
||||||
pending
|
pending
|
||||||
.acknowledge(staged)
|
.acknowledge(staged, &handoff)
|
||||||
.map_err(failure)?
|
.map_err(failure)?
|
||||||
.commit(&SandboxCoordinator::new(&self.store))
|
.commit(&SandboxCoordinator::new(&self.store))
|
||||||
.map_err(failure)
|
.map_err(failure)
|
||||||
|
|
@ -779,32 +882,9 @@ impl NetworkFixture {
|
||||||
fn new() -> Result<Self, MatrixError> {
|
fn new() -> Result<Self, MatrixError> {
|
||||||
let tree = tempfile::tempdir()?;
|
let tree = tempfile::tempdir()?;
|
||||||
let root = tree.path();
|
let root = tree.path();
|
||||||
let configuration_directory = root.join("configuration");
|
|
||||||
let workspace = root.join("workspace");
|
let workspace = root.join("workspace");
|
||||||
let runtime = root.join("runtime");
|
let runtime = root.join("runtime");
|
||||||
fs::create_dir(&configuration_directory)?;
|
let configuration = network_configuration(root, &workspace, &runtime)?;
|
||||||
fs::create_dir(&workspace)?;
|
|
||||||
fs::create_dir(&runtime)?;
|
|
||||||
let configuration_path = configuration_directory.join("lazaret.toml");
|
|
||||||
fs::write(
|
|
||||||
&configuration_path,
|
|
||||||
format!(
|
|
||||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/network-fixture:v1\"\npersistent_dirs = []\n",
|
|
||||||
root.join("data").display()
|
|
||||||
),
|
|
||||||
)?;
|
|
||||||
let configuration = load_trusted_configuration(ConfigurationLoadRequest {
|
|
||||||
explicit_configuration_path: Some(configuration_path),
|
|
||||||
selected_profile: "default".into(),
|
|
||||||
workspace: workspace.clone(),
|
|
||||||
home: root.join("home"),
|
|
||||||
xdg_config_home: None,
|
|
||||||
xdg_data_home: None,
|
|
||||||
xdg_runtime_dir: runtime,
|
|
||||||
environment: BTreeMap::new(),
|
|
||||||
sandbox_layout: production_sandbox_layout().map_err(failure)?,
|
|
||||||
})
|
|
||||||
.map_err(failure)?;
|
|
||||||
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label());
|
let runtime_domain = host_path(configuration.runtime_domain_root().canonical_label());
|
||||||
let container_id = ContainerId::new([16; 32]);
|
let container_id = ContainerId::new([16; 32]);
|
||||||
stage_rootfs_and_control(&runtime_domain, container_id)?;
|
stage_rootfs_and_control(&runtime_domain, container_id)?;
|
||||||
|
|
@ -845,36 +925,7 @@ impl NetworkFixture {
|
||||||
fs::create_dir_all(&bundle_path)?;
|
fs::create_dir_all(&bundle_path)?;
|
||||||
fs::write(bundle_path.join("config.json"), bundle.specification_json())?;
|
fs::write(bundle_path.join("config.json"), bundle.specification_json())?;
|
||||||
fs::create_dir_all(runtime_domain.join("runsc"))?;
|
fs::create_dir_all(runtime_domain.join("runsc"))?;
|
||||||
let domain = Domain {
|
let store = network_store(&configuration, &runtime_domain, container_id)?;
|
||||||
domain_id: configuration.domain_id(),
|
|
||||||
profile_name: configuration.selected_profile().to_owned(),
|
|
||||||
canonical_workspace: configuration.workspace().canonical_label().clone(),
|
|
||||||
};
|
|
||||||
let persistent_domain = host_path(configuration.persistent_domain_root().canonical_label());
|
|
||||||
let lock = try_exclusive_flock(lock_file(&persistent_domain.join("domain.lock")))?;
|
|
||||||
let store =
|
|
||||||
HostCoordinatorDomainStore::open(lock, &persistent_domain.join("domain.redb"), &domain)
|
|
||||||
.map_err(failure)?;
|
|
||||||
store
|
|
||||||
.apply(&[DomainStoreMutation::PutGeneration(Generation {
|
|
||||||
domain_id: configuration.domain_id(),
|
|
||||||
generation_id: GenerationId::new(7),
|
|
||||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
|
||||||
coordinator_id: CoordinatorId::new([13; 32]),
|
|
||||||
network_sidecar_id: NetworkSidecarId::new([14; 32]),
|
|
||||||
sandbox_id: SandboxId::new([15; 32]),
|
|
||||||
durable_decision: DurableDecision::None,
|
|
||||||
})])
|
|
||||||
.map_err(failure)?;
|
|
||||||
SandboxCoordinator::new(&store)
|
|
||||||
.begin_intent(
|
|
||||||
GenerationId::new(7),
|
|
||||||
container_id,
|
|
||||||
&runtime_domain.join("runsc"),
|
|
||||||
BundleDigest::new([17; 32]),
|
|
||||||
current_process_identity()?,
|
|
||||||
)
|
|
||||||
.map_err(failure)?;
|
|
||||||
let policy =
|
let policy =
|
||||||
derive_ipv4_policy(&configuration, [IpAddress::V4([10, 0, 2, 3])]).map_err(failure)?;
|
derive_ipv4_policy(&configuration, [IpAddress::V4([10, 0, 2, 3])]).map_err(failure)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
@ -898,6 +949,10 @@ impl NetworkFixture {
|
||||||
permitted_address: [u8; 4],
|
permitted_address: [u8; 4],
|
||||||
permitted_port: u16,
|
permitted_port: u16,
|
||||||
) -> Result<CommittedSandboxRuntime, MatrixError> {
|
) -> Result<CommittedSandboxRuntime, MatrixError> {
|
||||||
|
let handoff = self
|
||||||
|
.bundle
|
||||||
|
.capture_handoff_authority(&self.configuration)
|
||||||
|
.map_err(failure)?;
|
||||||
let launch = RuntimeSupervisorLaunch::from_networked_runsc_command(
|
let launch = RuntimeSupervisorLaunch::from_networked_runsc_command(
|
||||||
ProcessOwnershipContext {
|
ProcessOwnershipContext {
|
||||||
domain_id: self.configuration.domain_id(),
|
domain_id: self.configuration.domain_id(),
|
||||||
|
|
@ -950,7 +1005,7 @@ impl NetworkFixture {
|
||||||
.map_err(failure)?;
|
.map_err(failure)?;
|
||||||
let staged = pending.receive_staged(&sandbox).map_err(failure)?;
|
let staged = pending.receive_staged(&sandbox).map_err(failure)?;
|
||||||
let (runtime, _) = pending
|
let (runtime, _) = pending
|
||||||
.acknowledge(staged)
|
.acknowledge(staged, &handoff)
|
||||||
.map_err(failure)?
|
.map_err(failure)?
|
||||||
.commit_networked(&sandbox)
|
.commit_networked(&sandbox)
|
||||||
.map_err(failure)?;
|
.map_err(failure)?;
|
||||||
|
|
@ -958,16 +1013,83 @@ impl NetworkFixture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn network_configuration(
|
||||||
|
root: &Path,
|
||||||
|
workspace: &Path,
|
||||||
|
runtime: &Path,
|
||||||
|
) -> Result<ResolvedDomainConfiguration, MatrixError> {
|
||||||
|
let configuration_directory = root.join("configuration");
|
||||||
|
fs::create_dir(&configuration_directory)?;
|
||||||
|
fs::create_dir(workspace)?;
|
||||||
|
fs::create_dir(runtime)?;
|
||||||
|
let configuration_path = configuration_directory.join("lazaret.toml");
|
||||||
|
fs::write(
|
||||||
|
&configuration_path,
|
||||||
|
format!(
|
||||||
|
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/network-fixture:v1\"\npersistent_dirs = []\n",
|
||||||
|
root.join("data").display()
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
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.to_path_buf(),
|
||||||
|
environment: BTreeMap::new(),
|
||||||
|
sandbox_layout: production_sandbox_layout().map_err(failure)?,
|
||||||
|
})
|
||||||
|
.map_err(failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn network_store(
|
||||||
|
configuration: &ResolvedDomainConfiguration,
|
||||||
|
runtime_domain: &Path,
|
||||||
|
container_id: ContainerId,
|
||||||
|
) -> Result<HostCoordinatorDomainStore, MatrixError> {
|
||||||
|
let domain = Domain {
|
||||||
|
domain_id: configuration.domain_id(),
|
||||||
|
profile_name: configuration.selected_profile().to_owned(),
|
||||||
|
canonical_workspace: configuration.workspace().canonical_label().clone(),
|
||||||
|
};
|
||||||
|
let persistent_domain = host_path(configuration.persistent_domain_root().canonical_label());
|
||||||
|
let lock = try_exclusive_flock(lock_file(&persistent_domain.join("domain.lock")))?;
|
||||||
|
let store =
|
||||||
|
HostCoordinatorDomainStore::open(lock, &persistent_domain.join("domain.redb"), &domain)
|
||||||
|
.map_err(failure)?;
|
||||||
|
store
|
||||||
|
.apply(&[DomainStoreMutation::PutGeneration(Generation {
|
||||||
|
domain_id: configuration.domain_id(),
|
||||||
|
generation_id: GenerationId::new(7),
|
||||||
|
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||||
|
coordinator_id: CoordinatorId::new([13; 32]),
|
||||||
|
network_sidecar_id: NetworkSidecarId::new([14; 32]),
|
||||||
|
sandbox_id: SandboxId::new([15; 32]),
|
||||||
|
durable_decision: DurableDecision::None,
|
||||||
|
})])
|
||||||
|
.map_err(failure)?;
|
||||||
|
SandboxCoordinator::new(&store)
|
||||||
|
.begin_intent(
|
||||||
|
GenerationId::new(7),
|
||||||
|
container_id,
|
||||||
|
&runtime_domain.join("runsc"),
|
||||||
|
BundleDigest::new([17; 32]),
|
||||||
|
current_process_identity()?,
|
||||||
|
)
|
||||||
|
.map_err(failure)?;
|
||||||
|
Ok(store)
|
||||||
|
}
|
||||||
|
|
||||||
fn failed_probe_blocks_sandbox_start() -> Result<(), MatrixError> {
|
fn failed_probe_blocks_sandbox_start() -> Result<(), MatrixError> {
|
||||||
let fixture = NetworkFixture::new()?;
|
let fixture = NetworkFixture::new()?;
|
||||||
match fixture.launch_with_permitted([203, 0, 113, 1], 9) {
|
if let Ok(runtime) = fixture.launch_with_permitted([203, 0, 113, 1], 9) {
|
||||||
Ok(runtime) => {
|
|
||||||
runtime
|
runtime
|
||||||
.stop(&SandboxCoordinator::new(&fixture.store))
|
.stop(&SandboxCoordinator::new(&fixture.store))
|
||||||
.map_err(failure)?;
|
.map_err(failure)?;
|
||||||
Err(failure("failed physical probe started the sandbox"))
|
return Err(failure("failed physical probe started the sandbox"));
|
||||||
}
|
}
|
||||||
Err(_) => {
|
|
||||||
let sandbox = sandbox_participant(&fixture.store);
|
let sandbox = sandbox_participant(&fixture.store);
|
||||||
if sandbox.state() != ParticipantState::Intent || fixture.probe_result.exists() {
|
if sandbox.state() != ParticipantState::Intent || fixture.probe_result.exists() {
|
||||||
Err(failure(
|
Err(failure(
|
||||||
|
|
@ -977,8 +1099,6 @@ fn failed_probe_blocks_sandbox_start() -> Result<(), MatrixError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn networked_sandbox_view_isolated() -> Result<(), MatrixError> {
|
fn networked_sandbox_view_isolated() -> Result<(), MatrixError> {
|
||||||
let fixture = NetworkFixture::new()?;
|
let fixture = NetworkFixture::new()?;
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,7 @@ fn parse(arguments: &[OsString]) -> io::Result<MountConfiguration> {
|
||||||
mountpoint: PathBuf::from(&arguments[2]),
|
mountpoint: PathBuf::from(&arguments[2]),
|
||||||
global_excludes: arguments.get(3).map(PathBuf::from),
|
global_excludes: arguments.get(3).map(PathBuf::from),
|
||||||
threads: std::thread::available_parallelism()
|
threads: std::thread::available_parallelism()
|
||||||
.map(std::num::NonZero::get)
|
.map_or(1, std::num::NonZero::get)
|
||||||
.unwrap_or(1)
|
|
||||||
.min(8),
|
.min(8),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,13 @@ pub struct ProcessOwnershipContext {
|
||||||
pub slot: HostProcessOwnershipSlot,
|
pub slot: HostProcessOwnershipSlot,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AcknowledgedChildAcquisition<'target> {
|
||||||
|
pub expected_parent: PidfsProcessIdentityV1,
|
||||||
|
pub ownership: ProcessOwnershipContext,
|
||||||
|
pub target: &'target TargetCommand,
|
||||||
|
pub standard_io: ChildStandardIo,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RetainedProcess {
|
pub struct RetainedProcess {
|
||||||
pidfd: OwnedFd,
|
pidfd: OwnedFd,
|
||||||
|
|
@ -366,10 +373,12 @@ impl ChildBroker {
|
||||||
Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result<ProcessOwnershipCommitAck>,
|
Acknowledge: FnOnce(StagedProcessOwnership) -> io::Result<ProcessOwnershipCommitAck>,
|
||||||
{
|
{
|
||||||
self.acquire_with_stdio_after_ack(
|
self.acquire_with_stdio_after_ack(
|
||||||
|
AcknowledgedChildAcquisition {
|
||||||
expected_parent,
|
expected_parent,
|
||||||
ownership,
|
ownership,
|
||||||
target,
|
target,
|
||||||
standard_io,
|
standard_io,
|
||||||
|
},
|
||||||
stage,
|
stage,
|
||||||
acknowledge,
|
acknowledge,
|
||||||
|| Ok(()),
|
|| Ok(()),
|
||||||
|
|
@ -385,10 +394,7 @@ impl ChildBroker {
|
||||||
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||||
pub fn acquire_with_stdio_after_ack<Stage, Acknowledge, Transition>(
|
pub fn acquire_with_stdio_after_ack<Stage, Acknowledge, Transition>(
|
||||||
&self,
|
&self,
|
||||||
expected_parent: PidfsProcessIdentityV1,
|
acquisition: AcknowledgedChildAcquisition<'_>,
|
||||||
ownership: ProcessOwnershipContext,
|
|
||||||
target: &TargetCommand,
|
|
||||||
mut standard_io: ChildStandardIo,
|
|
||||||
stage: Stage,
|
stage: Stage,
|
||||||
acknowledge: Acknowledge,
|
acknowledge: Acknowledge,
|
||||||
transition: Transition,
|
transition: Transition,
|
||||||
|
|
@ -402,14 +408,18 @@ impl ChildBroker {
|
||||||
.exclusion
|
.exclusion
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|_| io::Error::other("child broker lock poisoned"))?;
|
.map_err(|_| io::Error::other("child broker lock poisoned"))?;
|
||||||
require_parent_edge_standard_io(target.parent_edge(), standard_io.policy())?;
|
require_parent_edge_standard_io(
|
||||||
|
acquisition.target.parent_edge(),
|
||||||
|
acquisition.standard_io.policy(),
|
||||||
|
)?;
|
||||||
require_default_sigchld()?;
|
require_default_sigchld()?;
|
||||||
require_reapable_sigchld()?;
|
require_reapable_sigchld()?;
|
||||||
let (release, wait) = launch_barrier()?;
|
let (release, wait) = launch_barrier()?;
|
||||||
let mut command = private_role_command(WRAPPER_ROLE);
|
let mut command = private_role_command(WRAPPER_ROLE);
|
||||||
append_parent_identity(&mut command, expected_parent);
|
append_parent_identity(&mut command, acquisition.expected_parent);
|
||||||
target.append_to(&mut command);
|
acquisition.target.append_to(&mut command);
|
||||||
command.stdin(wait.into_stdio());
|
command.stdin(wait.into_stdio());
|
||||||
|
let mut standard_io = acquisition.standard_io;
|
||||||
if let Some(stdout) = standard_io.stdout.take() {
|
if let Some(stdout) = standard_io.stdout.take() {
|
||||||
command.stdout(stdout);
|
command.stdout(stdout);
|
||||||
}
|
}
|
||||||
|
|
@ -424,10 +434,10 @@ impl ChildBroker {
|
||||||
Err(error) => return fail_before_pidfd(release, &mut child, error),
|
Err(error) => return fail_before_pidfd(release, &mut child, error),
|
||||||
};
|
};
|
||||||
let staged = StagedProcessOwnership::v1(
|
let staged = StagedProcessOwnership::v1(
|
||||||
ownership.domain_id,
|
acquisition.ownership.domain_id,
|
||||||
ownership.generation_id,
|
acquisition.ownership.generation_id,
|
||||||
ownership.coordinator_id,
|
acquisition.ownership.coordinator_id,
|
||||||
ownership.slot,
|
acquisition.ownership.slot,
|
||||||
process.identity,
|
process.identity,
|
||||||
);
|
);
|
||||||
if let Err(error) = stage(staged) {
|
if let Err(error) = stage(staged) {
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ use lazaret_core::records::{
|
||||||
HostProcessOwnershipSlot, ProcessOwnershipCommitAck, StagedProcessOwnership,
|
HostProcessOwnershipSlot, ProcessOwnershipCommitAck, StagedProcessOwnership,
|
||||||
};
|
};
|
||||||
use lazaret_linux::process::{
|
use lazaret_linux::process::{
|
||||||
ChildStandardIo, ProcessOwnershipContext, TargetCommand, append_parent_identity, child_broker,
|
AcknowledgedChildAcquisition, ChildStandardIo, ProcessOwnershipContext, TargetCommand,
|
||||||
current_process_identity, dispatch_private_process_role, verify_direct_child_pidfd,
|
append_parent_identity, child_broker, current_process_identity, dispatch_private_process_role,
|
||||||
|
verify_direct_child_pidfd,
|
||||||
};
|
};
|
||||||
use rustix::pipe::{PipeFlags, pipe_with};
|
use rustix::pipe::{PipeFlags, pipe_with};
|
||||||
use rustix::process::{PidfdFlags, pidfd_open};
|
use rustix::process::{PidfdFlags, pidfd_open};
|
||||||
|
|
@ -45,6 +46,7 @@ fn exit(result: std::io::Result<()>) -> ExitCode {
|
||||||
fn run_all() -> std::io::Result<()> {
|
fn run_all() -> std::io::Result<()> {
|
||||||
successful_acquisition()?;
|
successful_acquisition()?;
|
||||||
acknowledged_transition_precedes_exact_wrapper_release()?;
|
acknowledged_transition_precedes_exact_wrapper_release()?;
|
||||||
|
failed_acknowledged_transition_refuses_exact_wrapper_release()?;
|
||||||
lifetime_pipe_descriptor_contract()?;
|
lifetime_pipe_descriptor_contract()?;
|
||||||
failed_stages_and_acknowledgments()?;
|
failed_stages_and_acknowledgments()?;
|
||||||
production_wrapper_refuses_former_harness_path()?;
|
production_wrapper_refuses_former_harness_path()?;
|
||||||
|
|
@ -54,6 +56,29 @@ fn run_all() -> std::io::Result<()> {
|
||||||
concurrent_acquisitions_serialize()
|
concurrent_acquisitions_serialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||||
|
fn failed_acknowledged_transition_refuses_exact_wrapper_release() -> std::io::Result<()> {
|
||||||
|
let target_marker = marker("failed-transition-target");
|
||||||
|
let target = TargetCommand::new("/usr/bin/touch").argument(&target_marker);
|
||||||
|
let result = child_broker().acquire_with_stdio_after_ack(
|
||||||
|
AcknowledgedChildAcquisition {
|
||||||
|
expected_parent: current_process_identity()?,
|
||||||
|
ownership: context(),
|
||||||
|
target: &target,
|
||||||
|
standard_io: ChildStandardIo::inherit(),
|
||||||
|
},
|
||||||
|
|_| Ok(()),
|
||||||
|
|staged| Ok(ProcessOwnershipCommitAck::v1(staged)),
|
||||||
|
|| Err(std::io::Error::other("handoff authority changed")),
|
||||||
|
);
|
||||||
|
if result.is_ok() || target_marker.exists() {
|
||||||
|
return Err(std::io::Error::other(
|
||||||
|
"failed transition released the exact wrapper",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
#[allow(clippy::disallowed_methods, clippy::disallowed_types)]
|
||||||
fn acknowledged_transition_precedes_exact_wrapper_release() -> std::io::Result<()> {
|
fn acknowledged_transition_precedes_exact_wrapper_release() -> std::io::Result<()> {
|
||||||
let target_marker = marker("transition-target");
|
let target_marker = marker("transition-target");
|
||||||
|
|
@ -62,10 +87,12 @@ fn acknowledged_transition_precedes_exact_wrapper_release() -> std::io::Result<(
|
||||||
let acknowledged = std::cell::Cell::new(false);
|
let acknowledged = std::cell::Cell::new(false);
|
||||||
let target = TargetCommand::new("/usr/bin/touch").argument(&target_marker);
|
let target = TargetCommand::new("/usr/bin/touch").argument(&target_marker);
|
||||||
let process = child_broker().acquire_with_stdio_after_ack(
|
let process = child_broker().acquire_with_stdio_after_ack(
|
||||||
parent,
|
AcknowledgedChildAcquisition {
|
||||||
context(),
|
expected_parent: parent,
|
||||||
&target,
|
ownership: context(),
|
||||||
ChildStandardIo::inherit(),
|
target: &target,
|
||||||
|
standard_io: ChildStandardIo::inherit(),
|
||||||
|
},
|
||||||
|ownership| {
|
|ownership| {
|
||||||
staged.set(Some(ownership));
|
staged.set(Some(ownership));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "lazaret-network-sidecar"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
rust-version.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "lazaret-network-sidecar"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
fn main() {
|
|
||||||
let _ = lazaret_core::PROTOCOL_NAME;
|
|
||||||
}
|
|
||||||
|
|
@ -16,6 +16,7 @@ oci-spec.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
rustix.workspace = true
|
rustix.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
sha2.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "=3.27.0"
|
tempfile = "=3.27.0"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ use lazaret_core::records::{
|
||||||
};
|
};
|
||||||
use lazaret_linux::filesystem::{namespace_identity, require_write_sealed};
|
use lazaret_linux::filesystem::{namespace_identity, require_write_sealed};
|
||||||
use lazaret_linux::process::{
|
use lazaret_linux::process::{
|
||||||
ChildStandardIo, ProcessOwnershipContext, RetainedProcess, TargetCommand, child_broker,
|
AcknowledgedChildAcquisition, ChildStandardIo, ProcessOwnershipContext, RetainedProcess,
|
||||||
|
TargetCommand, child_broker,
|
||||||
};
|
};
|
||||||
use rustix::event::{PollFd, PollFlags, Timespec, poll};
|
use rustix::event::{PollFd, PollFlags, Timespec, poll};
|
||||||
use rustix::io::{FdFlags, fcntl_setfd, read};
|
use rustix::io::{FdFlags, fcntl_setfd, read};
|
||||||
|
|
@ -161,15 +162,17 @@ where
|
||||||
helper_ready_signal.as_raw_fd(),
|
helper_ready_signal.as_raw_fd(),
|
||||||
);
|
);
|
||||||
let helper = child_broker().acquire_with_stdio_after_ack(
|
let helper = child_broker().acquire_with_stdio_after_ack(
|
||||||
context.runtime_supervisor,
|
AcknowledgedChildAcquisition {
|
||||||
ProcessOwnershipContext {
|
expected_parent: context.runtime_supervisor,
|
||||||
|
ownership: ProcessOwnershipContext {
|
||||||
domain_id: context.domain_id,
|
domain_id: context.domain_id,
|
||||||
generation_id: context.generation_id,
|
generation_id: context.generation_id,
|
||||||
coordinator_id: context.coordinator_id,
|
coordinator_id: context.coordinator_id,
|
||||||
slot: HostProcessOwnershipSlot::HelperConnectivityHelper,
|
slot: HostProcessOwnershipSlot::HelperConnectivityHelper,
|
||||||
},
|
},
|
||||||
&target,
|
target: &target,
|
||||||
ChildStandardIo::silenced(),
|
standard_io: ChildStandardIo::silenced(),
|
||||||
|
},
|
||||||
stage,
|
stage,
|
||||||
acknowledge,
|
acknowledge,
|
||||||
enter_domain_namespaces,
|
enter_domain_namespaces,
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,16 @@ use std::os::fd::{AsFd, OwnedFd};
|
||||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
|
||||||
use lazaret_core::configuration::{ResolvedDomainConfiguration, SandboxLayoutV1};
|
use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration, SandboxLayoutV1};
|
||||||
use lazaret_core::identity::{ContainerId, NamespaceIdentity};
|
use lazaret_core::identity::{ConfigurationFingerprint, ContainerId, DomainId, NamespaceIdentity};
|
||||||
use lazaret_core::records::VolumeAccess;
|
use lazaret_core::records::VolumeAccess;
|
||||||
use oci_spec::runtime::{
|
use oci_spec::runtime::{
|
||||||
Capabilities, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxIdMappingBuilder,
|
Capabilities, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxIdMappingBuilder,
|
||||||
LinuxNamespaceBuilder, LinuxNamespaceType, Mount, MountBuilder, ProcessBuilder, RootBuilder,
|
LinuxNamespaceBuilder, LinuxNamespaceType, Mount, MountBuilder, ProcessBuilder, RootBuilder,
|
||||||
Spec, SpecBuilder, UserBuilder,
|
Spec, SpecBuilder, UserBuilder,
|
||||||
};
|
};
|
||||||
use rustix::fs::{AtFlags, FileType, Mode, OFlags, ResolveFlags, openat2};
|
use rustix::fs::{AtFlags, FileType, Mode, OFlags, ResolveFlags, StatxFlags, openat2, statx};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
pub const WORKSPACE_DESTINATION: &str = "/workspace";
|
pub const WORKSPACE_DESTINATION: &str = "/workspace";
|
||||||
pub const CONTROL_ROOT_DESTINATION: &str = "/run/lazaret";
|
pub const CONTROL_ROOT_DESTINATION: &str = "/run/lazaret";
|
||||||
|
|
@ -122,11 +123,20 @@ impl RootProcess {
|
||||||
struct FileIdentity {
|
struct FileIdentity {
|
||||||
device: u64,
|
device: u64,
|
||||||
inode: u64,
|
inode: u64,
|
||||||
|
mount_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileIdentity {
|
impl FileIdentity {
|
||||||
fn from_descriptor(descriptor: &OwnedFd) -> Result<Self, SandboxBuildError> {
|
fn from_descriptor(descriptor: &OwnedFd) -> Result<Self, SandboxBuildError> {
|
||||||
let stat = rustix::fs::fstat(descriptor).map_err(staging_error)?;
|
let stat = rustix::fs::fstat(descriptor).map_err(staging_error)?;
|
||||||
|
let extended = statx(descriptor, "", AtFlags::EMPTY_PATH, StatxFlags::MNT_ID)
|
||||||
|
.map_err(staging_error)?;
|
||||||
|
if !StatxFlags::from_bits_retain(extended.stx_mask).contains(StatxFlags::MNT_ID) {
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"host filesystem does not report mount identity",
|
||||||
|
));
|
||||||
|
}
|
||||||
if !FileType::from_raw_mode(stat.st_mode).is_file() {
|
if !FileType::from_raw_mode(stat.st_mode).is_file() {
|
||||||
return Err(SandboxBuildError::new(
|
return Err(SandboxBuildError::new(
|
||||||
SandboxBuildErrorKind::ChangedAuthority,
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
|
@ -136,10 +146,142 @@ impl FileIdentity {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
device: stat.st_dev,
|
device: stat.st_dev,
|
||||||
inode: stat.st_ino,
|
inode: stat.st_ino,
|
||||||
|
mount_id: extended.stx_mnt_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
enum HandoffPathType {
|
||||||
|
Directory,
|
||||||
|
RegularFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct DerivedHandoffAuthority {
|
||||||
|
relative_path: PathBuf,
|
||||||
|
expected_type: HandoffPathType,
|
||||||
|
identity: FileIdentity,
|
||||||
|
expected_bytes: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct RuntimeHandoffSource {
|
||||||
|
relative_path: PathBuf,
|
||||||
|
expected_type: HandoffPathType,
|
||||||
|
build_identity: Option<FileIdentity>,
|
||||||
|
expected_bytes: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
struct PathAuthorityIdentity {
|
||||||
|
device: u64,
|
||||||
|
inode: u64,
|
||||||
|
mount_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathAuthorityIdentity {
|
||||||
|
fn from_plan(plan: &ConfinedPathPlan) -> Self {
|
||||||
|
Self {
|
||||||
|
device: plan.target_device().get(),
|
||||||
|
inode: plan.target_inode().get(),
|
||||||
|
mount_id: plan.target_mount_id().get(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct ConfigurationAuthorityIdentity {
|
||||||
|
workspace: PathAuthorityIdentity,
|
||||||
|
runtime_domain: PathAuthorityIdentity,
|
||||||
|
volumes: Vec<PathAuthorityIdentity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigurationAuthorityIdentity {
|
||||||
|
fn capture(configuration: &ResolvedDomainConfiguration) -> Self {
|
||||||
|
Self {
|
||||||
|
workspace: PathAuthorityIdentity::from_plan(configuration.workspace()),
|
||||||
|
runtime_domain: PathAuthorityIdentity::from_plan(configuration.runtime_domain_root()),
|
||||||
|
volumes: configuration
|
||||||
|
.volumes()
|
||||||
|
.iter()
|
||||||
|
.map(|volume| PathAuthorityIdentity::from_plan(volume.source_plan()))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SandboxHandoffAuthority<'configuration> {
|
||||||
|
configuration: &'configuration ResolvedDomainConfiguration,
|
||||||
|
domain_id: DomainId,
|
||||||
|
configuration_fingerprint: ConfigurationFingerprint,
|
||||||
|
configuration_authority: ConfigurationAuthorityIdentity,
|
||||||
|
command: RunscCommand,
|
||||||
|
derived: Vec<DerivedHandoffAuthority>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SandboxHandoffAuthority<'_> {
|
||||||
|
/// Revalidates every pathname source immediately before attached runsc is released.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Refuses changed workspace, volume, Runtime Domain, bundle, rootfs, control, or terminal
|
||||||
|
/// authority.
|
||||||
|
pub fn revalidate(&self) -> Result<(), SandboxBuildError> {
|
||||||
|
if self.configuration.domain_id() != self.domain_id
|
||||||
|
|| self.configuration.configuration_fingerprint() != self.configuration_fingerprint
|
||||||
|
|| ConfigurationAuthorityIdentity::capture(self.configuration)
|
||||||
|
!= self.configuration_authority
|
||||||
|
{
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"sandbox handoff configuration does not match its bundle",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.configuration
|
||||||
|
.workspace()
|
||||||
|
.open_authority()
|
||||||
|
.map_err(changed_configuration_authority)?;
|
||||||
|
for volume in self.configuration.volumes() {
|
||||||
|
volume
|
||||||
|
.open_source()
|
||||||
|
.map_err(changed_configuration_authority)?;
|
||||||
|
}
|
||||||
|
let runtime_root = self
|
||||||
|
.configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.open_authority()
|
||||||
|
.map_err(changed_configuration_authority)?;
|
||||||
|
for authority in &self.derived {
|
||||||
|
let descriptor = open_runtime_source(
|
||||||
|
&runtime_root,
|
||||||
|
&authority.relative_path,
|
||||||
|
authority.expected_type,
|
||||||
|
authority.expected_bytes.is_some(),
|
||||||
|
)?;
|
||||||
|
if descriptor_identity(&descriptor)? != authority.identity {
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"runtime handoff source changed identity",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(expected) = &authority.expected_bytes {
|
||||||
|
require_exact_bytes(descriptor, expected)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn domain_id(&self) -> DomainId {
|
||||||
|
self.domain_id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn matches_command(&self, command: &RunscCommand) -> bool {
|
||||||
|
self.command == *command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct HostTerminal {
|
pub struct HostTerminal {
|
||||||
term: String,
|
term: String,
|
||||||
|
|
@ -279,9 +421,22 @@ pub struct SandboxBuildRequest<'a> {
|
||||||
pub struct RunscCommand {
|
pub struct RunscCommand {
|
||||||
program: OsString,
|
program: OsString,
|
||||||
arguments: Vec<OsString>,
|
arguments: Vec<OsString>,
|
||||||
|
bundle_authorization: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunscCommand {
|
impl RunscCommand {
|
||||||
|
pub(crate) fn new(
|
||||||
|
program: impl Into<OsString>,
|
||||||
|
arguments: impl IntoIterator<Item = OsString>,
|
||||||
|
bundle_authorization: [u8; 32],
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
program: program.into(),
|
||||||
|
arguments: arguments.into_iter().collect(),
|
||||||
|
bundle_authorization,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn program(&self) -> &OsStr {
|
pub fn program(&self) -> &OsStr {
|
||||||
&self.program
|
&self.program
|
||||||
|
|
@ -298,6 +453,10 @@ pub struct SandboxBundle {
|
||||||
specification: Spec,
|
specification: Spec,
|
||||||
specification_json: Vec<u8>,
|
specification_json: Vec<u8>,
|
||||||
command: RunscCommand,
|
command: RunscCommand,
|
||||||
|
domain_id: DomainId,
|
||||||
|
configuration_fingerprint: ConfigurationFingerprint,
|
||||||
|
configuration_authority: ConfigurationAuthorityIdentity,
|
||||||
|
runtime_sources: Vec<RuntimeHandoffSource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SandboxBundle {
|
impl SandboxBundle {
|
||||||
|
|
@ -315,6 +474,78 @@ impl SandboxBundle {
|
||||||
pub const fn command(&self) -> &RunscCommand {
|
pub const fn command(&self) -> &RunscCommand {
|
||||||
&self.command
|
&self.command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Captures the exact staged sources that must survive until attached runsc release.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Refuses absent, changed, symlinked, or incorrectly typed handoff sources.
|
||||||
|
pub fn capture_handoff_authority<'configuration>(
|
||||||
|
&self,
|
||||||
|
configuration: &'configuration ResolvedDomainConfiguration,
|
||||||
|
) -> Result<SandboxHandoffAuthority<'configuration>, SandboxBuildError> {
|
||||||
|
if configuration.domain_id() != self.domain_id
|
||||||
|
|| configuration.configuration_fingerprint() != self.configuration_fingerprint
|
||||||
|
|| ConfigurationAuthorityIdentity::capture(configuration)
|
||||||
|
!= self.configuration_authority
|
||||||
|
{
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"sandbox handoff configuration does not match its bundle",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
configuration
|
||||||
|
.workspace()
|
||||||
|
.open_authority()
|
||||||
|
.map_err(changed_configuration_authority)?;
|
||||||
|
for volume in configuration.volumes() {
|
||||||
|
volume
|
||||||
|
.open_source()
|
||||||
|
.map_err(changed_configuration_authority)?;
|
||||||
|
}
|
||||||
|
let runtime_root = configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.open_authority()
|
||||||
|
.map_err(changed_configuration_authority)?;
|
||||||
|
let derived = self
|
||||||
|
.runtime_sources
|
||||||
|
.iter()
|
||||||
|
.map(|source| {
|
||||||
|
let descriptor = open_runtime_source(
|
||||||
|
&runtime_root,
|
||||||
|
&source.relative_path,
|
||||||
|
source.expected_type,
|
||||||
|
source.expected_bytes.is_some(),
|
||||||
|
)?;
|
||||||
|
let identity = descriptor_identity(&descriptor)?;
|
||||||
|
if source
|
||||||
|
.build_identity
|
||||||
|
.is_some_and(|expected| expected != identity)
|
||||||
|
{
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"staged handoff source changed identity after bundle construction",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(expected) = &source.expected_bytes {
|
||||||
|
require_exact_bytes(descriptor, expected)?;
|
||||||
|
}
|
||||||
|
Ok(DerivedHandoffAuthority {
|
||||||
|
relative_path: source.relative_path.clone(),
|
||||||
|
expected_type: source.expected_type,
|
||||||
|
identity,
|
||||||
|
expected_bytes: source.expected_bytes.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Result<_, SandboxBuildError>>()?;
|
||||||
|
Ok(SandboxHandoffAuthority {
|
||||||
|
configuration,
|
||||||
|
domain_id: self.domain_id,
|
||||||
|
configuration_fingerprint: self.configuration_fingerprint,
|
||||||
|
configuration_authority: self.configuration_authority.clone(),
|
||||||
|
command: self.command.clone(),
|
||||||
|
derived,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the complete protected destination roots used by bundle construction.
|
/// Returns the complete protected destination roots used by bundle construction.
|
||||||
|
|
@ -371,9 +602,8 @@ pub fn build_sandbox_bundle(
|
||||||
request.terminal.as_ref(),
|
request.terminal.as_ref(),
|
||||||
)?;
|
)?;
|
||||||
let linux = build_linux()?;
|
let linux = build_linux()?;
|
||||||
let command = build_runsc_command(&paths);
|
|
||||||
let root = RootBuilder::default()
|
let root = RootBuilder::default()
|
||||||
.path(paths.rootfs)
|
.path(paths.rootfs.clone())
|
||||||
.readonly(false)
|
.readonly(false)
|
||||||
.build()
|
.build()
|
||||||
.map_err(oci_error)?;
|
.map_err(oci_error)?;
|
||||||
|
|
@ -389,10 +619,18 @@ pub fn build_sandbox_bundle(
|
||||||
let specification_json = serde_json::to_vec(&specification).map_err(|error| {
|
let specification_json = serde_json::to_vec(&specification).map_err(|error| {
|
||||||
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
SandboxBuildError::new(SandboxBuildErrorKind::JsonEncoding, error.to_string())
|
||||||
})?;
|
})?;
|
||||||
|
let bundle_authorization = Sha256::digest(&specification_json).into();
|
||||||
|
let command = build_runsc_command(&paths, bundle_authorization);
|
||||||
|
let runtime_sources =
|
||||||
|
handoff_runtime_sources(&paths, request.terminal.as_ref(), &specification_json);
|
||||||
Ok(SandboxBundle {
|
Ok(SandboxBundle {
|
||||||
specification,
|
specification,
|
||||||
specification_json,
|
specification_json,
|
||||||
command,
|
command,
|
||||||
|
domain_id: request.configuration.domain_id(),
|
||||||
|
configuration_fingerprint: request.configuration.configuration_fingerprint(),
|
||||||
|
configuration_authority: ConfigurationAuthorityIdentity::capture(request.configuration),
|
||||||
|
runtime_sources,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,6 +642,143 @@ struct RuntimePaths {
|
||||||
control_sources: PathBuf,
|
control_sources: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handoff_runtime_sources(
|
||||||
|
paths: &RuntimePaths,
|
||||||
|
terminal: Option<&StagedHostTerminal>,
|
||||||
|
specification_json: &[u8],
|
||||||
|
) -> Vec<RuntimeHandoffSource> {
|
||||||
|
let container = Path::new("sandbox-control").join(&paths.container_name);
|
||||||
|
let mut sources = [
|
||||||
|
(
|
||||||
|
Path::new("bundles").join(&paths.container_name),
|
||||||
|
HandoffPathType::Directory,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Path::new("bundles")
|
||||||
|
.join(&paths.container_name)
|
||||||
|
.join("config.json"),
|
||||||
|
HandoffPathType::RegularFile,
|
||||||
|
),
|
||||||
|
(PathBuf::from("runsc"), HandoffPathType::Directory),
|
||||||
|
(
|
||||||
|
PathBuf::from("state-anchor/merged"),
|
||||||
|
HandoffPathType::Directory,
|
||||||
|
),
|
||||||
|
(container.clone(), HandoffPathType::Directory),
|
||||||
|
(container.join("credentials"), HandoffPathType::Directory),
|
||||||
|
(
|
||||||
|
container.join("mailbox/requests"),
|
||||||
|
HandoffPathType::Directory,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
container.join("mailbox/results"),
|
||||||
|
HandoffPathType::Directory,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
container.join("bin/lazaret-escalate"),
|
||||||
|
HandoffPathType::RegularFile,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(|(relative_path, expected_type)| {
|
||||||
|
let expected_bytes = (relative_path
|
||||||
|
== Path::new("bundles")
|
||||||
|
.join(&paths.container_name)
|
||||||
|
.join("config.json"))
|
||||||
|
.then(|| specification_json.to_vec());
|
||||||
|
RuntimeHandoffSource {
|
||||||
|
relative_path,
|
||||||
|
expected_type,
|
||||||
|
build_identity: None,
|
||||||
|
expected_bytes,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if let Some(terminal) = terminal {
|
||||||
|
sources.push(RuntimeHandoffSource {
|
||||||
|
relative_path: terminal.relative_source.clone(),
|
||||||
|
expected_type: HandoffPathType::RegularFile,
|
||||||
|
build_identity: Some(terminal.identity),
|
||||||
|
expected_bytes: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sources
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_runtime_source(
|
||||||
|
runtime_root: &OwnedFd,
|
||||||
|
relative_path: &Path,
|
||||||
|
expected_type: HandoffPathType,
|
||||||
|
readable: bool,
|
||||||
|
) -> Result<OwnedFd, SandboxBuildError> {
|
||||||
|
let descriptor = openat2(
|
||||||
|
runtime_root,
|
||||||
|
relative_path,
|
||||||
|
if readable {
|
||||||
|
OFlags::RDONLY | OFlags::CLOEXEC | OFlags::NOFOLLOW
|
||||||
|
} else {
|
||||||
|
OFlags::PATH | OFlags::CLOEXEC | OFlags::NOFOLLOW
|
||||||
|
},
|
||||||
|
Mode::empty(),
|
||||||
|
ResolveFlags::BENEATH | ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
|
||||||
|
)
|
||||||
|
.map_err(staging_error)?;
|
||||||
|
let stat = rustix::fs::fstat(&descriptor).map_err(staging_error)?;
|
||||||
|
let actual = FileType::from_raw_mode(stat.st_mode);
|
||||||
|
let matches = match expected_type {
|
||||||
|
HandoffPathType::Directory => actual.is_dir(),
|
||||||
|
HandoffPathType::RegularFile => actual.is_file(),
|
||||||
|
};
|
||||||
|
if !matches {
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"runtime handoff source changed type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn require_exact_bytes(descriptor: OwnedFd, expected: &[u8]) -> Result<(), SandboxBuildError> {
|
||||||
|
let limit = u64::try_from(expected.len())
|
||||||
|
.ok()
|
||||||
|
.and_then(|length| length.checked_add(1))
|
||||||
|
.ok_or_else(|| {
|
||||||
|
SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"staged OCI configuration length is invalid",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let mut observed = Vec::new();
|
||||||
|
File::from(descriptor)
|
||||||
|
.take(limit)
|
||||||
|
.read_to_end(&mut observed)
|
||||||
|
.map_err(staging_error)?;
|
||||||
|
if observed != expected {
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"staged OCI configuration differs from its bundle",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn descriptor_identity(descriptor: &OwnedFd) -> Result<FileIdentity, SandboxBuildError> {
|
||||||
|
let stat = rustix::fs::fstat(descriptor).map_err(staging_error)?;
|
||||||
|
let extended =
|
||||||
|
statx(descriptor, "", AtFlags::EMPTY_PATH, StatxFlags::MNT_ID).map_err(staging_error)?;
|
||||||
|
if !StatxFlags::from_bits_retain(extended.stx_mask).contains(StatxFlags::MNT_ID) {
|
||||||
|
return Err(SandboxBuildError::new(
|
||||||
|
SandboxBuildErrorKind::ChangedAuthority,
|
||||||
|
"host filesystem does not report mount identity",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(FileIdentity {
|
||||||
|
device: stat.st_dev,
|
||||||
|
inode: stat.st_ino,
|
||||||
|
mount_id: extended.stx_mnt_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn runtime_paths(
|
fn runtime_paths(
|
||||||
configuration: &ResolvedDomainConfiguration,
|
configuration: &ResolvedDomainConfiguration,
|
||||||
container_id: ContainerId,
|
container_id: ContainerId,
|
||||||
|
|
@ -491,10 +866,10 @@ fn build_linux() -> Result<oci_spec::runtime::Linux, SandboxBuildError> {
|
||||||
.map_err(oci_error)
|
.map_err(oci_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_runsc_command(paths: &RuntimePaths) -> RunscCommand {
|
fn build_runsc_command(paths: &RuntimePaths, bundle_authorization: [u8; 32]) -> RunscCommand {
|
||||||
RunscCommand {
|
RunscCommand::new(
|
||||||
program: OsString::from("runsc"),
|
"runsc",
|
||||||
arguments: vec![
|
[
|
||||||
OsString::from(format!(
|
OsString::from(format!(
|
||||||
"-root={}",
|
"-root={}",
|
||||||
paths
|
paths
|
||||||
|
|
@ -515,7 +890,8 @@ fn build_runsc_command(paths: &RuntimePaths) -> RunscCommand {
|
||||||
)),
|
)),
|
||||||
OsString::from(&paths.container_name),
|
OsString::from(&paths.container_name),
|
||||||
],
|
],
|
||||||
}
|
bundle_authorization,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_process(
|
fn build_process(
|
||||||
|
|
|
||||||
|
|
@ -162,10 +162,12 @@ impl CoordinatorChannel {
|
||||||
pub struct RuntimeSupervisorLaunch {
|
pub struct RuntimeSupervisorLaunch {
|
||||||
target: TargetCommand,
|
target: TargetCommand,
|
||||||
ownership: ProcessOwnershipContext,
|
ownership: ProcessOwnershipContext,
|
||||||
_policy_file: Option<File>,
|
policy_file: Option<File>,
|
||||||
network_sidecar_id: Option<NetworkSidecarId>,
|
network_sidecar_id: Option<NetworkSidecarId>,
|
||||||
|
runsc_command: RunscCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
pub struct RuntimeNetworkLaunch<'policy> {
|
pub struct RuntimeNetworkLaunch<'policy> {
|
||||||
pub policy: &'policy CanonicalDerivedNetworkPolicy,
|
pub policy: &'policy CanonicalDerivedNetworkPolicy,
|
||||||
pub configuration_fingerprint: ConfigurationFingerprint,
|
pub configuration_fingerprint: ConfigurationFingerprint,
|
||||||
|
|
@ -210,8 +212,9 @@ impl RuntimeSupervisorLaunch {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
target: target.with_lifetime_pipe_parent_edge(),
|
target: target.with_lifetime_pipe_parent_edge(),
|
||||||
ownership,
|
ownership,
|
||||||
_policy_file: None,
|
policy_file: None,
|
||||||
network_sidecar_id: None,
|
network_sidecar_id: None,
|
||||||
|
runsc_command: command.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,7 +259,7 @@ impl RuntimeSupervisorLaunch {
|
||||||
}
|
}
|
||||||
launch.target = launch.target.with_lifetime_pipe_parent_edge();
|
launch.target = launch.target.with_lifetime_pipe_parent_edge();
|
||||||
drop(target);
|
drop(target);
|
||||||
launch._policy_file = Some(policy_file);
|
launch.policy_file = Some(policy_file);
|
||||||
launch.network_sidecar_id = Some(network.network_sidecar_id);
|
launch.network_sidecar_id = Some(network.network_sidecar_id);
|
||||||
Ok(launch)
|
Ok(launch)
|
||||||
}
|
}
|
||||||
|
|
@ -276,6 +279,11 @@ impl RuntimeSupervisorLaunch {
|
||||||
self.network_sidecar_id
|
self.network_sidecar_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn runsc_command(&self) -> &RunscCommand {
|
||||||
|
&self.runsc_command
|
||||||
|
}
|
||||||
|
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// Refuses a Runtime Supervisor target without the lifetime-pipe parent edge.
|
/// Refuses a Runtime Supervisor target without the lifetime-pipe parent edge.
|
||||||
pub fn child_standard_io(
|
pub fn child_standard_io(
|
||||||
|
|
@ -557,14 +565,11 @@ fn prepare_runtime_network(
|
||||||
Ok(message) => message,
|
Ok(message) => message,
|
||||||
Err(error) => return fail_network(Some(prepared), error),
|
Err(error) => return fail_network(Some(prepared), error),
|
||||||
};
|
};
|
||||||
let request = match message {
|
let CoordinatorMessage::NetworkAttestationAccepted(request) = message else {
|
||||||
CoordinatorMessage::NetworkAttestationAccepted(request) => request,
|
|
||||||
_ => {
|
|
||||||
return fail_network(
|
return fail_network(
|
||||||
Some(prepared),
|
Some(prepared),
|
||||||
io::Error::other("network attestation acceptance was not received"),
|
io::Error::other("network attestation acceptance was not received"),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let probe = match probe_current_network(&request) {
|
let probe = match probe_current_network(&request) {
|
||||||
Ok(probe) => probe,
|
Ok(probe) => probe,
|
||||||
|
|
@ -896,7 +901,12 @@ mod tests {
|
||||||
let launch = RuntimeSupervisorLaunch {
|
let launch = RuntimeSupervisorLaunch {
|
||||||
target: TargetCommand::new("/proc/self/exe").with_lifetime_pipe_parent_edge(),
|
target: TargetCommand::new("/proc/self/exe").with_lifetime_pipe_parent_edge(),
|
||||||
ownership,
|
ownership,
|
||||||
_policy_file: None,
|
policy_file: None,
|
||||||
|
runsc_command: RunscCommand::new(
|
||||||
|
"runsc",
|
||||||
|
[OsString::from("run"), OsString::from("container")],
|
||||||
|
[0; 32],
|
||||||
|
),
|
||||||
network_sidecar_id: None,
|
network_sidecar_id: None,
|
||||||
};
|
};
|
||||||
let (_coordinator_control, supervisor_control) = private_seqpacket_pair().unwrap();
|
let (_coordinator_control, supervisor_control) = private_seqpacket_pair().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use serde_json::Value;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
struct Fixture {
|
struct Fixture {
|
||||||
_tree: TempDir,
|
tree: TempDir,
|
||||||
configuration: ResolvedDomainConfiguration,
|
configuration: ResolvedDomainConfiguration,
|
||||||
terminfo_source: PathBuf,
|
terminfo_source: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +82,7 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
||||||
fs::create_dir_all(terminfo_source.parent().unwrap()).unwrap();
|
fs::create_dir_all(terminfo_source.parent().unwrap()).unwrap();
|
||||||
fs::write(&terminfo_source, compiled_terminfo("xterm-lazaret")).unwrap();
|
fs::write(&terminfo_source, compiled_terminfo("xterm-lazaret")).unwrap();
|
||||||
Self {
|
Self {
|
||||||
_tree: tree,
|
tree,
|
||||||
configuration,
|
configuration,
|
||||||
terminfo_source,
|
terminfo_source,
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +98,25 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
||||||
stage_host_terminal(&self.configuration, ContainerId::new([0x11; 32]), terminal).unwrap()
|
stage_host_terminal(&self.configuration, ContainerId::new([0x11; 32]), terminal).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn reload_configuration(&self) -> ResolvedDomainConfiguration {
|
||||||
|
let root = self.tree.path();
|
||||||
|
load_trusted_configuration(ConfigurationLoadRequest {
|
||||||
|
explicit_configuration_path: Some(root.join("configuration/lazaret.toml")),
|
||||||
|
selected_profile: "default".to_owned(),
|
||||||
|
workspace: root.join("workspace"),
|
||||||
|
home: root.join("home"),
|
||||||
|
xdg_config_home: None,
|
||||||
|
xdg_data_home: None,
|
||||||
|
xdg_runtime_dir: root.join("runtime"),
|
||||||
|
environment: BTreeMap::from([
|
||||||
|
("SELECTED_HOST_SECRET".to_owned(), "selected-value".into()),
|
||||||
|
("UNSELECTED_HOST_SECRET".to_owned(), "must-not-enter".into()),
|
||||||
|
]),
|
||||||
|
sandbox_layout: production_sandbox_layout().unwrap(),
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn staged_terminfo_source(&self) -> PathBuf {
|
fn staged_terminfo_source(&self) -> PathBuf {
|
||||||
PathBuf::from(
|
PathBuf::from(
|
||||||
String::from_utf8(
|
String::from_utf8(
|
||||||
|
|
@ -123,6 +142,53 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
||||||
terminal: Some(self.terminal()),
|
terminal: Some(self.terminal()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stage_handoff(&self, bundle: &lazaret_runtime_supervisor::sandbox::SandboxBundle) {
|
||||||
|
let runtime = host_path(
|
||||||
|
self.configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
let container = "11".repeat(32);
|
||||||
|
for directory in [
|
||||||
|
format!("bundles/{container}"),
|
||||||
|
"runsc".to_owned(),
|
||||||
|
"state-anchor/merged".to_owned(),
|
||||||
|
format!("sandbox-control/{container}/credentials"),
|
||||||
|
format!("sandbox-control/{container}/mailbox/requests"),
|
||||||
|
format!("sandbox-control/{container}/mailbox/results"),
|
||||||
|
format!("sandbox-control/{container}/bin"),
|
||||||
|
] {
|
||||||
|
fs::create_dir_all(runtime.join(directory)).unwrap();
|
||||||
|
}
|
||||||
|
fs::write(
|
||||||
|
runtime.join(format!("bundles/{container}/config.json")),
|
||||||
|
bundle.specification_json(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
fs::write(
|
||||||
|
runtime.join(format!("sandbox-control/{container}/bin/lazaret-escalate")),
|
||||||
|
b"executable",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bundle_config_path(&self) -> PathBuf {
|
||||||
|
host_path(
|
||||||
|
self.configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.join("bundles")
|
||||||
|
.join("11".repeat(32))
|
||||||
|
.join("config.json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn host_path(bytes: &[u8]) -> PathBuf {
|
||||||
|
PathBuf::from(OsStr::from_bytes(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compiled_terminfo(term: &str) -> Vec<u8> {
|
fn compiled_terminfo(term: &str) -> Vec<u8> {
|
||||||
|
|
@ -473,3 +539,157 @@ fn production_layout_reserves_every_mount_root() {
|
||||||
);
|
);
|
||||||
assert_eq!(TERMINFO_ROOT_DESTINATION, "/run/lazaret/terminfo");
|
assert_eq!(TERMINFO_ROOT_DESTINATION, "/run/lazaret/terminfo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn final_handoff_refuses_replaced_workspace_and_runtime_sources() {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let bundle = build_sandbox_bundle(fixture.request(root_process([]))).unwrap();
|
||||||
|
fixture.stage_handoff(&bundle);
|
||||||
|
let handoff = bundle
|
||||||
|
.capture_handoff_authority(&fixture.configuration)
|
||||||
|
.unwrap();
|
||||||
|
let workspace = host_path(
|
||||||
|
fixture
|
||||||
|
.configuration
|
||||||
|
.workspace()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
fs::rename(&workspace, workspace.with_extension("selected")).unwrap();
|
||||||
|
fs::create_dir(&workspace).unwrap();
|
||||||
|
fs::write(workspace.join("replacement-marker"), b"unchanged").unwrap();
|
||||||
|
let error = handoff.revalidate().unwrap_err();
|
||||||
|
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read(workspace.join("replacement-marker")).unwrap(),
|
||||||
|
b"unchanged"
|
||||||
|
);
|
||||||
|
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let bundle = build_sandbox_bundle(fixture.request(root_process([]))).unwrap();
|
||||||
|
fixture.stage_handoff(&bundle);
|
||||||
|
let handoff = bundle
|
||||||
|
.capture_handoff_authority(&fixture.configuration)
|
||||||
|
.unwrap();
|
||||||
|
let runtime = host_path(
|
||||||
|
fixture
|
||||||
|
.configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
);
|
||||||
|
let rootfs = runtime.join("state-anchor/merged");
|
||||||
|
fs::rename(&rootfs, runtime.join("state-anchor/selected")).unwrap();
|
||||||
|
fs::create_dir(&rootfs).unwrap();
|
||||||
|
fs::write(rootfs.join("replacement-marker"), b"unchanged").unwrap();
|
||||||
|
let error = handoff.revalidate().unwrap_err();
|
||||||
|
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read(rootfs.join("replacement-marker")).unwrap(),
|
||||||
|
b"unchanged"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reloaded_same_label_configuration_cannot_replace_bundle_authority() {
|
||||||
|
for selected in ["workspace", "volume", "runtime-domain"] {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let bundle = build_sandbox_bundle(fixture.request(root_process([]))).unwrap();
|
||||||
|
fixture.stage_handoff(&bundle);
|
||||||
|
let path = match selected {
|
||||||
|
"workspace" => host_path(
|
||||||
|
fixture
|
||||||
|
.configuration
|
||||||
|
.workspace()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
),
|
||||||
|
"volume" => host_path(
|
||||||
|
fixture.configuration.volumes()[0]
|
||||||
|
.record()
|
||||||
|
.canonical_host_source
|
||||||
|
.as_bytes(),
|
||||||
|
),
|
||||||
|
"runtime-domain" => host_path(
|
||||||
|
fixture
|
||||||
|
.configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let selected_path = path.with_extension(format!("{selected}-selected"));
|
||||||
|
fs::rename(&path, &selected_path).unwrap();
|
||||||
|
fs::create_dir(&path).unwrap();
|
||||||
|
fs::set_permissions(&path, std::os::unix::fs::PermissionsExt::from_mode(0o700)).unwrap();
|
||||||
|
let marker = path.join("replacement-marker");
|
||||||
|
fs::write(&marker, b"unchanged").unwrap();
|
||||||
|
let reloaded = fixture.reload_configuration();
|
||||||
|
let Err(error) = bundle.capture_handoff_authority(&reloaded) else {
|
||||||
|
panic!("reloaded {selected} replacement was accepted");
|
||||||
|
};
|
||||||
|
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||||
|
assert_eq!(fs::read(marker).unwrap(), b"unchanged");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn post_build_terminfo_replacement_cannot_become_handoff_authority() {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let bundle = build_sandbox_bundle(fixture.request(root_process([]))).unwrap();
|
||||||
|
let terminfo = fixture.staged_terminfo_source();
|
||||||
|
fs::rename(&terminfo, terminfo.with_extension("selected")).unwrap();
|
||||||
|
fs::write(&terminfo, b"replacement remains unchanged").unwrap();
|
||||||
|
fixture.stage_handoff(&bundle);
|
||||||
|
let Err(error) = bundle.capture_handoff_authority(&fixture.configuration) else {
|
||||||
|
panic!("post-build terminfo replacement was accepted");
|
||||||
|
};
|
||||||
|
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||||
|
assert_eq!(
|
||||||
|
fs::read(terminfo).unwrap(),
|
||||||
|
b"replacement remains unchanged"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bundle_authorization_distinguishes_equal_runsc_argv() {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let first = build_sandbox_bundle(fixture.request(root_process(["first".to_owned()]))).unwrap();
|
||||||
|
let second =
|
||||||
|
build_sandbox_bundle(fixture.request(root_process(["second".to_owned()]))).unwrap();
|
||||||
|
assert_eq!(first.command().program(), second.command().program());
|
||||||
|
assert_eq!(first.command().arguments(), second.command().arguments());
|
||||||
|
assert_ne!(first.command(), second.command());
|
||||||
|
fixture.stage_handoff(&first);
|
||||||
|
let handoff = first
|
||||||
|
.capture_handoff_authority(&fixture.configuration)
|
||||||
|
.unwrap();
|
||||||
|
assert!(!handoff.matches_command(second.command()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn staged_oci_configuration_must_match_bundle_at_capture_and_release() {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let bundle = build_sandbox_bundle(fixture.request(root_process([]))).unwrap();
|
||||||
|
fixture.stage_handoff(&bundle);
|
||||||
|
let config = fixture.bundle_config_path();
|
||||||
|
fs::write(&config, b"pre-capture replacement").unwrap();
|
||||||
|
let Err(error) = bundle.capture_handoff_authority(&fixture.configuration) else {
|
||||||
|
panic!("mutated pre-capture OCI configuration was accepted");
|
||||||
|
};
|
||||||
|
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||||
|
assert_eq!(fs::read(&config).unwrap(), b"pre-capture replacement");
|
||||||
|
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let bundle = build_sandbox_bundle(fixture.request(root_process([]))).unwrap();
|
||||||
|
fixture.stage_handoff(&bundle);
|
||||||
|
let handoff = bundle
|
||||||
|
.capture_handoff_authority(&fixture.configuration)
|
||||||
|
.unwrap();
|
||||||
|
let config = fixture.bundle_config_path();
|
||||||
|
fs::write(&config, b"post-capture replacement").unwrap();
|
||||||
|
let error = handoff.revalidate().unwrap_err();
|
||||||
|
assert_eq!(error.kind(), SandboxBuildErrorKind::ChangedAuthority);
|
||||||
|
assert_eq!(fs::read(config).unwrap(), b"post-capture replacement");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.90.0"
|
channel = "1.97.0"
|
||||||
components = ["clippy", "rustfmt"]
|
components = ["clippy", "rustfmt"]
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
|
|
|
||||||
|
|
@ -202,9 +202,11 @@ class VerificationTests(unittest.TestCase):
|
||||||
"lazaret",
|
"lazaret",
|
||||||
"lazaret-escalation-client",
|
"lazaret-escalation-client",
|
||||||
"lazaret-host-coordinator",
|
"lazaret-host-coordinator",
|
||||||
|
"lazaret-git-sentry",
|
||||||
|
"lazaret-ignored-workspace",
|
||||||
"lazaret-launch-wrapper",
|
"lazaret-launch-wrapper",
|
||||||
"lazaret-network-sidecar",
|
|
||||||
"lazaret-runtime-supervisor",
|
"lazaret-runtime-supervisor",
|
||||||
|
"lazaret-workspace-benchmark",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -238,8 +240,8 @@ class VerificationTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_tool_versions_are_exactly_pinned(self) -> None:
|
def test_tool_versions_are_exactly_pinned(self) -> None:
|
||||||
expected = {
|
expected = {
|
||||||
"rustc": r"^rustc 1\.90\.0 ",
|
"rustc": r"^rustc 1\.97\.0 ",
|
||||||
"cargo": r"^cargo 1\.90\.0 ",
|
"cargo": r"^cargo 1\.97\.0 ",
|
||||||
"cargo-nextest": r"^cargo-nextest 0\.9\.140 ",
|
"cargo-nextest": r"^cargo-nextest 0\.9\.140 ",
|
||||||
"cargo-deny": r"^cargo-deny 0\.20\.2$",
|
"cargo-deny": r"^cargo-deny 0\.20\.2$",
|
||||||
"tlc": r"Version 2\.19 ",
|
"tlc": r"Version 2\.19 ",
|
||||||
|
|
@ -273,7 +275,7 @@ class VerificationTests(unittest.TestCase):
|
||||||
"--filterset",
|
"--filterset",
|
||||||
(
|
(
|
||||||
"not binary(capability-drop-helper) and not binary(acquisition) "
|
"not binary(capability-drop-helper) and not binary(acquisition) "
|
||||||
"and not binary(runtime-composition)"
|
"and not binary(runtime-composition) and not binary(network-runtime)"
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|
@ -306,6 +308,16 @@ class VerificationTests(unittest.TestCase):
|
||||||
"runtime-composition",
|
"runtime-composition",
|
||||||
"--locked",
|
"--locked",
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"cargo",
|
||||||
|
"test",
|
||||||
|
"--offline",
|
||||||
|
"-p",
|
||||||
|
"lazaret-runtime-supervisor",
|
||||||
|
"--test",
|
||||||
|
"network-runtime",
|
||||||
|
"--locked",
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"cargo",
|
"cargo",
|
||||||
"test",
|
"test",
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ NEXTEST_COMMAND = (
|
||||||
"--filterset",
|
"--filterset",
|
||||||
(
|
(
|
||||||
"not binary(capability-drop-helper) and not binary(acquisition) "
|
"not binary(capability-drop-helper) and not binary(acquisition) "
|
||||||
"and not binary(runtime-composition)"
|
"and not binary(runtime-composition) and not binary(network-runtime)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
DOCTEST_COMMAND = (
|
DOCTEST_COMMAND = (
|
||||||
|
|
@ -71,9 +71,19 @@ RUNTIME_COMPOSITION_TEST_COMMAND = (
|
||||||
"runtime-composition",
|
"runtime-composition",
|
||||||
"--locked",
|
"--locked",
|
||||||
)
|
)
|
||||||
|
NETWORK_RUNTIME_TEST_COMMAND = (
|
||||||
|
"cargo",
|
||||||
|
"test",
|
||||||
|
"--offline",
|
||||||
|
"-p",
|
||||||
|
"lazaret-runtime-supervisor",
|
||||||
|
"--test",
|
||||||
|
"network-runtime",
|
||||||
|
"--locked",
|
||||||
|
)
|
||||||
REQUIRED_TOOL_VERSIONS = (
|
REQUIRED_TOOL_VERSIONS = (
|
||||||
("rustc", ("--version",), (0,), re.compile(r"^rustc 1\.90\.0 ")),
|
("rustc", ("--version",), (0,), re.compile(r"^rustc 1\.97\.0 ")),
|
||||||
("cargo", ("--version",), (0,), re.compile(r"^cargo 1\.90\.0 ")),
|
("cargo", ("--version",), (0,), re.compile(r"^cargo 1\.97\.0 ")),
|
||||||
(
|
(
|
||||||
"cargo-nextest",
|
"cargo-nextest",
|
||||||
("--version",),
|
("--version",),
|
||||||
|
|
@ -131,6 +141,7 @@ PROCESS_LINT_ALLOWANCES = {
|
||||||
"send_signal",
|
"send_signal",
|
||||||
"acquire",
|
"acquire",
|
||||||
"acquire_with_stdio",
|
"acquire_with_stdio",
|
||||||
|
"acquire_with_stdio_after_ack",
|
||||||
"acquire_captured",
|
"acquire_captured",
|
||||||
"private_role_command",
|
"private_role_command",
|
||||||
"require_reapable_sigchld",
|
"require_reapable_sigchld",
|
||||||
|
|
@ -147,10 +158,14 @@ PROCESS_LINT_ALLOWANCES = {
|
||||||
"current_process_identity",
|
"current_process_identity",
|
||||||
"append_parent_identity",
|
"append_parent_identity",
|
||||||
"exec_target",
|
"exec_target",
|
||||||
|
"run_transient",
|
||||||
},
|
},
|
||||||
|
"crates/lazaret-host-coordinator/tests/runtime-composition.rs": {"run_benchmark_guest"},
|
||||||
"crates/lazaret-linux/tests/acquisition.rs": {
|
"crates/lazaret-linux/tests/acquisition.rs": {
|
||||||
"run_acquisition",
|
"run_acquisition",
|
||||||
"successful_acquisition",
|
"successful_acquisition",
|
||||||
|
"acknowledged_transition_precedes_exact_wrapper_release",
|
||||||
|
"failed_acknowledged_transition_refuses_exact_wrapper_release",
|
||||||
"lifetime_pipe_descriptor_contract",
|
"lifetime_pipe_descriptor_contract",
|
||||||
"run_lifetime_pipe_target",
|
"run_lifetime_pipe_target",
|
||||||
"failed_stages_and_acknowledgments",
|
"failed_stages_and_acknowledgments",
|
||||||
|
|
@ -534,6 +549,7 @@ def run_rust_tests() -> None:
|
||||||
run(list(ACQUISITION_TEST_COMMAND))
|
run(list(ACQUISITION_TEST_COMMAND))
|
||||||
run(list(CAPABILITY_TEST_COMMAND))
|
run(list(CAPABILITY_TEST_COMMAND))
|
||||||
run_physical(list(RUNTIME_COMPOSITION_TEST_COMMAND))
|
run_physical(list(RUNTIME_COMPOSITION_TEST_COMMAND))
|
||||||
|
run_physical(list(NETWORK_RUNTIME_TEST_COMMAND))
|
||||||
run(list(DOCTEST_COMMAND))
|
run(list(DOCTEST_COMMAND))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue