Bind production cold start to installed Lazaret authorities
This commit is contained in:
parent
c0b139256e
commit
bc04112c77
9 changed files with 573 additions and 25 deletions
|
|
@ -19,6 +19,7 @@ ed25519-dalek.workspace = true
|
|||
getrandom.workspace = true
|
||||
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
|
||||
lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" }
|
||||
lazaret-prepared-lower = { version = "=0.0.0", path = "../lazaret-prepared-lower" }
|
||||
lazaret-runtime-supervisor = { version = "=0.0.0", path = "../lazaret-runtime-supervisor" }
|
||||
lazaret-state-anchor = { version = "=0.0.0", path = "../lazaret-state-anchor" }
|
||||
lazaret-storage-catalog = { version = "=0.0.0", path = "../lazaret-storage-catalog" }
|
||||
|
|
|
|||
|
|
@ -7,5 +7,7 @@ pub mod escalation;
|
|||
pub mod lifecycle;
|
||||
pub mod network;
|
||||
pub mod production;
|
||||
pub mod production_selector;
|
||||
pub mod runtime;
|
||||
pub mod sandbox_init;
|
||||
pub mod state_anchor;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
fn main() {
|
||||
let arguments = std::env::args_os().skip(1).collect::<Vec<_>>();
|
||||
if lazaret_host_coordinator::sandbox_init::dispatch_private_sandbox_init(&arguments) {
|
||||
return;
|
||||
}
|
||||
if let Some(result) = lazaret_linux::process::dispatch_private_process_role(&arguments) {
|
||||
if let Err(error) = result {
|
||||
eprintln!("private process role failed: {error}");
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ pub struct ProductionColdStartInputs<'a> {
|
|||
pub container_id: ContainerId,
|
||||
pub invoking_uid: u32,
|
||||
pub escalation_client: &'a File,
|
||||
pub sandbox_init: &'a File,
|
||||
}
|
||||
|
||||
pub struct BootstrapOwnerGuard {
|
||||
|
|
@ -138,6 +139,7 @@ impl<'a> ProductionColdStart<'a> {
|
|||
inputs.configuration.runtime_domain_root(),
|
||||
inputs.container_id,
|
||||
inputs.escalation_client,
|
||||
inputs.sandbox_init,
|
||||
inputs.invoking_uid,
|
||||
)
|
||||
.map_err(|error| failure(format!("runtime staging: {error}")))?;
|
||||
|
|
@ -293,6 +295,7 @@ impl RuntimeStaging {
|
|||
runtime_domain: &ConfinedPathPlan,
|
||||
container_id: ContainerId,
|
||||
escalation_client: &File,
|
||||
sandbox_init: &File,
|
||||
invoking_uid: u32,
|
||||
) -> Result<Self, ProductionColdStartError> {
|
||||
let root = runtime_domain.open_authority().map_err(failure)?;
|
||||
|
|
@ -325,11 +328,15 @@ impl RuntimeStaging {
|
|||
bundle_identity,
|
||||
control_identity,
|
||||
};
|
||||
staging.populate_control(escalation_client)?;
|
||||
staging.populate_control(escalation_client, sandbox_init)?;
|
||||
Ok(staging)
|
||||
}
|
||||
|
||||
fn populate_control(&self, escalation_client: &File) -> Result<(), ProductionColdStartError> {
|
||||
fn populate_control(
|
||||
&self,
|
||||
escalation_client: &File,
|
||||
sandbox_init: &File,
|
||||
) -> Result<(), ProductionColdStartError> {
|
||||
let invoking_uid = fstat(&self.control).map_err(failure)?.st_uid;
|
||||
drop(open_or_create_private_directory(
|
||||
&self.control,
|
||||
|
|
@ -352,15 +359,10 @@ impl RuntimeStaging {
|
|||
true,
|
||||
)?);
|
||||
let bin = open_or_create_private_directory(&self.control, "bin", invoking_uid, true)?;
|
||||
let source = escalation_client
|
||||
.metadata()
|
||||
.map_err(|error| failure(format!("inspecting escalation client: {error}")))?;
|
||||
if !source.file_type().is_file() || source.permissions().mode() & 0o111 == 0 {
|
||||
return Err(ProductionColdStartError(
|
||||
"escalation client is not an executable regular file".into(),
|
||||
));
|
||||
}
|
||||
require_executable_regular_file(escalation_client, "escalation client")?;
|
||||
copy_descriptor(escalation_client, &bin, "lazaret-escalate", invoking_uid)?;
|
||||
require_executable_regular_file(sandbox_init, "sandbox init")?;
|
||||
copy_descriptor(sandbox_init, &bin, "lazaret-sandbox-init", invoking_uid)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -414,6 +416,21 @@ fn copy_descriptor(
|
|||
require_private_file(&destination, invoking_uid, 0o700)
|
||||
}
|
||||
|
||||
fn require_executable_regular_file(
|
||||
source: &File,
|
||||
label: &str,
|
||||
) -> Result<(), ProductionColdStartError> {
|
||||
let metadata = source
|
||||
.metadata()
|
||||
.map_err(|error| failure(format!("inspecting {label}: {error}")))?;
|
||||
if !metadata.file_type().is_file() || metadata.permissions().mode() & 0o111 == 0 {
|
||||
return Err(ProductionColdStartError(format!(
|
||||
"{label} is not an executable regular file"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FileIdentity {
|
||||
device: u64,
|
||||
|
|
|
|||
405
crates/lazaret-host-coordinator/src/production_selector.rs
Normal file
405
crates/lazaret-host-coordinator/src/production_selector.rs
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use lazaret_core::configuration::ResolvedDomainConfiguration;
|
||||
use lazaret_core::identity::{
|
||||
ConfigurationFingerprint, ContainerId, CoordinatorId, DomainId, GenerationId, NetworkSidecarId,
|
||||
PrepareOperationId, SandboxId,
|
||||
};
|
||||
use lazaret_core::network::{DnsQueryExpectation, ReachabilityExpectation};
|
||||
use lazaret_core::records::{DurableDecision, Generation, IpAddress, PreparedArtifact};
|
||||
use lazaret_linux::filesystem::namespace_identity;
|
||||
use lazaret_prepared_lower::{
|
||||
IdMap, IdMapRange, OciRegistryTransport, Platform, PreparedLowerStore, RegistryCredentials,
|
||||
};
|
||||
use lazaret_runtime_supervisor::network::{DnsProbeTarget, NetworkProbeRequest, TcpProbeTarget};
|
||||
use lazaret_runtime_supervisor::sandbox::{RootProcess, SANDBOX_INIT_DESTINATION};
|
||||
use lazaret_storage_catalog::{Catalog, CatalogError};
|
||||
use rustix::fs::{Mode, OFlags, ResolveFlags, openat2};
|
||||
|
||||
use crate::domain_store::HostCoordinatorDomainStore;
|
||||
use crate::production::ProductionColdStartInputs;
|
||||
use crate::sandbox_init::PRIVATE_SANDBOX_INIT_ROLE;
|
||||
|
||||
const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProductionSelectionError(String);
|
||||
|
||||
impl fmt::Display for ProductionSelectionError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ProductionSelectionError {}
|
||||
|
||||
fn failure(error: impl fmt::Display) -> ProductionSelectionError {
|
||||
ProductionSelectionError(error.to_string())
|
||||
}
|
||||
|
||||
pub struct InstalledProductPaths<'a> {
|
||||
pub escalation_client: &'a Path,
|
||||
pub sandbox_init: &'a Path,
|
||||
}
|
||||
|
||||
pub struct ProductionSelection {
|
||||
domain_id: DomainId,
|
||||
configuration_fingerprint: ConfigurationFingerprint,
|
||||
catalog: Catalog,
|
||||
artifact: PreparedArtifact,
|
||||
generation: Generation,
|
||||
resolver_addresses: Vec<IpAddress>,
|
||||
network_probe: NetworkProbeRequest,
|
||||
reachability: ReachabilityExpectation,
|
||||
container_id: ContainerId,
|
||||
escalation_client: File,
|
||||
sandbox_init: File,
|
||||
}
|
||||
|
||||
impl ProductionSelection {
|
||||
/// Borrows the selected resources as the existing production cold-start input.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses a configuration other than the exact one used for selection.
|
||||
pub fn cold_start_inputs<'a>(
|
||||
&'a self,
|
||||
store: Arc<HostCoordinatorDomainStore>,
|
||||
configuration: &'a ResolvedDomainConfiguration,
|
||||
) -> Result<ProductionColdStartInputs<'a>, ProductionSelectionError> {
|
||||
if configuration.domain_id() != self.domain_id
|
||||
|| configuration.configuration_fingerprint() != self.configuration_fingerprint
|
||||
{
|
||||
return Err(failure("selected cold start does not match configuration"));
|
||||
}
|
||||
Ok(ProductionColdStartInputs {
|
||||
store,
|
||||
catalog: &self.catalog,
|
||||
configuration,
|
||||
generation: self.generation.clone(),
|
||||
prepared_artifact: &self.artifact,
|
||||
root_process: RootProcess::new(
|
||||
SANDBOX_INIT_DESTINATION,
|
||||
[PRIVATE_SANDBOX_INIT_ROLE.to_owned()],
|
||||
)
|
||||
.map_err(failure)?,
|
||||
resolver_addresses: self.resolver_addresses.clone(),
|
||||
network_probe: self.network_probe.clone(),
|
||||
reachability: self.reachability.clone(),
|
||||
container_id: self.container_id,
|
||||
invoking_uid: rustix::process::getuid().as_raw(),
|
||||
escalation_client: &self.escalation_client,
|
||||
sandbox_init: &self.sandbox_init,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects and prepares every Host Coordinator-owned cold-start input.
|
||||
///
|
||||
/// # Errors
|
||||
/// Refuses invalid installed paths, unsupported architectures, registry or image failures,
|
||||
/// changed Storage Root authority, exhausted identities, and configuration-policy mismatch.
|
||||
pub async fn select_production_cold_start(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
store: &HostCoordinatorDomainStore,
|
||||
product_paths: InstalledProductPaths<'_>,
|
||||
) -> Result<ProductionSelection, ProductionSelectionError> {
|
||||
let escalation_client = open_installed_executable(product_paths.escalation_client)?;
|
||||
let sandbox_init = open_installed_executable(product_paths.sandbox_init)?;
|
||||
let catalog = Catalog::under_storage_root(configuration.storage_root()).map_err(failure)?;
|
||||
match catalog.initialize() {
|
||||
Ok(()) | Err(CatalogError::AlreadyInitialized) => {}
|
||||
Err(error) => return Err(failure(error)),
|
||||
}
|
||||
let prepared =
|
||||
PreparedLowerStore::under_storage_root(configuration.storage_root(), catalog.clone())
|
||||
.map_err(failure)?;
|
||||
let image = OciRegistryTransport
|
||||
.pull_confined(
|
||||
&configuration.fingerprint_record().image,
|
||||
&RegistryCredentials::Anonymous,
|
||||
&host_platform()?,
|
||||
configuration.storage_root(),
|
||||
random_id()?,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| failure(format!("pulling configured anonymous OCI image: {error}")))?;
|
||||
let artifact = prepared
|
||||
.prepare_transported(
|
||||
PrepareOperationId::new(random_id()?),
|
||||
&image,
|
||||
rootless_id_map(),
|
||||
)
|
||||
.map_err(failure)?;
|
||||
image.cleanup().map_err(failure)?;
|
||||
|
||||
let next_generation = store
|
||||
.snapshot()
|
||||
.map_err(failure)?
|
||||
.generations
|
||||
.iter()
|
||||
.map(|generation| generation.generation_id.get())
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.checked_add(1)
|
||||
.ok_or_else(|| failure("generation identity exhausted"))?;
|
||||
let (coordinator_id, network_sidecar_id, sandbox_id) = distinct_generation_ids()?;
|
||||
let generation = Generation {
|
||||
domain_id: configuration.domain_id(),
|
||||
generation_id: GenerationId::new(next_generation),
|
||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||
coordinator_id,
|
||||
network_sidecar_id,
|
||||
sandbox_id,
|
||||
durable_decision: DurableDecision::None,
|
||||
};
|
||||
let namespace = namespace_identity(Path::new(NETWORK_NAMESPACE_PATH)).map_err(failure)?;
|
||||
let (resolver_addresses, network_probe, reachability) =
|
||||
select_network_readiness(configuration, &generation, namespace);
|
||||
Ok(ProductionSelection {
|
||||
domain_id: configuration.domain_id(),
|
||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||
catalog,
|
||||
artifact,
|
||||
generation,
|
||||
resolver_addresses,
|
||||
network_probe,
|
||||
reachability,
|
||||
container_id: ContainerId::new(random_id()?),
|
||||
escalation_client,
|
||||
sandbox_init,
|
||||
})
|
||||
}
|
||||
|
||||
fn select_network_readiness(
|
||||
configuration: &ResolvedDomainConfiguration,
|
||||
generation: &Generation,
|
||||
enforcement_namespace: lazaret_core::identity::NamespaceIdentity,
|
||||
) -> (Vec<IpAddress>, NetworkProbeRequest, ReachabilityExpectation) {
|
||||
let readiness = configuration.network_readiness();
|
||||
let denied = readiness
|
||||
.denied
|
||||
.iter()
|
||||
.map(|target| TcpProbeTarget {
|
||||
address: IpAddress::V4(target.address),
|
||||
port: target.port,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let permitted = readiness
|
||||
.permitted
|
||||
.iter()
|
||||
.map(|target| TcpProbeTarget {
|
||||
address: IpAddress::V4(target.address),
|
||||
port: target.port,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let dns = readiness
|
||||
.dns
|
||||
.iter()
|
||||
.map(|target| DnsProbeTarget {
|
||||
resolver: IpAddress::V4(target.resolver),
|
||||
query_name: target.query.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let reachability = ReachabilityExpectation {
|
||||
denied_destinations: denied.iter().map(|target| target.address).collect(),
|
||||
permitted_destinations: permitted.iter().map(|target| target.address).collect(),
|
||||
dns_queries: dns
|
||||
.iter()
|
||||
.map(|target| DnsQueryExpectation {
|
||||
resolver: target.resolver,
|
||||
query_name: target.query_name.clone(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let resolvers = dns
|
||||
.iter()
|
||||
.map(|target| target.resolver)
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
(
|
||||
resolvers,
|
||||
NetworkProbeRequest {
|
||||
domain_id: generation.domain_id,
|
||||
generation_id: generation.generation_id,
|
||||
enforcement_namespace,
|
||||
denied,
|
||||
permitted,
|
||||
dns,
|
||||
},
|
||||
reachability,
|
||||
)
|
||||
}
|
||||
|
||||
fn open_installed_executable(path: &Path) -> Result<File, ProductionSelectionError> {
|
||||
let descriptor = openat2(
|
||||
rustix::fs::CWD,
|
||||
path,
|
||||
OFlags::RDONLY | OFlags::NOFOLLOW | OFlags::CLOEXEC,
|
||||
Mode::empty(),
|
||||
ResolveFlags::NO_MAGICLINKS | ResolveFlags::NO_SYMLINKS,
|
||||
)
|
||||
.map_err(failure)?;
|
||||
let file = File::from(descriptor);
|
||||
let metadata = file.metadata().map_err(failure)?;
|
||||
if !metadata.is_file() || metadata.permissions().mode() & 0o111 == 0 {
|
||||
return Err(failure(
|
||||
"installed product path is not an executable regular file",
|
||||
));
|
||||
}
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
fn rootless_id_map() -> IdMap {
|
||||
IdMap {
|
||||
uid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::getuid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
gid: vec![IdMapRange {
|
||||
container_id: 0,
|
||||
namespace_id: rustix::process::getgid().as_raw(),
|
||||
size: 1,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
fn host_platform() -> Result<Platform, ProductionSelectionError> {
|
||||
match std::env::consts::ARCH {
|
||||
"x86_64" => Ok(Platform::linux("amd64")),
|
||||
"aarch64" => Ok(Platform::linux("arm64")),
|
||||
architecture => Err(failure(format!(
|
||||
"host architecture {architecture} has no selected OCI platform mapping"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn distinct_generation_ids()
|
||||
-> Result<(CoordinatorId, NetworkSidecarId, SandboxId), ProductionSelectionError> {
|
||||
for _ in 0..8 {
|
||||
let coordinator = CoordinatorId::new(random_id()?);
|
||||
let network = NetworkSidecarId::new(random_id()?);
|
||||
let sandbox = SandboxId::new(random_id()?);
|
||||
if coordinator.as_bytes() != network.as_bytes()
|
||||
&& coordinator.as_bytes() != sandbox.as_bytes()
|
||||
&& network.as_bytes() != sandbox.as_bytes()
|
||||
{
|
||||
return Ok((coordinator, network, sandbox));
|
||||
}
|
||||
}
|
||||
Err(failure(
|
||||
"fresh generation role identities repeatedly collided",
|
||||
))
|
||||
}
|
||||
|
||||
fn random_id() -> Result<[u8; 32], ProductionSelectionError> {
|
||||
let mut bytes = [0; 32];
|
||||
getrandom::fill(&mut bytes).map_err(failure)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs;
|
||||
|
||||
use lazaret_core::configuration::{ConfigurationLoadRequest, load_trusted_configuration};
|
||||
use lazaret_core::identity::{DeviceId, InodeId, NamespaceIdentity};
|
||||
use lazaret_runtime_supervisor::sandbox::production_sandbox_layout;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resolved_readiness_maps_exactly_and_rootless_id_map_matches_builder_policy() {
|
||||
let tree = tempfile::tempdir().unwrap();
|
||||
let configuration = configuration(tree.path());
|
||||
let generation = Generation {
|
||||
domain_id: configuration.domain_id(),
|
||||
generation_id: GenerationId::new(9),
|
||||
configuration_fingerprint: configuration.configuration_fingerprint(),
|
||||
coordinator_id: CoordinatorId::new([1; 32]),
|
||||
network_sidecar_id: NetworkSidecarId::new([2; 32]),
|
||||
sandbox_id: SandboxId::new([3; 32]),
|
||||
durable_decision: DurableDecision::None,
|
||||
};
|
||||
let namespace = NamespaceIdentity {
|
||||
device: DeviceId::new(4),
|
||||
inode: InodeId::new(5),
|
||||
};
|
||||
let (resolvers, request, expectation) =
|
||||
select_network_readiness(&configuration, &generation, namespace);
|
||||
assert_eq!(resolvers, [IpAddress::V4([198, 51, 100, 53])]);
|
||||
assert_eq!(request.denied[0].port, 8443);
|
||||
assert_eq!(request.permitted[0].port, 443);
|
||||
assert_eq!(request.dns[0].query_name, "example.invalid");
|
||||
assert_eq!(expectation.denied_destinations, [request.denied[0].address]);
|
||||
assert_eq!(
|
||||
expectation.permitted_destinations,
|
||||
[request.permitted[0].address]
|
||||
);
|
||||
assert_eq!(expectation.dns_queries[0].resolver, request.dns[0].resolver);
|
||||
|
||||
let id_map = rootless_id_map();
|
||||
assert_eq!(id_map.uid[0].container_id, 0);
|
||||
assert_eq!(
|
||||
id_map.uid[0].namespace_id,
|
||||
rustix::process::getuid().as_raw()
|
||||
);
|
||||
assert_eq!(
|
||||
id_map.gid[0].namespace_id,
|
||||
rustix::process::getgid().as_raw()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installed_product_selection_requires_exact_executable_and_fresh_roles() {
|
||||
let tree = tempfile::tempdir().unwrap();
|
||||
let executable = tree.path().join("product");
|
||||
fs::copy("/bin/true", &executable).unwrap();
|
||||
fs::set_permissions(&executable, fs::Permissions::from_mode(0o700)).unwrap();
|
||||
assert!(open_installed_executable(&executable).is_ok());
|
||||
fs::set_permissions(&executable, fs::Permissions::from_mode(0o600)).unwrap();
|
||||
assert!(open_installed_executable(&executable).is_err());
|
||||
let (coordinator, network, sandbox) = distinct_generation_ids().unwrap();
|
||||
assert_ne!(coordinator.as_bytes(), network.as_bytes());
|
||||
assert_ne!(coordinator.as_bytes(), sandbox.as_bytes());
|
||||
assert_ne!(network.as_bytes(), sandbox.as_bytes());
|
||||
assert!(host_platform().is_ok());
|
||||
}
|
||||
|
||||
fn configuration(root: &Path) -> ResolvedDomainConfiguration {
|
||||
let configuration_directory = root.join("configuration");
|
||||
let workspace = root.join("workspace");
|
||||
let runtime = root.join("runtime");
|
||||
fs::create_dir(&configuration_directory).unwrap();
|
||||
fs::create_dir(&workspace).unwrap();
|
||||
fs::create_dir(&runtime).unwrap();
|
||||
let configuration_path = configuration_directory.join("lazaret.toml");
|
||||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n[storage]\ndata_root = \"{}\"\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n[profiles.default]\nimage = \"example.invalid/factory:v1\"\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 8443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"Example.Invalid.\" }}]\n",
|
||||
root.join("data").display()
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
load_trusted_configuration(ConfigurationLoadRequest {
|
||||
explicit_configuration_path: Some(configuration_path),
|
||||
selected_profile: "default".into(),
|
||||
workspace,
|
||||
home: root.join("home"),
|
||||
xdg_config_home: None,
|
||||
xdg_data_home: None,
|
||||
xdg_runtime_dir: runtime,
|
||||
environment: BTreeMap::new(),
|
||||
sandbox_layout: production_sandbox_layout().unwrap(),
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
38
crates/lazaret-host-coordinator/src/sandbox_init.rs
Normal file
38
crates/lazaret-host-coordinator/src/sandbox_init.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use std::ffi::{OsStr, OsString};
|
||||
|
||||
pub const PRIVATE_SANDBOX_INIT_ROLE: &str = "--lazaret-private-sandbox-init-v1";
|
||||
|
||||
#[must_use]
|
||||
pub fn is_private_sandbox_init_role(arguments: &[OsString]) -> bool {
|
||||
arguments.len() == 1 && arguments[0] == OsStr::new(PRIVATE_SANDBOX_INIT_ROLE)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn dispatch_private_sandbox_init(arguments: &[OsString]) -> bool {
|
||||
if !is_private_sandbox_init_role(arguments) {
|
||||
return false;
|
||||
}
|
||||
loop {
|
||||
std::thread::park();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn private_role_dispatch_is_exact_and_closed() {
|
||||
assert!(is_private_sandbox_init_role(&[OsString::from(
|
||||
PRIVATE_SANDBOX_INIT_ROLE
|
||||
)]));
|
||||
assert!(!is_private_sandbox_init_role(&[]));
|
||||
assert!(!is_private_sandbox_init_role(&[
|
||||
OsString::from(PRIVATE_SANDBOX_INIT_ROLE),
|
||||
OsString::from("extra"),
|
||||
]));
|
||||
assert!(!is_private_sandbox_init_role(&[OsString::from(
|
||||
"--private-sandbox-init"
|
||||
)]));
|
||||
}
|
||||
}
|
||||
|
|
@ -577,7 +577,7 @@ fn configuration(tree: &TestTree) -> lazaret_core::configuration::ResolvedDomain
|
|||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n[storage]\ndata_root = \"{}\"\n[profiles.default]\nimage = \"example.invalid/fixture:v1\"\npersistent_dirs = [\"/usr\"]\n",
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n[storage]\ndata_root = \"{}\"\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n[profiles.default]\nimage = \"example.invalid/fixture:v1\"\npersistent_dirs = [\"/usr\"]\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\n",
|
||||
tree.path.join("data").display()
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ impl ProductionFixture {
|
|||
container_id: self.container_id,
|
||||
invoking_uid: getuid().as_raw(),
|
||||
escalation_client,
|
||||
sandbox_init: escalation_client,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +243,7 @@ fn configuration(root: &Path) -> lazaret_core::configuration::ResolvedDomainConf
|
|||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/factory:v1\"\npersistent_dirs = []\n",
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n\n[profiles.default]\nimage = \"example.invalid/factory:v1\"\npersistent_dirs = []\n\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\n",
|
||||
root.join("data").display()
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ impl Fixture {
|
|||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/runtime-fixture:v1\"\npersistent_dirs = [\"/tooling\", \"/fixtures\"]\nenv = {{ PATH = \"env:BENCHMARK_PATH\", CARGO_HOME = \"env:BENCHMARK_CARGO_HOME\", RUSTUP_HOME = \"env:BENCHMARK_RUSTUP_HOME\", CARGO_BUILD_JOBS = \"env:BENCHMARK_CARGO_BUILD_JOBS\" }}\nvolumes = [\n {{ host = \"{}\", container = \"/tooling/usr\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/fixtures/host\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/fixtures/output\", kind = \"directory\", access = \"read-write\" }},\n {{ host = \"{}\", container = \"/fixtures/repository\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/cargo\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/rustup\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/uv-bin\", kind = \"directory\", access = \"read-only\" }},\n]\n",
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n\n[profiles.default]\nimage = \"example.invalid/runtime-fixture:v1\"\npersistent_dirs = [\"/tooling\", \"/fixtures\"]\nenv = {{ PATH = \"env:BENCHMARK_PATH\", CARGO_HOME = \"env:BENCHMARK_CARGO_HOME\", RUSTUP_HOME = \"env:BENCHMARK_RUSTUP_HOME\", CARGO_BUILD_JOBS = \"env:BENCHMARK_CARGO_BUILD_JOBS\" }}\nvolumes = [\n {{ host = \"{}\", container = \"/tooling/usr\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/fixtures/host\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/fixtures/output\", kind = \"directory\", access = \"read-write\" }},\n {{ host = \"{}\", container = \"/fixtures/repository\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/cargo\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/rustup\", kind = \"directory\", access = \"read-only\" }},\n {{ host = \"{}\", container = \"/tooling/uv-bin\", kind = \"directory\", access = \"read-only\" }},\n]\n\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\n",
|
||||
root.join("data").display(),
|
||||
Path::new("/usr").display(),
|
||||
host_root.display(),
|
||||
|
|
@ -708,7 +708,7 @@ impl Fixture {
|
|||
fs::write(
|
||||
&configuration_path,
|
||||
format!(
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[profiles.default]\nimage = \"example.invalid/runtime-fixture:v1\"\npersistent_dirs = []\n",
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n\n[profiles.default]\nimage = \"example.invalid/runtime-fixture:v1\"\npersistent_dirs = []\n\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\n",
|
||||
root.join("data").display()
|
||||
),
|
||||
)?;
|
||||
|
|
@ -1026,7 +1026,7 @@ fn network_configuration(
|
|||
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",
|
||||
"schema = \"lazaret-trusted-host-config-v1\"\n\n[storage]\ndata_root = \"{}\"\n\n[network]\ndefault_allow = [\"192.0.2.10/32\"]\n\n[profiles.default]\nimage = \"example.invalid/network-fixture:v1\"\npersistent_dirs = []\n\n[profiles.default.readiness]\ndenied = [{{ address = \"10.20.30.40\", port = 443 }}]\npermitted = [{{ address = \"192.0.2.10\", port = 443 }}]\ndns = [{{ resolver = \"198.51.100.53\", query = \"example.invalid\" }}]\n",
|
||||
root.join("data").display()
|
||||
),
|
||||
)?;
|
||||
|
|
|
|||
Loading…
Reference in a new issue