Refuse inexact production selector authorities

This commit is contained in:
ms2k 2026-07-16 21:06:02 +09:00
commit 4cf7a5e595

View file

@ -13,6 +13,7 @@ use lazaret_core::identity::{
use lazaret_core::network::{DnsQueryExpectation, ReachabilityExpectation};
use lazaret_core::records::{DurableDecision, Generation, IpAddress, PreparedArtifact};
use lazaret_linux::filesystem::namespace_identity;
use lazaret_linux::platform::{PlatformObservation, check_platform};
use lazaret_prepared_lower::{
IdMap, IdMapRange, OciRegistryTransport, Platform, PreparedLowerStore, RegistryCredentials,
};
@ -48,6 +49,7 @@ pub struct InstalledProductPaths<'a> {
}
pub struct ProductionSelection {
store: Arc<HostCoordinatorDomainStore>,
domain_id: DomainId,
configuration_fingerprint: ConfigurationFingerprint,
catalog: Catalog,
@ -68,7 +70,6 @@ impl ProductionSelection {
/// 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
@ -77,7 +78,7 @@ impl ProductionSelection {
return Err(failure("selected cold start does not match configuration"));
}
Ok(ProductionColdStartInputs {
store,
store: Arc::clone(&self.store),
catalog: &self.catalog,
configuration,
generation: self.generation.clone(),
@ -105,9 +106,30 @@ impl ProductionSelection {
/// changed Storage Root authority, exhausted identities, and configuration-policy mismatch.
pub async fn select_production_cold_start(
configuration: &ResolvedDomainConfiguration,
store: &HostCoordinatorDomainStore,
store: Arc<HostCoordinatorDomainStore>,
product_paths: InstalledProductPaths<'_>,
) -> Result<ProductionSelection, ProductionSelectionError> {
let kernel_release = std::fs::read_to_string("/proc/sys/kernel/osrelease").map_err(failure)?;
select_production_cold_start_for_platform(
configuration,
store,
product_paths,
&PlatformObservation {
operating_system: std::env::consts::OS,
architecture: std::env::consts::ARCH,
kernel_release: kernel_release.trim(),
},
)
.await
}
async fn select_production_cold_start_for_platform(
configuration: &ResolvedDomainConfiguration,
store: Arc<HostCoordinatorDomainStore>,
product_paths: InstalledProductPaths<'_>,
observation: &PlatformObservation<'_>,
) -> Result<ProductionSelection, ProductionSelectionError> {
check_platform(observation).map_err(failure)?;
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)?;
@ -122,7 +144,7 @@ pub async fn select_production_cold_start(
.pull_confined(
&configuration.fingerprint_record().image,
&RegistryCredentials::Anonymous,
&host_platform()?,
&Platform::linux("amd64"),
configuration.storage_root(),
random_id()?,
)
@ -137,16 +159,7 @@ pub async fn select_production_cold_start(
.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 next_generation = next_generation(&store)?;
let (coordinator_id, network_sidecar_id, sandbox_id) = distinct_generation_ids()?;
let generation = Generation {
domain_id: configuration.domain_id(),
@ -161,6 +174,7 @@ pub async fn select_production_cold_start(
let (resolver_addresses, network_probe, reachability) =
select_network_readiness(configuration, &generation, namespace);
Ok(ProductionSelection {
store,
domain_id: configuration.domain_id(),
configuration_fingerprint: configuration.configuration_fingerprint(),
catalog,
@ -175,6 +189,19 @@ pub async fn select_production_cold_start(
})
}
fn next_generation(store: &HostCoordinatorDomainStore) -> Result<u64, ProductionSelectionError> {
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"))
}
fn select_network_readiness(
configuration: &ResolvedDomainConfiguration,
generation: &Generation,
@ -270,16 +297,6 @@ fn rootless_id_map() -> IdMap {
}
}
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 {
@ -307,12 +324,20 @@ fn random_id() -> Result<[u8; 32], ProductionSelectionError> {
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::fs;
use std::fs::{self, OpenOptions};
use std::future::Future;
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use std::task::{Context, Poll, Waker};
use lazaret_core::configuration::{ConfigurationLoadRequest, load_trusted_configuration};
use lazaret_core::identity::{DeviceId, InodeId, NamespaceIdentity};
use lazaret_core::records::Domain;
use lazaret_linux::filesystem::try_exclusive_flock;
use lazaret_runtime_supervisor::sandbox::production_sandbox_layout;
use crate::domain_store::DomainStoreMutation;
use super::*;
#[test]
@ -370,7 +395,108 @@ mod tests {
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());
}
#[test]
fn unsupported_platforms_refuse_before_installed_or_storage_side_effects() {
for observation in [
PlatformObservation {
operating_system: "linux",
architecture: "x86_64",
kernel_release: "6.8.14",
},
PlatformObservation {
operating_system: "linux",
architecture: "aarch64",
kernel_release: "6.9.0",
},
] {
let tree = tempfile::tempdir().unwrap();
let configuration = configuration(tree.path());
let store = domain_store(&configuration);
let mut future = Box::pin(select_production_cold_start_for_platform(
&configuration,
store,
InstalledProductPaths {
escalation_client: Path::new("/missing-escalation-client"),
sandbox_init: Path::new("/missing-sandbox-init"),
},
&observation,
));
let mut context = Context::from_waker(Waker::noop());
let Poll::Ready(result) = future.as_mut().poll(&mut context) else {
panic!("platform refusal performed asynchronous work");
};
assert!(result.is_err());
let storage = tree.path().join("data");
assert!(!storage.join("storage-catalog.lock").exists());
assert!(!storage.join("catalog.redb").exists());
assert!(!storage.join("prepared-lower").exists());
assert!(!storage.join("image-transports").exists());
}
}
#[test]
fn generation_selection_and_cold_start_retain_one_exact_domain_store() {
let tree = tempfile::tempdir().unwrap();
let configuration = configuration(tree.path());
let first_root = tree.path().join("store-a");
let second_root = tree.path().join("store-b");
fs::create_dir(&first_root).unwrap();
fs::create_dir(&second_root).unwrap();
let first = domain_store_at(&configuration, &first_root);
let second = domain_store_at(&configuration, &second_root);
second
.apply(&[DomainStoreMutation::PutGeneration(Generation {
domain_id: configuration.domain_id(),
generation_id: GenerationId::new(1),
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,
})])
.unwrap();
assert_eq!(next_generation(&first).unwrap(), 1);
assert_eq!(next_generation(&second).unwrap(), 2);
assert!(!Arc::ptr_eq(&first, &second));
}
fn domain_store(
configuration: &ResolvedDomainConfiguration,
) -> Arc<HostCoordinatorDomainStore> {
let root = PathBuf::from(std::ffi::OsString::from_vec(
configuration
.persistent_domain_root()
.canonical_label()
.as_bytes()
.to_vec(),
));
domain_store_at(configuration, &root)
}
fn domain_store_at(
configuration: &ResolvedDomainConfiguration,
root: &Path,
) -> Arc<HostCoordinatorDomainStore> {
let lock = OpenOptions::new()
.read(true)
.write(true)
.create_new(true)
.open(root.join("domain.lock"))
.unwrap();
Arc::new(
HostCoordinatorDomainStore::open(
try_exclusive_flock(lock).unwrap(),
&root.join("domain.redb"),
&Domain {
domain_id: configuration.domain_id(),
profile_name: configuration.selected_profile().to_owned(),
canonical_workspace: configuration.workspace().canonical_label().clone(),
},
)
.unwrap(),
)
}
fn configuration(root: &Path) -> ResolvedDomainConfiguration {