use std::fs::OpenOptions; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::path::PathBuf; use std::sync::Arc; use lazaret_core::identity::{ ConfigurationFingerprint, CoordinatorId, DomainId, GenerationId, HostPath, NetworkSidecarId, SandboxId, }; use lazaret_core::records::{Domain, DurableDecision, Generation, GenerationIdentity}; use lazaret_host_coordinator::control::{PublishedControlFace, TrustAnchor}; use lazaret_host_coordinator::domain_store::{DomainStoreMutation, HostCoordinatorDomainStore}; use lazaret_linux::filesystem::try_exclusive_flock; use rustix::fs::Mode; use rustix::process::{getuid, umask}; struct TestDirectory(PathBuf); impl TestDirectory { fn new() -> Self { let parent = PathBuf::from("/tmp"); let path = parent.join(format!( "lazaret-control-restrictive-umask-{}", std::process::id() )); std::fs::create_dir(&path).unwrap(); std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700)).unwrap(); Self(path) } fn path(&self, name: &str) -> PathBuf { self.0.join(name) } } impl Drop for TestDirectory { fn drop(&mut self) { std::fs::remove_dir_all(&self.0).unwrap(); } } struct UmaskGuard(Mode); impl Drop for UmaskGuard { fn drop(&mut self) { umask(self.0); } } fn bytes(value: u8) -> [u8; 32] { [value; 32] } fn generation_identity() -> GenerationIdentity { GenerationIdentity { agent_profile: "trusted".into(), domain_id: DomainId::new(bytes(1)), generation_id: GenerationId::new(7), configuration_fingerprint: ConfigurationFingerprint::new(bytes(2)), coordinator_id: CoordinatorId::new(bytes(17)), sandbox_id: SandboxId::new(bytes(27)), } } #[test] fn publication_identity_mode_is_exact_under_restrictive_umask() { let directory = TestDirectory::new(); let identity = generation_identity(); let lock = OpenOptions::new() .read(true) .write(true) .create_new(true) .open(directory.path("domain.lock")) .unwrap(); let lock = try_exclusive_flock(lock).unwrap(); let domain = Domain { domain_id: identity.domain_id, profile_name: identity.agent_profile.clone(), canonical_workspace: HostPath::new(b"/workspace/project".to_vec()), }; let store = Arc::new( HostCoordinatorDomainStore::open(lock, &directory.path("domain.redb"), &domain).unwrap(), ); for durable_decision in [ DurableDecision::None, DurableDecision::Preparing, DurableDecision::Published, ] { store .apply(&[DomainStoreMutation::PutGeneration(Generation { domain_id: identity.domain_id, generation_id: identity.generation_id, configuration_fingerprint: identity.configuration_fingerprint, coordinator_id: identity.coordinator_id, network_sidecar_id: NetworkSidecarId::new(bytes(4)), sandbox_id: identity.sandbox_id, durable_decision, })]) .unwrap(); } let _umask = UmaskGuard(umask(Mode::from_raw_mode(0o777))); let face = PublishedControlFace::publish( &directory.0, getuid().as_raw(), TrustAnchor::create(identity).unwrap(), store, ) .unwrap(); assert_eq!( face.identity_path(), directory.path("protocol-identity.postcard") ); let metadata = std::fs::symlink_metadata(face.identity_path()).unwrap(); assert!(metadata.file_type().is_file()); assert_eq!(metadata.uid(), getuid().as_raw()); assert_eq!(metadata.mode() & 0o7777, 0o600); }