Bind production cold start to installed Lazaret authorities
This commit is contained in:
parent
c0b139256e
commit
bc04112c77
14 changed files with 573 additions and 25 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1245,6 +1245,7 @@ dependencies = [
|
||||||
"lazaret-core",
|
"lazaret-core",
|
||||||
"lazaret-escalation-client",
|
"lazaret-escalation-client",
|
||||||
"lazaret-linux",
|
"lazaret-linux",
|
||||||
|
"lazaret-prepared-lower",
|
||||||
"lazaret-runtime-supervisor",
|
"lazaret-runtime-supervisor",
|
||||||
"lazaret-state-anchor",
|
"lazaret-state-anchor",
|
||||||
"lazaret-storage-catalog",
|
"lazaret-storage-catalog",
|
||||||
|
|
@ -1335,6 +1336,7 @@ dependencies = [
|
||||||
"lazaret-linux",
|
"lazaret-linux",
|
||||||
"postcard",
|
"postcard",
|
||||||
"redb",
|
"redb",
|
||||||
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ ed25519-dalek.workspace = true
|
||||||
getrandom.workspace = true
|
getrandom.workspace = true
|
||||||
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
|
lazaret-core = { version = "=0.0.0", path = "../lazaret-core" }
|
||||||
lazaret-linux = { version = "=0.0.0", path = "../lazaret-linux" }
|
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-runtime-supervisor = { version = "=0.0.0", path = "../lazaret-runtime-supervisor" }
|
||||||
lazaret-state-anchor = { version = "=0.0.0", path = "../lazaret-state-anchor" }
|
lazaret-state-anchor = { version = "=0.0.0", path = "../lazaret-state-anchor" }
|
||||||
lazaret-storage-catalog = { version = "=0.0.0", path = "../lazaret-storage-catalog" }
|
lazaret-storage-catalog = { version = "=0.0.0", path = "../lazaret-storage-catalog" }
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,7 @@ pub mod escalation;
|
||||||
pub mod lifecycle;
|
pub mod lifecycle;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod production;
|
pub mod production;
|
||||||
|
pub mod production_selector;
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
pub mod sandbox_init;
|
||||||
pub mod state_anchor;
|
pub mod state_anchor;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
let arguments = std::env::args_os().skip(1).collect::<Vec<_>>();
|
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 Some(result) = lazaret_linux::process::dispatch_private_process_role(&arguments) {
|
||||||
if let Err(error) = result {
|
if let Err(error) = result {
|
||||||
eprintln!("private process role failed: {error}");
|
eprintln!("private process role failed: {error}");
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ pub struct ProductionColdStartInputs<'a> {
|
||||||
pub container_id: ContainerId,
|
pub container_id: ContainerId,
|
||||||
pub invoking_uid: u32,
|
pub invoking_uid: u32,
|
||||||
pub escalation_client: &'a File,
|
pub escalation_client: &'a File,
|
||||||
|
pub sandbox_init: &'a File,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct BootstrapOwnerGuard {
|
pub struct BootstrapOwnerGuard {
|
||||||
|
|
@ -138,6 +139,7 @@ impl<'a> ProductionColdStart<'a> {
|
||||||
inputs.configuration.runtime_domain_root(),
|
inputs.configuration.runtime_domain_root(),
|
||||||
inputs.container_id,
|
inputs.container_id,
|
||||||
inputs.escalation_client,
|
inputs.escalation_client,
|
||||||
|
inputs.sandbox_init,
|
||||||
inputs.invoking_uid,
|
inputs.invoking_uid,
|
||||||
)
|
)
|
||||||
.map_err(|error| failure(format!("runtime staging: {error}")))?;
|
.map_err(|error| failure(format!("runtime staging: {error}")))?;
|
||||||
|
|
@ -293,6 +295,7 @@ impl RuntimeStaging {
|
||||||
runtime_domain: &ConfinedPathPlan,
|
runtime_domain: &ConfinedPathPlan,
|
||||||
container_id: ContainerId,
|
container_id: ContainerId,
|
||||||
escalation_client: &File,
|
escalation_client: &File,
|
||||||
|
sandbox_init: &File,
|
||||||
invoking_uid: u32,
|
invoking_uid: u32,
|
||||||
) -> Result<Self, ProductionColdStartError> {
|
) -> Result<Self, ProductionColdStartError> {
|
||||||
let root = runtime_domain.open_authority().map_err(failure)?;
|
let root = runtime_domain.open_authority().map_err(failure)?;
|
||||||
|
|
@ -325,11 +328,15 @@ impl RuntimeStaging {
|
||||||
bundle_identity,
|
bundle_identity,
|
||||||
control_identity,
|
control_identity,
|
||||||
};
|
};
|
||||||
staging.populate_control(escalation_client)?;
|
staging.populate_control(escalation_client, sandbox_init)?;
|
||||||
Ok(staging)
|
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;
|
let invoking_uid = fstat(&self.control).map_err(failure)?.st_uid;
|
||||||
drop(open_or_create_private_directory(
|
drop(open_or_create_private_directory(
|
||||||
&self.control,
|
&self.control,
|
||||||
|
|
@ -352,15 +359,10 @@ impl RuntimeStaging {
|
||||||
true,
|
true,
|
||||||
)?);
|
)?);
|
||||||
let bin = open_or_create_private_directory(&self.control, "bin", invoking_uid, true)?;
|
let bin = open_or_create_private_directory(&self.control, "bin", invoking_uid, true)?;
|
||||||
let source = escalation_client
|
require_executable_regular_file(escalation_client, "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(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
copy_descriptor(escalation_client, &bin, "lazaret-escalate", invoking_uid)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -414,6 +416,21 @@ fn copy_descriptor(
|
||||||
require_private_file(&destination, invoking_uid, 0o700)
|
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)]
|
#[derive(Clone, Copy)]
|
||||||
struct FileIdentity {
|
struct FileIdentity {
|
||||||
device: u64,
|
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(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
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()
|
tree.path.join("data").display()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -199,6 +199,7 @@ impl ProductionFixture {
|
||||||
container_id: self.container_id,
|
container_id: self.container_id,
|
||||||
invoking_uid: getuid().as_raw(),
|
invoking_uid: getuid().as_raw(),
|
||||||
escalation_client,
|
escalation_client,
|
||||||
|
sandbox_init: escalation_client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,7 +243,7 @@ fn configuration(root: &Path) -> lazaret_core::configuration::ResolvedDomainConf
|
||||||
fs::write(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
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()
|
root.join("data").display()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -629,7 +629,7 @@ impl Fixture {
|
||||||
fs::write(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
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(),
|
root.join("data").display(),
|
||||||
Path::new("/usr").display(),
|
Path::new("/usr").display(),
|
||||||
host_root.display(),
|
host_root.display(),
|
||||||
|
|
@ -708,7 +708,7 @@ impl Fixture {
|
||||||
fs::write(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
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()
|
root.join("data").display()
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -1026,7 +1026,7 @@ fn network_configuration(
|
||||||
fs::write(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
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()
|
root.join("data").display()
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ pub const CREDENTIALS_DESTINATION: &str = "/run/lazaret/credentials";
|
||||||
pub const MAILBOX_REQUESTS_DESTINATION: &str = "/run/lazaret/mailbox/requests";
|
pub const MAILBOX_REQUESTS_DESTINATION: &str = "/run/lazaret/mailbox/requests";
|
||||||
pub const MAILBOX_RESULTS_DESTINATION: &str = "/run/lazaret/mailbox/results";
|
pub const MAILBOX_RESULTS_DESTINATION: &str = "/run/lazaret/mailbox/results";
|
||||||
pub const ESCALATION_CLIENT_DESTINATION: &str = "/run/lazaret/bin/lazaret-escalate";
|
pub const ESCALATION_CLIENT_DESTINATION: &str = "/run/lazaret/bin/lazaret-escalate";
|
||||||
|
pub const SANDBOX_INIT_DESTINATION: &str = "/run/lazaret/bin/lazaret-sandbox-init";
|
||||||
pub const TERMINFO_ROOT_DESTINATION: &str = "/run/lazaret/terminfo";
|
pub const TERMINFO_ROOT_DESTINATION: &str = "/run/lazaret/terminfo";
|
||||||
pub const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
|
pub const NETWORK_NAMESPACE_PATH: &str = "/proc/self/ns/net";
|
||||||
|
|
||||||
|
|
@ -678,6 +679,10 @@ fn handoff_runtime_sources(
|
||||||
container.join("bin/lazaret-escalate"),
|
container.join("bin/lazaret-escalate"),
|
||||||
HandoffPathType::RegularFile,
|
HandoffPathType::RegularFile,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
container.join("bin/lazaret-sandbox-init"),
|
||||||
|
HandoffPathType::RegularFile,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(relative_path, expected_type)| {
|
.map(|(relative_path, expected_type)| {
|
||||||
|
|
@ -851,16 +856,22 @@ fn build_linux() -> Result<oci_spec::runtime::Linux, SandboxBuildError> {
|
||||||
.map_err(oci_error),
|
.map_err(oci_error),
|
||||||
))
|
))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let identity_mapping = LinuxIdMappingBuilder::default()
|
let uid_mapping = LinuxIdMappingBuilder::default()
|
||||||
.container_id(0_u32)
|
.container_id(0_u32)
|
||||||
.host_id(0_u32)
|
.host_id(rustix::process::getuid().as_raw())
|
||||||
|
.size(1_u32)
|
||||||
|
.build()
|
||||||
|
.map_err(oci_error)?;
|
||||||
|
let gid_mapping = LinuxIdMappingBuilder::default()
|
||||||
|
.container_id(0_u32)
|
||||||
|
.host_id(rustix::process::getgid().as_raw())
|
||||||
.size(1_u32)
|
.size(1_u32)
|
||||||
.build()
|
.build()
|
||||||
.map_err(oci_error)?;
|
.map_err(oci_error)?;
|
||||||
LinuxBuilder::default()
|
LinuxBuilder::default()
|
||||||
.namespaces(private_namespaces)
|
.namespaces(private_namespaces)
|
||||||
.uid_mappings(vec![identity_mapping])
|
.uid_mappings(vec![uid_mapping])
|
||||||
.gid_mappings(vec![identity_mapping])
|
.gid_mappings(vec![gid_mapping])
|
||||||
.rootfs_propagation("rprivate")
|
.rootfs_propagation("rprivate")
|
||||||
.build()
|
.build()
|
||||||
.map_err(oci_error)
|
.map_err(oci_error)
|
||||||
|
|
@ -1021,6 +1032,12 @@ fn control_mounts(control_sources: &Path) -> Result<Vec<Mount>, SandboxBuildErro
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"bin/lazaret-sandbox-init",
|
||||||
|
SANDBOX_INIT_DESTINATION,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(source, destination, read_only, no_exec)| {
|
.map(|(source, destination, read_only, no_exec)| {
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ use lazaret_core::identity::{ContainerId, DeviceId, InodeId, NamespaceIdentity};
|
||||||
use lazaret_runtime_supervisor::sandbox::{
|
use lazaret_runtime_supervisor::sandbox::{
|
||||||
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, HostTerminal,
|
CREDENTIALS_DESTINATION, ESCALATION_CLIENT_DESTINATION, HostTerminal,
|
||||||
MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, RootProcess,
|
MAILBOX_REQUESTS_DESTINATION, MAILBOX_RESULTS_DESTINATION, NETWORK_NAMESPACE_PATH, RootProcess,
|
||||||
SandboxBuildErrorKind, SandboxBuildRequest, TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION,
|
SANDBOX_INIT_DESTINATION, SandboxBuildErrorKind, SandboxBuildRequest,
|
||||||
build_sandbox_bundle, production_sandbox_layout, stage_host_terminal,
|
TERMINFO_ROOT_DESTINATION, WORKSPACE_DESTINATION, build_sandbox_bundle,
|
||||||
|
production_sandbox_layout, stage_host_terminal,
|
||||||
};
|
};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
@ -41,6 +42,9 @@ impl Fixture {
|
||||||
[storage]
|
[storage]
|
||||||
data_root = "{}"
|
data_root = "{}"
|
||||||
|
|
||||||
|
[network]
|
||||||
|
default_allow = ["192.0.2.10/32"]
|
||||||
|
|
||||||
[profiles.default]
|
[profiles.default]
|
||||||
image = "example.invalid/agent:v1"
|
image = "example.invalid/agent:v1"
|
||||||
persistent_dirs = ["/root"]
|
persistent_dirs = ["/root"]
|
||||||
|
|
@ -50,6 +54,11 @@ volumes = [
|
||||||
{{ host = "{{profile_dir}}/read-write", container = "/root/read-write", kind = "directory", access = "read-write", create = true }},
|
{{ host = "{{profile_dir}}/read-write", container = "/root/read-write", kind = "directory", access = "read-write", create = true }},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profiles.default.readiness]
|
||||||
|
denied = [{{ address = "10.20.30.40", port = 443 }}]
|
||||||
|
permitted = [{{ address = "192.0.2.10", port = 443 }}]
|
||||||
|
dns = [{{ resolver = "198.51.100.53", query = "example.invalid" }}]
|
||||||
|
|
||||||
[profiles.unselected]
|
[profiles.unselected]
|
||||||
image = "example.invalid/unselected:v1"
|
image = "example.invalid/unselected:v1"
|
||||||
env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
||||||
|
|
@ -172,6 +181,13 @@ env = {{ UNSELECTED_SECRET = "env:UNSELECTED_HOST_SECRET" }}
|
||||||
b"executable",
|
b"executable",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
fs::write(
|
||||||
|
runtime.join(format!(
|
||||||
|
"sandbox-control/{container}/bin/lazaret-sandbox-init"
|
||||||
|
)),
|
||||||
|
b"executable",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bundle_config_path(&self) -> PathBuf {
|
fn bundle_config_path(&self) -> PathBuf {
|
||||||
|
|
@ -274,11 +290,15 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
||||||
assert_eq!(namespaces[5]["path"], NETWORK_NAMESPACE_PATH);
|
assert_eq!(namespaces[5]["path"], NETWORK_NAMESPACE_PATH);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
specification["linux"]["uidMappings"],
|
specification["linux"]["uidMappings"],
|
||||||
serde_json::json!([{ "hostID": 0, "containerID": 0, "size": 1 }])
|
serde_json::json!([{
|
||||||
|
"hostID": rustix::process::getuid().as_raw(), "containerID": 0, "size": 1
|
||||||
|
}])
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
specification["linux"]["gidMappings"],
|
specification["linux"]["gidMappings"],
|
||||||
serde_json::json!([{ "hostID": 0, "containerID": 0, "size": 1 }])
|
serde_json::json!([{
|
||||||
|
"hostID": rustix::process::getgid().as_raw(), "containerID": 0, "size": 1
|
||||||
|
}])
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(first.command().program(), OsStr::new("runsc"));
|
assert_eq!(first.command().program(), OsStr::new("runsc"));
|
||||||
|
|
@ -328,6 +348,48 @@ fn deterministic_snapshot_has_only_private_namespaces_and_fixed_runtime_shape()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn product_init_role_has_exact_terminal_neutral_rootless_bundle_shape() {
|
||||||
|
let fixture = Fixture::new();
|
||||||
|
let root = RootProcess::new(
|
||||||
|
SANDBOX_INIT_DESTINATION,
|
||||||
|
["--lazaret-private-sandbox-init-v1".to_owned()],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let specification = value(&build_sandbox_bundle(fixture.request(root)).unwrap());
|
||||||
|
assert_eq!(specification["process"]["terminal"], false);
|
||||||
|
assert_eq!(
|
||||||
|
specification["process"]["args"],
|
||||||
|
serde_json::json!([
|
||||||
|
SANDBOX_INIT_DESTINATION,
|
||||||
|
"--lazaret-private-sandbox-init-v1"
|
||||||
|
])
|
||||||
|
);
|
||||||
|
assert_eq!(specification["process"]["user"]["uid"], 0);
|
||||||
|
assert_eq!(specification["process"]["user"]["gid"], 0);
|
||||||
|
assert_eq!(
|
||||||
|
specification["linux"]["uidMappings"],
|
||||||
|
serde_json::json!([{
|
||||||
|
"hostID": rustix::process::getuid().as_raw(), "containerID": 0, "size": 1
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mounts(&specification)[SANDBOX_INIT_DESTINATION]["source"],
|
||||||
|
host_path(
|
||||||
|
fixture
|
||||||
|
.configuration
|
||||||
|
.runtime_domain_root()
|
||||||
|
.canonical_label()
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.join("sandbox-control")
|
||||||
|
.join("11".repeat(32))
|
||||||
|
.join("bin/lazaret-sandbox-init")
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn selected_environment_volume_modes_and_host_terminal_are_preserved() {
|
fn selected_environment_volume_modes_and_host_terminal_are_preserved() {
|
||||||
let fixture = Fixture::new();
|
let fixture = Fixture::new();
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ fn configuration(tree: &TestTree) -> lazaret_core::configuration::ResolvedDomain
|
||||||
fs::write(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
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()
|
tree.path.join("data").display()
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ fn configuration(
|
||||||
fs::write(
|
fs::write(
|
||||||
&configuration_path,
|
&configuration_path,
|
||||||
format!(
|
format!(
|
||||||
"schema = \"lazaret-trusted-host-config-v1\"\n[storage]\ndata_root = \"{}\"\n[profiles.default]\nimage = \"example.invalid/fixture:v1\"\npersistent_dirs = [\"/usr\", \"/etc\"]\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\", \"/etc\"]\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()
|
tree.path.join("data").display()
|
||||||
),
|
),
|
||||||
)?;
|
)?;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue