Confine runtime staging to retained authority

This commit is contained in:
ms2k 2026-07-16 18:53:20 +09:00
commit fc5b2b6ca0
2 changed files with 434 additions and 173 deletions

View file

@ -1,6 +1,6 @@
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt; use std::fmt;
use std::fs::{self, File, OpenOptions}; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::os::fd::{AsFd, OwnedFd}; use std::os::fd::{AsFd, OwnedFd};
use std::os::unix::ffi::OsStringExt; use std::os::unix::ffi::OsStringExt;
@ -8,7 +8,7 @@ use std::os::unix::fs::{FileExt, PermissionsExt};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use lazaret_core::configuration::ResolvedDomainConfiguration; use lazaret_core::configuration::{ConfinedPathPlan, ResolvedDomainConfiguration};
use lazaret_core::identity::{BundleDigest, ContainerId}; use lazaret_core::identity::{BundleDigest, ContainerId};
use lazaret_core::network::{ReachabilityExpectation, derive_ipv4_policy}; use lazaret_core::network::{ReachabilityExpectation, derive_ipv4_policy};
use lazaret_core::records::{ use lazaret_core::records::{
@ -24,7 +24,10 @@ use lazaret_runtime_supervisor::supervision::{RuntimeNetworkLaunch, RuntimeSuper
use lazaret_state_anchor::{StateAnchorLaunchAuthority, StateAnchorLayout}; use lazaret_state_anchor::{StateAnchorLaunchAuthority, StateAnchorLayout};
use lazaret_storage_catalog::Catalog; use lazaret_storage_catalog::Catalog;
use rustix::event::{PollFd, PollFlags, Timespec, poll}; use rustix::event::{PollFd, PollFlags, Timespec, poll};
use rustix::fs::{FileType, fstat}; use rustix::fs::{
AtFlags, FileType, Mode, OFlags, RawDir, ResolveFlags, fchmod, fstat, mkdirat, openat2, statat,
unlinkat,
};
use rustix::io::{FdFlags, fcntl_setfd}; use rustix::io::{FdFlags, fcntl_setfd};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@ -132,9 +135,10 @@ impl<'a> ProductionColdStart<'a> {
let state_anchor_layout = StateAnchorLayout::create(inputs.configuration) let state_anchor_layout = StateAnchorLayout::create(inputs.configuration)
.map_err(|error| failure(format!("State Anchor layout: {error}")))?; .map_err(|error| failure(format!("State Anchor layout: {error}")))?;
let staging = RuntimeStaging::create( let staging = RuntimeStaging::create(
&runtime_domain, inputs.configuration.runtime_domain_root(),
inputs.container_id, inputs.container_id,
inputs.escalation_client, inputs.escalation_client,
inputs.invoking_uid,
) )
.map_err(|error| failure(format!("runtime staging: {error}")))?; .map_err(|error| failure(format!("runtime staging: {error}")))?;
let expected_network_namespace = namespace_identity(Path::new(NETWORK_NAMESPACE_PATH)) let expected_network_namespace = namespace_identity(Path::new(NETWORK_NAMESPACE_PATH))
@ -265,6 +269,7 @@ fn require_inputs(inputs: &ProductionColdStartInputs<'_>) -> Result<(), Producti
|| inputs.network_probe.domain_id != inputs.generation.domain_id || inputs.network_probe.domain_id != inputs.generation.domain_id
|| inputs.network_probe.generation_id != inputs.generation.generation_id || inputs.network_probe.generation_id != inputs.generation.generation_id
|| inputs.resolver_addresses.is_empty() || inputs.resolver_addresses.is_empty()
|| inputs.invoking_uid != rustix::process::getuid().as_raw()
{ {
return Err(ProductionColdStartError( return Err(ProductionColdStartError(
"production cold-start inputs disagree".into(), "production cold-start inputs disagree".into(),
@ -274,56 +279,79 @@ fn require_inputs(inputs: &ProductionColdStartInputs<'_>) -> Result<(), Producti
} }
struct RuntimeStaging { struct RuntimeStaging {
bundle: PathBuf, bundles: OwnedFd,
control: PathBuf, bundle: OwnedFd,
controls: OwnedFd,
control: OwnedFd,
name: OsString,
bundle_identity: FileIdentity,
control_identity: FileIdentity,
} }
impl RuntimeStaging { impl RuntimeStaging {
fn create( fn create(
runtime_domain: &Path, runtime_domain: &ConfinedPathPlan,
container_id: ContainerId, container_id: ContainerId,
escalation_client: &File, escalation_client: &File,
invoking_uid: u32,
) -> Result<Self, ProductionColdStartError> { ) -> Result<Self, ProductionColdStartError> {
let name = hex(container_id.as_bytes()); let root = runtime_domain.open_authority().map_err(failure)?;
let bundles = runtime_domain.join("bundles"); let bundles = open_or_create_private_directory(&root, "bundles", invoking_uid, false)?;
let controls = runtime_domain.join("sandbox-control"); let controls =
let runsc = runtime_domain.join("runsc"); open_or_create_private_directory(&root, "sandbox-control", invoking_uid, false)?;
for directory in [&bundles, &controls, &runsc] { drop(open_or_create_private_directory(
ensure_directory(directory) &root,
.map_err(|error| failure(format!("creating {}: {error}", directory.display())))?; "runsc",
} invoking_uid,
let bundle = bundles.join(&name); false,
let control = controls.join(name); )?);
fs::create_dir(&bundle) let name = OsString::from(hex(container_id.as_bytes()));
.map_err(|error| failure(format!("creating bundle leaf: {error}")))?; let bundle = open_or_create_private_directory(&bundles, &name, invoking_uid, true)?;
set_private_directory(&bundle)?; let bundle_identity = FileIdentity::capture(&bundle)?;
if let Err(error) = fs::create_dir(&control) { let control = match open_or_create_private_directory(&controls, &name, invoking_uid, true) {
let _ = fs::remove_dir(&bundle); Ok(control) => control,
return Err(failure(error)); Err(error) => {
} remove_staging_directory(&bundles, &name, &bundle, bundle_identity);
if let Err(error) = set_private_directory(&control) { return Err(error);
let _ = fs::remove_dir(&control); }
let _ = fs::remove_dir(&bundle); };
return Err(error); let control_identity = FileIdentity::capture(&control)?;
} let staging = Self {
let staging = Self { bundle, control }; bundles,
bundle,
controls,
control,
name,
bundle_identity,
control_identity,
};
staging.populate_control(escalation_client)?; staging.populate_control(escalation_client)?;
Ok(staging) Ok(staging)
} }
fn populate_control(&self, escalation_client: &File) -> Result<(), ProductionColdStartError> { fn populate_control(&self, escalation_client: &File) -> Result<(), ProductionColdStartError> {
for relative in [ let invoking_uid = fstat(&self.control).map_err(failure)?.st_uid;
drop(open_or_create_private_directory(
&self.control,
"credentials", "credentials",
"mailbox", invoking_uid,
"mailbox/requests", true,
"mailbox/results", )?);
"bin", let mailbox =
] { open_or_create_private_directory(&self.control, "mailbox", invoking_uid, true)?;
let directory = self.control.join(relative); drop(open_or_create_private_directory(
fs::create_dir(&directory) &mailbox,
.map_err(|error| failure(format!("creating control {relative}: {error}")))?; "requests",
set_private_directory(&directory)?; invoking_uid,
} true,
)?);
drop(open_or_create_private_directory(
&mailbox,
"results",
invoking_uid,
true,
)?);
let bin = open_or_create_private_directory(&self.control, "bin", invoking_uid, true)?;
let source = escalation_client let source = escalation_client
.metadata() .metadata()
.map_err(|error| failure(format!("inspecting escalation client: {error}")))?; .map_err(|error| failure(format!("inspecting escalation client: {error}")))?;
@ -332,37 +360,44 @@ impl RuntimeStaging {
"escalation client is not an executable regular file".into(), "escalation client is not an executable regular file".into(),
)); ));
} }
copy_descriptor( copy_descriptor(escalation_client, &bin, "lazaret-escalate", invoking_uid)?;
escalation_client,
&self.control.join("bin/lazaret-escalate"),
)?;
Ok(()) Ok(())
} }
fn write_bundle(&self, bytes: &[u8]) -> Result<(), ProductionColdStartError> { fn write_bundle(&self, bytes: &[u8]) -> Result<(), ProductionColdStartError> {
let path = self.bundle.join("config.json"); let descriptor = openat2(
let mut file = OpenOptions::new() &self.bundle,
.write(true) "config.json",
.create_new(true) OFlags::WRONLY | OFlags::CREATE | OFlags::EXCL | OFlags::CLOEXEC | OFlags::NOFOLLOW,
.open(path) Mode::RUSR | Mode::WUSR,
.map_err(failure)?; confined_resolve_flags(),
)
.map_err(failure)?;
let mut file = File::from(descriptor);
file.write_all(bytes).map_err(failure)?; file.write_all(bytes).map_err(failure)?;
file.set_permissions(fs::Permissions::from_mode(0o600)) fchmod(file.as_fd(), Mode::RUSR | Mode::WUSR).map_err(failure)?;
.map_err(failure)?;
file.sync_all().map_err(failure)?; file.sync_all().map_err(failure)?;
require_private_file(&file, fstat(&self.bundle).map_err(failure)?.st_uid, 0o600)?;
Ok(()) Ok(())
} }
} }
fn copy_descriptor(source: &File, destination: &Path) -> Result<(), ProductionColdStartError> { fn copy_descriptor(
let mut destination = OpenOptions::new() source: &File,
.write(true) parent: &OwnedFd,
.create_new(true) name: &str,
.open(destination) invoking_uid: u32,
.map_err(failure)?; ) -> Result<(), ProductionColdStartError> {
destination let descriptor = openat2(
.set_permissions(fs::Permissions::from_mode(0o700)) parent,
.map_err(failure)?; name,
OFlags::WRONLY | OFlags::CREATE | OFlags::EXCL | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::RWXU,
confined_resolve_flags(),
)
.map_err(failure)?;
let mut destination = File::from(descriptor);
fchmod(destination.as_fd(), Mode::RWXU).map_err(failure)?;
let mut offset = 0_u64; let mut offset = 0_u64;
let mut buffer = [0_u8; 16 * 1024]; let mut buffer = [0_u8; 16 * 1024];
loop { loop {
@ -375,35 +410,171 @@ fn copy_descriptor(source: &File, destination: &Path) -> Result<(), ProductionCo
.checked_add(read as u64) .checked_add(read as u64)
.ok_or_else(|| ProductionColdStartError("escalation client is too large".into()))?; .ok_or_else(|| ProductionColdStartError("escalation client is too large".into()))?;
} }
destination.sync_all().map_err(failure) destination.sync_all().map_err(failure)?;
require_private_file(&destination, invoking_uid, 0o700)
} }
fn set_private_directory(path: &Path) -> Result<(), ProductionColdStartError> { #[derive(Clone, Copy)]
fs::set_permissions(path, fs::Permissions::from_mode(0o700)).map_err(failure) struct FileIdentity {
device: u64,
inode: u64,
}
impl FileIdentity {
fn capture(descriptor: impl AsFd) -> Result<Self, ProductionColdStartError> {
let metadata = fstat(descriptor).map_err(failure)?;
Ok(Self {
device: metadata.st_dev,
inode: metadata.st_ino,
})
}
fn matches(self, metadata: &rustix::fs::Stat) -> bool {
metadata.st_dev == self.device && metadata.st_ino == self.inode
}
} }
impl Drop for RuntimeStaging { impl Drop for RuntimeStaging {
fn drop(&mut self) { fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.control); remove_staging_directory(
let _ = fs::remove_dir_all(&self.bundle); &self.controls,
&self.name,
&self.control,
self.control_identity,
);
remove_staging_directory(
&self.bundles,
&self.name,
&self.bundle,
self.bundle_identity,
);
} }
} }
fn ensure_directory(path: &Path) -> Result<(), ProductionColdStartError> { fn open_or_create_private_directory(
match fs::create_dir(path) { parent: impl AsFd,
Ok(()) => Ok(()), name: impl AsRef<std::ffi::OsStr>,
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => { invoking_uid: u32,
let metadata = fs::symlink_metadata(path).map_err(failure)?; require_new: bool,
if metadata.file_type().is_dir() { ) -> Result<OwnedFd, ProductionColdStartError> {
Ok(()) let name = name.as_ref();
} else { let created = match mkdirat(parent.as_fd(), name, Mode::RWXU) {
Err(ProductionColdStartError( Ok(()) => true,
"production staging path is not a directory".into(), Err(rustix::io::Errno::EXIST) if !require_new => false,
)) Err(error) => return Err(failure(error)),
};
let directory = openat2(
parent.as_fd(),
name,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::empty(),
confined_resolve_flags(),
)
.map_err(failure)?;
if created {
fchmod(&directory, Mode::RWXU).map_err(failure)?;
}
require_private_directory(&directory, invoking_uid)?;
Ok(directory)
}
fn require_private_directory(
descriptor: impl AsFd,
invoking_uid: u32,
) -> Result<(), ProductionColdStartError> {
let metadata = fstat(descriptor).map_err(failure)?;
if !FileType::from_raw_mode(metadata.st_mode).is_dir()
|| metadata.st_uid != invoking_uid
|| metadata.st_mode & 0o7777 != 0o700
{
return Err(ProductionColdStartError(
"runtime staging directory is not invoking-user-owned mode 0700".into(),
));
}
Ok(())
}
fn require_private_file(
descriptor: impl AsFd,
invoking_uid: u32,
mode: u32,
) -> Result<(), ProductionColdStartError> {
let metadata = fstat(descriptor).map_err(failure)?;
if !FileType::from_raw_mode(metadata.st_mode).is_file()
|| metadata.st_uid != invoking_uid
|| metadata.st_mode & 0o7777 != mode
|| metadata.st_nlink != 1
{
return Err(ProductionColdStartError(
"runtime staging file has unsafe type, ownership, mode, or link count".into(),
));
}
Ok(())
}
fn remove_staging_directory(
parent: impl AsFd,
name: &std::ffi::OsStr,
directory: impl AsFd,
expected: FileIdentity,
) {
let retained = fstat(directory.as_fd());
if !retained
.as_ref()
.is_ok_and(|metadata| expected.matches(metadata))
{
return;
}
if remove_children(directory.as_fd()).is_err() {
return;
}
let Ok(current) = statat(parent.as_fd(), name, AtFlags::SYMLINK_NOFOLLOW) else {
return;
};
if expected.matches(&current) && FileType::from_raw_mode(current.st_mode).is_dir() {
let _ = unlinkat(parent.as_fd(), name, AtFlags::REMOVEDIR);
}
}
fn remove_children(directory: impl AsFd) -> std::io::Result<()> {
let duplicate = rustix::io::dup(directory.as_fd())?;
let mut buffer = [std::mem::MaybeUninit::uninit(); 8192];
let names = {
let mut names = Vec::new();
let mut entries = RawDir::new(duplicate, &mut buffer);
while let Some(entry) = entries.next() {
let entry = entry?;
let bytes = entry.file_name().to_bytes();
if bytes != b"." && bytes != b".." {
names.push(OsString::from_vec(bytes.to_vec()));
} }
} }
Err(error) => Err(failure(error)), names
};
for name in names {
let metadata = statat(directory.as_fd(), &name, AtFlags::SYMLINK_NOFOLLOW)?;
if FileType::from_raw_mode(metadata.st_mode).is_dir() {
let child = openat2(
directory.as_fd(),
&name,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC | OFlags::NOFOLLOW,
Mode::empty(),
confined_resolve_flags(),
)?;
remove_children(&child)?;
unlinkat(directory.as_fd(), &name, AtFlags::REMOVEDIR)?;
} else {
unlinkat(directory.as_fd(), &name, AtFlags::empty())?;
}
} }
Ok(())
}
fn confined_resolve_flags() -> ResolveFlags {
ResolveFlags::BENEATH
| ResolveFlags::NO_MAGICLINKS
| ResolveFlags::NO_SYMLINKS
| ResolveFlags::NO_XDEV
} }
fn host_path(bytes: &[u8]) -> PathBuf { fn host_path(bytes: &[u8]) -> PathBuf {

View file

@ -23,108 +23,198 @@ use rustix::process::getuid;
#[test] #[test]
fn validated_inputs_stage_one_complete_plan_and_drop_exact_runtime_artifacts() { fn validated_inputs_stage_one_complete_plan_and_drop_exact_runtime_artifacts() {
let tree = tempfile::tempdir().unwrap(); let fixture = ProductionFixture::new();
let configuration = configuration(tree.path()); let staged = ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
let persistent_domain = host_path( let bundle = fixture.bundle();
configuration let control = fixture.control();
.persistent_domain_root()
.canonical_label()
.as_bytes(),
);
let runtime_domain = host_path(
configuration
.runtime_domain_root()
.canonical_label()
.as_bytes(),
);
let catalog = Catalog::new(
tree.path().join("catalog.redb"),
tree.path().join("catalog.lock"),
);
catalog.initialize().unwrap();
let domain = Domain {
domain_id: configuration.domain_id(),
profile_name: configuration.selected_profile().to_owned(),
canonical_workspace: configuration.workspace().canonical_label().clone(),
};
let lock = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(persistent_domain.join("domain.lock"))
.unwrap();
let store = Arc::new(
HostCoordinatorDomainStore::open(
try_exclusive_flock(lock).unwrap(),
&persistent_domain.join("domain.redb"),
&domain,
)
.unwrap(),
);
let lower = tree.path().join("prepared-lower");
fs::create_dir(&lower).unwrap();
let metadata = lower.metadata().unwrap();
let artifact = PreparedArtifact {
prepared_artifact_id: PreparedArtifactId::new([8; 32]),
source_identity: SourceIdentity::new(b"synthetic-spike".to_vec()),
selected_manifest_digest: SelectedManifestDigest::new([9; 32]),
policy_id: PolicyId::new([10; 32]),
id_map_digest: IdMapDigest::new([11; 32]),
path: HostPath::new(lower.as_os_str().as_bytes().to_vec()),
device: DeviceId::new(metadata.dev()),
inode: InodeId::new(metadata.ino()),
state: ArtifactState::Available,
};
let generation = Generation {
domain_id: configuration.domain_id(),
generation_id: GenerationId::new(1),
configuration_fingerprint: configuration.configuration_fingerprint(),
coordinator_id: CoordinatorId::new([2; 32]),
network_sidecar_id: lazaret_core::identity::NetworkSidecarId::new([3; 32]),
sandbox_id: SandboxId::new([4; 32]),
durable_decision: DurableDecision::None,
};
let container_id = ContainerId::new([5; 32]);
let escalation_client = File::open("/bin/true").unwrap();
let invalid_escalation_client = File::open("/dev/null").unwrap();
let inputs = |escalation_client| ProductionColdStartInputs {
store: Arc::clone(&store),
catalog: &catalog,
configuration: &configuration,
generation: generation.clone(),
prepared_artifact: &artifact,
root_process: RootProcess::new("/bin/sh", ["-c".into(), "sleep infinity".into()]).unwrap(),
resolver_addresses: vec![IpAddress::V4([10, 0, 2, 3])],
network_probe: NetworkProbeRequest {
domain_id: configuration.domain_id(),
generation_id: GenerationId::new(1),
enforcement_namespace: namespace_identity(Path::new("/proc/self/ns/net")).unwrap(),
denied: Vec::new(),
permitted: Vec::new(),
dns: Vec::new(),
},
reachability: lazaret_core::network::ReachabilityExpectation {
denied_destinations: Vec::new(),
permitted_destinations: Vec::new(),
dns_queries: Vec::new(),
},
container_id,
invoking_uid: getuid().as_raw(),
escalation_client,
};
let staged = ProductionColdStart::stage(inputs(&escalation_client)).unwrap();
let name = hex(container_id.as_bytes());
let bundle = runtime_domain.join("bundles").join(&name);
let control = runtime_domain.join("sandbox-control").join(&name);
assert_private_artifacts(&bundle, &control); assert_private_artifacts(&bundle, &control);
drop(staged); drop(staged);
assert!(!bundle.exists()); assert!(!bundle.exists());
assert!(!control.exists()); assert!(!control.exists());
assert!(ProductionColdStart::stage(inputs(&invalid_escalation_client)).is_err()); assert!(
ProductionColdStart::stage(fixture.inputs(&fixture.invalid_escalation_client)).is_err()
);
assert!(!bundle.exists()); assert!(!bundle.exists());
assert!(!control.exists()); assert!(!control.exists());
} }
#[test]
fn cleanup_leaves_a_replacement_directory_untouched() {
let fixture = ProductionFixture::new();
let staged = ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).unwrap();
let bundle = fixture.bundle();
let moved = bundle.with_extension("moved");
fs::rename(&bundle, &moved).unwrap();
fs::create_dir(&bundle).unwrap();
fs::write(bundle.join("replacement"), b"trusted replacement").unwrap();
drop(staged);
assert_eq!(
fs::read(bundle.join("replacement")).unwrap(),
b"trusted replacement"
);
assert!(moved.is_dir());
}
#[test]
fn fixed_runtime_parents_refuse_symlinks_and_wrong_modes() {
let fixture = ProductionFixture::new();
let bundles = fixture.runtime_domain.join("bundles");
fs::create_dir(&bundles).unwrap();
fs::set_permissions(&bundles, fs::Permissions::from_mode(0o755)).unwrap();
assert!(ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
fs::remove_dir(&bundles).unwrap();
let controls = fixture.runtime_domain.join("sandbox-control");
let redirect = fixture.tree.path().join("redirect");
fs::create_dir(&redirect).unwrap();
std::os::unix::fs::symlink(&redirect, &controls).unwrap();
assert!(ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
assert!(redirect.read_dir().unwrap().next().is_none());
}
#[test]
fn replaced_runtime_domain_authority_is_not_used_for_staging() {
let fixture = ProductionFixture::new();
let moved = fixture.runtime_domain.with_extension("moved");
fs::rename(&fixture.runtime_domain, &moved).unwrap();
fs::create_dir(&fixture.runtime_domain).unwrap();
fs::set_permissions(&fixture.runtime_domain, fs::Permissions::from_mode(0o700)).unwrap();
assert!(ProductionColdStart::stage(fixture.inputs(&fixture.escalation_client)).is_err());
assert!(fixture.runtime_domain.read_dir().unwrap().next().is_none());
}
struct ProductionFixture {
tree: tempfile::TempDir,
configuration: lazaret_core::configuration::ResolvedDomainConfiguration,
runtime_domain: PathBuf,
catalog: Catalog,
store: Arc<HostCoordinatorDomainStore>,
artifact: PreparedArtifact,
generation: Generation,
container_id: ContainerId,
escalation_client: File,
invalid_escalation_client: File,
}
impl ProductionFixture {
fn new() -> Self {
let tree = tempfile::tempdir().unwrap();
let configuration = configuration(tree.path());
let persistent_domain = host_path(
configuration
.persistent_domain_root()
.canonical_label()
.as_bytes(),
);
let runtime_domain = host_path(
configuration
.runtime_domain_root()
.canonical_label()
.as_bytes(),
);
let catalog = Catalog::new(
tree.path().join("catalog.redb"),
tree.path().join("catalog.lock"),
);
catalog.initialize().unwrap();
let domain = Domain {
domain_id: configuration.domain_id(),
profile_name: configuration.selected_profile().to_owned(),
canonical_workspace: configuration.workspace().canonical_label().clone(),
};
let lock = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(persistent_domain.join("domain.lock"))
.unwrap();
let store = Arc::new(
HostCoordinatorDomainStore::open(
try_exclusive_flock(lock).unwrap(),
&persistent_domain.join("domain.redb"),
&domain,
)
.unwrap(),
);
let lower = tree.path().join("prepared-lower");
fs::create_dir(&lower).unwrap();
let metadata = lower.metadata().unwrap();
let artifact = PreparedArtifact {
prepared_artifact_id: PreparedArtifactId::new([8; 32]),
source_identity: SourceIdentity::new(b"synthetic-spike".to_vec()),
selected_manifest_digest: SelectedManifestDigest::new([9; 32]),
policy_id: PolicyId::new([10; 32]),
id_map_digest: IdMapDigest::new([11; 32]),
path: HostPath::new(lower.as_os_str().as_bytes().to_vec()),
device: DeviceId::new(metadata.dev()),
inode: InodeId::new(metadata.ino()),
state: ArtifactState::Available,
};
let generation = Generation {
domain_id: configuration.domain_id(),
generation_id: GenerationId::new(1),
configuration_fingerprint: configuration.configuration_fingerprint(),
coordinator_id: CoordinatorId::new([2; 32]),
network_sidecar_id: lazaret_core::identity::NetworkSidecarId::new([3; 32]),
sandbox_id: SandboxId::new([4; 32]),
durable_decision: DurableDecision::None,
};
Self {
tree,
configuration,
runtime_domain,
catalog,
store,
artifact,
generation,
container_id: ContainerId::new([5; 32]),
escalation_client: File::open("/bin/true").unwrap(),
invalid_escalation_client: File::open("/dev/null").unwrap(),
}
}
fn inputs<'a>(&'a self, escalation_client: &'a File) -> ProductionColdStartInputs<'a> {
ProductionColdStartInputs {
store: Arc::clone(&self.store),
catalog: &self.catalog,
configuration: &self.configuration,
generation: self.generation.clone(),
prepared_artifact: &self.artifact,
root_process: RootProcess::new("/bin/sh", ["-c".into(), "sleep infinity".into()])
.unwrap(),
resolver_addresses: vec![IpAddress::V4([10, 0, 2, 3])],
network_probe: NetworkProbeRequest {
domain_id: self.configuration.domain_id(),
generation_id: GenerationId::new(1),
enforcement_namespace: namespace_identity(Path::new("/proc/self/ns/net")).unwrap(),
denied: Vec::new(),
permitted: Vec::new(),
dns: Vec::new(),
},
reachability: lazaret_core::network::ReachabilityExpectation {
denied_destinations: Vec::new(),
permitted_destinations: Vec::new(),
dns_queries: Vec::new(),
},
container_id: self.container_id,
invoking_uid: getuid().as_raw(),
escalation_client,
}
}
fn bundle(&self) -> PathBuf {
self.runtime_domain
.join("bundles")
.join(hex(self.container_id.as_bytes()))
}
fn control(&self) -> PathBuf {
self.runtime_domain
.join("sandbox-control")
.join(hex(self.container_id.as_bytes()))
}
}
fn assert_private_artifacts(bundle: &Path, control: &Path) { fn assert_private_artifacts(bundle: &Path, control: &Path) {
let config = bundle.join("config.json"); let config = bundle.join("config.json");
let escalation_client = control.join("bin/lazaret-escalate"); let escalation_client = control.join("bin/lazaret-escalate");