Refuse inexact production selector authorities
This commit is contained in:
parent
bc04112c77
commit
4cf7a5e595
1 changed files with 230 additions and 34 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue