use std::collections::BTreeMap; use std::ffi::OsString; use std::fs; use std::os::unix::ffi::OsStringExt; use std::path::PathBuf; use std::sync::atomic::{AtomicU64, Ordering}; use lazaret_core::configuration::{ ConfigurationLoadRequest, SandboxLayoutV1, load_trusted_configuration, }; use lazaret_state_anchor::{ProtectedRuntimeFiles, StateAnchorLayout}; static SEQUENCE: AtomicU64 = AtomicU64::new(0); #[test] fn replaced_runtime_root_is_refused_before_runtime_file_write() { let tree = TestTree::new(); let configuration = configuration(&tree); let layout = StateAnchorLayout::create(&configuration).unwrap(); let runtime_root = PathBuf::from(OsString::from_vec( configuration .runtime_domain_root() .canonical_label() .as_bytes() .to_vec(), )); let moved_root = runtime_root.with_extension("retained"); fs::rename(&runtime_root, &moved_root).unwrap(); let replacement_files = runtime_root.join("state-anchor/runtime-files"); fs::create_dir_all(&replacement_files).unwrap(); fs::write(replacement_files.join("hostname"), b"replacement\n").unwrap(); assert!(layout.revalidate().is_err()); assert!( layout .replace_protected_runtime_files(&ProtectedRuntimeFiles { resolv_conf: b"nameserver 192.0.2.1\n", hosts: b"127.0.0.1 localhost\n", hostname: b"changed\n", }) .is_err() ); assert_eq!( fs::read(replacement_files.join("hostname")).unwrap(), b"replacement\n" ); assert!(!replacement_files.join("resolv.conf").exists()); assert!(!replacement_files.join("hosts").exists()); } struct TestTree { path: PathBuf, } impl TestTree { fn new() -> Self { let path = std::env::temp_dir().join(format!( "lazaret-state-anchor-authority-{}-{}", std::process::id(), SEQUENCE.fetch_add(1, Ordering::Relaxed) )); fs::create_dir(&path).unwrap(); Self { path } } } impl Drop for TestTree { fn drop(&mut self) { fs::remove_dir_all(&self.path).unwrap(); } } fn configuration(tree: &TestTree) -> lazaret_core::configuration::ResolvedDomainConfiguration { let configuration_directory = tree.path.join("configuration"); let workspace = tree.path.join("workspace"); let runtime = tree.path.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[profiles.default]\nimage = \"example.invalid/fixture:v1\"\npersistent_dirs = [\"/usr\"]\n", tree.path.join("data").display() ), ) .unwrap(); let layout = SandboxLayoutV1::from_production_layout([ "/workspace".to_owned(), "/proc".to_owned(), "/sys".to_owned(), "/dev".to_owned(), "/run/lazaret".to_owned(), ]) .unwrap(); load_trusted_configuration(ConfigurationLoadRequest { explicit_configuration_path: Some(configuration_path), selected_profile: "default".to_owned(), workspace, home: tree.path.join("home"), xdg_config_home: None, xdg_data_home: None, xdg_runtime_dir: runtime, environment: BTreeMap::::new(), sandbox_layout: layout, }) .unwrap() }