use std::collections::BTreeMap; use std::fs; use std::os::unix::fs::{MetadataExt, PermissionsExt, symlink}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Barrier}; use lazaret_core::codec::{CodecError, decode, encode}; use lazaret_core::configuration::{ ConfigurationErrorKind, ConfigurationLoadRequest, ConfinedPathPlan, ConfinedPathType, ResolvedDomainConfiguration, SandboxLayoutV1, derive_configuration_fingerprint, derive_domain_id, load_trusted_configuration, parse_trusted_configuration, }; use lazaret_core::identity::HostPath; use lazaret_core::records::{ ConfigurationFingerprintRecordV1, EnvironmentReference, ResolvedAutoApprovalRule, ResolvedDnsProbeTargetV1, ResolvedNetworkProbeTargetV1, ResolvedNetworkReadinessV1, ResolvedVolume, VolumeAccess, VolumeKind, }; use static_assertions::assert_not_impl_any; assert_not_impl_any!(ResolvedDomainConfiguration: serde::Serialize, serde::de::DeserializeOwned); assert_not_impl_any!(SandboxLayoutV1: serde::Serialize, serde::de::DeserializeOwned); assert_not_impl_any!(ConfinedPathPlan: serde::Serialize, serde::de::DeserializeOwned); static NEXT_TREE: AtomicU64 = AtomicU64::new(0); struct TestTree(PathBuf); impl TestTree { fn new() -> Self { let sequence = NEXT_TREE.fetch_add(1, Ordering::Relaxed); let path = std::env::temp_dir().join(format!( "lazaret-configuration-{}-{sequence}", std::process::id() )); fs::create_dir(&path).unwrap(); Self(path) } fn path(&self) -> &Path { &self.0 } } impl Drop for TestTree { fn drop(&mut self) { fs::remove_dir_all(&self.0).unwrap(); } } fn write_configuration(tree: &TestTree, body: &str) -> PathBuf { let directory = tree.path().join("config"); fs::create_dir(&directory).unwrap(); let path = directory.join("config.toml"); fs::write(&path, body).unwrap(); path } fn layout() -> SandboxLayoutV1 { SandboxLayoutV1::from_production_layout([ "/workspace".to_owned(), "/proc".to_owned(), "/sys".to_owned(), "/dev".to_owned(), "/run/lazaret".to_owned(), ]) .unwrap() } fn request( tree: &TestTree, config: PathBuf, environment: BTreeMap, ) -> ConfigurationLoadRequest { let workspace = tree.path().join("workspace"); let runtime = tree.path().join("runtime"); fs::create_dir_all(&workspace).unwrap(); fs::create_dir_all(&runtime).unwrap(); ConfigurationLoadRequest { explicit_configuration_path: Some(config), selected_profile: "default".into(), workspace, home: tree.path().join("home"), xdg_config_home: None, xdg_data_home: None, xdg_runtime_dir: runtime, environment, sandbox_layout: layout(), } } fn valid_configuration(tree: &TestTree) -> String { format!( r#"schema = "lazaret-trusted-host-config-v1" [storage] data_root = "{}" [network] default_allow = ["192.0.2.4/24", "198.51.100.53/32"] [profiles.default] image = "example.invalid/agent:v1" persistent_dirs = ["/root"] allow_egress = ["10.1.2.3/24"] env = {{ TOKEN = "env:HOST_TOKEN" }} volumes = [ {{ host = "{{profile_dir}}/agent-state", container = "/root/.local/state/agent", kind = "directory", access = "read-write", create = true }}, ] [profiles.default.readiness] denied = [{{ address = "10.2.3.4", port = 443 }}] permitted = [{{ address = "192.0.2.4", port = 443 }}] dns = [{{ resolver = "198.51.100.53", query = "example.invalid" }}] [profiles.default.escalation] enabled = false "#, tree.path().join("data").display() ) } #[test] fn strict_parser_applies_defaults_and_rejects_every_unknown_level() { let minimal = r#"schema = "lazaret-trusted-host-config-v1" [profiles.default] image = "example.invalid/agent:v1" "#; let parsed = parse_trusted_configuration(minimal.as_bytes()).unwrap(); let profile = &parsed.profiles()["default"]; assert!(profile.isolate_gitignored); assert!(profile.persistent_dirs.is_empty()); assert!(profile.allow_egress.is_empty()); assert!(profile.deny_egress.is_empty()); assert!(profile.env.is_empty()); assert!(profile.volumes.is_empty()); assert!(!profile.escalation.enabled); assert!(profile.escalation.auto_approve.is_empty()); assert!(parsed.storage().data_root.is_none()); assert!(parsed.network().default_allow.is_empty()); assert!(parsed.network().default_deny.is_empty()); for invalid in [ minimal.replace("schema", "unknown"), format!("{minimal}unknown = true\n"), minimal.replace( "image = \"example.invalid/agent:v1\"", "image = \"example.invalid/agent:v1\"\nunknown = true", ), minimal.replace( "example.invalid/agent:v1", "https://example.invalid/agent:v1", ), minimal.replace("example.invalid/agent:v1", ""), minimal.replace("-v1", "-v2"), ] { assert!(parse_trusted_configuration(invalid.as_bytes()).is_err()); } let duplicate = format!("{minimal}schema = \"lazaret-trusted-host-config-v1\"\n"); assert!(parse_trusted_configuration(duplicate.as_bytes()).is_err()); for invalid in [ format!("{minimal}\n[storage]\nunknown = true\n"), format!("{minimal}\n[network]\nunknown = true\n"), r#"schema = "lazaret-trusted-host-config-v1" [profiles.default] image = "example.invalid/agent:v1" volumes = [{ host = "/source", container = "/root/source", kind = "directory", unknown = true }] "# .into(), format!("{minimal}\n[profiles.default.escalation]\nunknown = true\n"), format!( "{minimal}\n[profiles.default.escalation]\nenabled = true\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"/bin/true\"\nunknown = true\n" ), r#"schema = "lazaret-trusted-host-config-v1" [profiles.default] image = "example.invalid/agent:v1" volumes = [{ host = "/source", container = "/root/source", kind = "socket" }] "# .into(), ] { assert!(parse_trusted_configuration(invalid.as_bytes()).is_err()); } } #[test] fn sandbox_layout_is_closed_normalized_and_requires_workspace() { assert!(SandboxLayoutV1::from_production_layout(["/proc".into()]).is_err()); assert!( SandboxLayoutV1::from_production_layout(["/workspace".into(), "/workspace".into()]) .is_err() ); for invalid in ["workspace", "/", "/workspace/", "/workspace/../control"] { assert!( SandboxLayoutV1::from_production_layout(["/workspace".into(), invalid.into(),]) .is_err() ); } } #[test] fn repository_example_loads_with_ipv4_normalization_and_private_denies() { let tree = TestTree::new(); let config = write_configuration(&tree, include_str!("../../../docs/example.toml")); let resolved = load_trusted_configuration(request(&tree, config, BTreeMap::new())).unwrap(); assert_eq!(resolved.selected_profile(), "default"); assert!( resolved .effective_allow_prefixes() .iter() .any(|prefix| prefix.address() == [192, 0, 2, 53] && prefix.prefix_length() == 32) ); for (address, length) in [ ([10, 0, 0, 0], 8), ([172, 16, 0, 0], 12), ([192, 168, 0, 0], 16), ] { assert!( resolved .effective_deny_prefixes() .iter() .any(|prefix| prefix.address() == address && prefix.prefix_length() == length) ); } } #[test] fn explicit_tilde_configuration_path_expands_only_against_home() { let tree = TestTree::new(); let home = tree.path().join("home"); fs::create_dir(&home).unwrap(); fs::set_permissions(&home, fs::Permissions::from_mode(0o700)).unwrap(); fs::write(home.join("trusted.toml"), valid_configuration(&tree)).unwrap(); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let resolved = load_trusted_configuration(request(&tree, PathBuf::from("~/trusted.toml"), environment)) .unwrap(); assert_eq!(resolved.selected_profile(), "default"); } #[test] fn nonselected_profiles_are_decoded_but_do_not_resolve_host_authority() { let tree = TestTree::new(); let body = format!( "{}\n[profiles.other]\nimage = \"example.invalid/other:v1\"\nenv = {{ SECRET = \"env:UNSET\" }}\nvolumes = [{{ host = \"/does/not/exist\", container = \"/root/missing\", kind = \"directory\" }}]\n", valid_configuration(&tree) ); let config = write_configuration(&tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); assert!(load_trusted_configuration(request(&tree, config, environment)).is_ok()); } #[test] fn nonselected_profiles_cannot_hide_invalid_approval_semantics() { for other_profile in [ r#"[profiles.other] image = "example.invalid/other:v1" [[profiles.other.escalation.auto_approve]] executable = "/bin/true" "#, r#"[profiles.other] image = "example.invalid/other:v1" [profiles.other.escalation] enabled = true [[profiles.other.escalation.auto_approve]] executable = "/bin/true" allow_extra_args = true "#, ] { let document = format!( "schema = \"lazaret-trusted-host-config-v1\"\n[profiles.default]\nimage = \"example.invalid/default:v1\"\n{other_profile}" ); let error = parse_trusted_configuration(document.as_bytes()).unwrap_err(); assert_eq!(error.kind(), ConfigurationErrorKind::InvalidAutoApproval); } } #[test] fn selected_profile_loads_into_confined_nonserializable_authority() { let tree = TestTree::new(); let config = write_configuration(&tree, &valid_configuration(&tree)); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "top-secret".into()); let resolved = load_trusted_configuration(request(&tree, config, environment)).unwrap(); assert_eq!(resolved.selected_profile(), "default"); assert!( resolved .effective_allow_prefixes() .iter() .any(|prefix| prefix.address() == [192, 0, 2, 0] && prefix.prefix_length() == 24) ); assert!( resolved .effective_deny_prefixes() .iter() .any(|prefix| prefix.address() == [10, 0, 0, 0] && prefix.prefix_length() == 8) ); assert_eq!( resolved.ephemeral_environment()["TOKEN"].expose().to_str(), Some("top-secret") ); assert!(!format!("{resolved:?}").contains("top-secret")); assert_eq!(resolved.volumes().len(), 1); resolved.volumes()[0].open_source().unwrap(); assert_eq!( fs::metadata(tree.path().join("data")) .unwrap() .permissions() .mode() & 0o777, 0o700 ); } #[test] fn environment_values_never_change_the_fingerprint() { let tree = TestTree::new(); let config = write_configuration(&tree, &valid_configuration(&tree)); let mut first = BTreeMap::new(); first.insert("HOST_TOKEN".into(), "first-secret".into()); let first = load_trusted_configuration(request(&tree, config.clone(), first)).unwrap(); let mut second = BTreeMap::new(); second.insert("HOST_TOKEN".into(), "second-secret".into()); let second = load_trusted_configuration(request(&tree, config, second)).unwrap(); assert_eq!( first.configuration_fingerprint(), second.configuration_fingerprint() ); } #[test] fn ignored_routing_is_explicit_and_fingerprinted() { let first_tree = TestTree::new(); let first_config = write_configuration(&first_tree, &valid_configuration(&first_tree)); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let first = load_trusted_configuration(request(&first_tree, first_config, environment)).unwrap(); assert!(first.fingerprint_record().isolate_gitignored); let second_tree = TestTree::new(); let body = valid_configuration(&second_tree).replace( "image = \"example.invalid/agent:v1\"", "image = \"example.invalid/agent:v1\"\nisolate_gitignored = false", ); let second_config = write_configuration(&second_tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let second = load_trusted_configuration(request(&second_tree, second_config, environment)).unwrap(); assert!(!second.fingerprint_record().isolate_gitignored); let disabled = derive_configuration_fingerprint(second.fingerprint_record()).unwrap(); let mut enabled_record = second.fingerprint_record().clone(); enabled_record.isolate_gitignored = true; assert_ne!( disabled, derive_configuration_fingerprint(&enabled_record).unwrap() ); } #[test] fn retained_ancestor_authority_survives_path_replacement() { let tree = TestTree::new(); let config = write_configuration(&tree, &valid_configuration(&tree)); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let resolved = load_trusted_configuration(request(&tree, config, environment)).unwrap(); let original_source = tree.path().join("data/profiles/default/agent-state"); let original_inode = fs::metadata(&original_source).unwrap().ino(); let profile = tree.path().join("data/profiles/default"); let displaced = tree.path().join("data/profiles/displaced"); fs::rename(&profile, &displaced).unwrap(); fs::create_dir(&profile).unwrap(); fs::set_permissions(&profile, fs::Permissions::from_mode(0o700)).unwrap(); fs::create_dir(profile.join("agent-state")).unwrap(); let replacement_inode = fs::metadata(profile.join("agent-state")).unwrap().ino(); let opened = resolved.volumes()[0].open_source().unwrap(); let opened_inode = rustix::fs::fstat(&opened).unwrap().st_ino; assert_eq!(opened_inode, original_inode); assert_ne!(opened_inode, replacement_inode); } #[test] fn consumer_api_is_descriptor_relative_and_refuses_final_identity_drift() { let tree = TestTree::new(); let config = write_configuration(&tree, &valid_configuration(&tree)); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let load_request = request(&tree, config, environment); let nested = load_request.workspace.join("nested"); fs::create_dir(&nested).unwrap(); fs::write(nested.join("data"), b"validated").unwrap(); symlink("data", nested.join("alias")).unwrap(); let expected_inode = fs::metadata(nested.join("data")).unwrap().ino(); let resolved = load_trusted_configuration(load_request).unwrap(); let opened = resolved .workspace() .open_beneath(Path::new("nested/data"), ConfinedPathType::RegularFile) .unwrap(); assert_eq!(rustix::fs::fstat(&opened).unwrap().st_ino, expected_inode); for refused in ["../outside", "/absolute", "nested/./data", "nested/alias"] { assert!( resolved .workspace() .open_beneath(Path::new(refused), ConfinedPathType::RegularFile) .is_err(), "{refused} must not escape or redirect authority" ); } let workspace = tree.path().join("workspace"); fs::rename(&workspace, tree.path().join("displaced-workspace")).unwrap(); fs::create_dir(&workspace).unwrap(); assert!(resolved.workspace().open_authority().is_err()); let source = tree.path().join("data/profiles/default/agent-state"); fs::rename(&source, tree.path().join("displaced-agent-state")).unwrap(); fs::create_dir(&source).unwrap(); assert!(resolved.volumes()[0].open_source().is_err()); } #[test] fn auto_approval_uses_only_the_canonical_direct_vector_and_safe_object() { let tree = TestTree::new(); let binary_directory = tree.path().join("bin"); fs::create_dir(&binary_directory).unwrap(); let executable = binary_directory.join("tool"); fs::write(&executable, b"#!/bin/sh\nexit 0\n").unwrap(); fs::set_permissions(&executable, fs::Permissions::from_mode(0o700)).unwrap(); let body = valid_configuration(&tree).replace( "[profiles.default.escalation]\nenabled = false", &format!( "[profiles.default.escalation]\nenabled = true\n\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"{}\"\nargs = [\"fixed\"]\nallow_extra_args = false", executable.display() ), ); let config = write_configuration(&tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let resolved = load_trusted_configuration(request(&tree, config, environment)).unwrap(); let rule = &resolved.auto_approval_rules()[0]; let canonical = fs::canonicalize(&executable) .unwrap() .to_str() .unwrap() .as_bytes() .to_vec(); assert!(rule.matches_command_argv(&[canonical.clone(), b"fixed".to_vec()])); assert!(!rule.matches_command_argv(&[b"tool".to_vec(), b"fixed".to_vec()])); assert!(!rule.matches_command_argv(std::slice::from_ref(&canonical))); assert!( !rule.matches_command_argv(&[canonical.clone(), b"fixed".to_vec(), b"extra".to_vec(),]) ); assert!(!rule.matches_command_argv(&[canonical, b"fixed\0extra".to_vec()])); let prefix_tree = TestTree::new(); let binary_directory = prefix_tree.path().join("bin"); fs::create_dir(&binary_directory).unwrap(); let executable = binary_directory.join("tool"); fs::write(&executable, b"#!/bin/sh\nexit 0\n").unwrap(); fs::set_permissions(&executable, fs::Permissions::from_mode(0o700)).unwrap(); let body = valid_configuration(&prefix_tree).replace( "[profiles.default.escalation]\nenabled = false", &format!( "[profiles.default.escalation]\nenabled = true\n\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"{}\"\nargs = [\"fixed\"]\nallow_extra_args = true", executable.display() ), ); let config = write_configuration(&prefix_tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let resolved = load_trusted_configuration(request(&prefix_tree, config, environment)).unwrap(); let canonical = fs::canonicalize(executable) .unwrap() .to_str() .unwrap() .as_bytes() .to_vec(); let rule = &resolved.auto_approval_rules()[0]; assert!(rule.matches_command_argv(&[canonical.clone(), b"fixed".to_vec()])); assert!(rule.matches_command_argv(&[canonical, b"fixed".to_vec(), b"extra".to_vec(),])); let hardlink_tree = TestTree::new(); let binary_directory = hardlink_tree.path().join("bin"); fs::create_dir(&binary_directory).unwrap(); let executable = binary_directory.join("tool"); fs::write(&executable, b"#!/bin/sh\nexit 0\n").unwrap(); fs::set_permissions(&executable, fs::Permissions::from_mode(0o700)).unwrap(); fs::hard_link(&executable, binary_directory.join("alias")).unwrap(); let body = valid_configuration(&hardlink_tree).replace( "[profiles.default.escalation]\nenabled = false", &format!( "[profiles.default.escalation]\nenabled = true\n\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"{}\"", executable.display() ), ); let config = write_configuration(&hardlink_tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); assert_eq!( load_trusted_configuration(request(&hardlink_tree, config, environment)) .unwrap_err() .kind(), ConfigurationErrorKind::InvalidAutoApproval ); } #[test] fn path_network_environment_and_volume_conflicts_refuse_loading() { let tree = TestTree::new(); let base = valid_configuration(&tree); let cases = [ ( base.replace( "persistent_dirs = [\"/root\"]", "persistent_dirs = [\"/workspace/cache\"]", ), ConfigurationErrorKind::PathConflict, ), ( base.replace( "allow_egress = [\"10.1.2.3/24\"]", "allow_egress = [\"10.0.0.0/8\"]", ), ConfigurationErrorKind::InvalidNetwork, ), ( base.replace("/root/.local/state/agent", "/root"), ConfigurationErrorKind::InvalidVolume, ), ( base.replace( "persistent_dirs = [\"/root\"]", "persistent_dirs = [\"/root\", \"/root/cache\"]", ), ConfigurationErrorKind::InvalidProfile, ), ( base.replace("kind = \"directory\"", "kind = \"file\""), ConfigurationErrorKind::InvalidVolume, ), ( format!( "{base}\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"/bin/true\"\n" ), ConfigurationErrorKind::InvalidAutoApproval, ), ( base.replace( "[profiles.default.escalation]\nenabled = false", "[profiles.default.escalation]\nenabled = true\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"/bin/true\"\nallow_extra_args = true", ), ConfigurationErrorKind::InvalidAutoApproval, ), ]; for (index, (body, expected)) in cases.into_iter().enumerate() { let case_tree = TestTree::new(); let body = body.replace( &tree.path().join("data").display().to_string(), &case_tree.path().join("data").display().to_string(), ); let config = write_configuration(&case_tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); let error = load_trusted_configuration(request(&case_tree, config, environment)) .expect_err(&format!("case {index} must fail")); assert_eq!(error.kind(), expected); } let config = write_configuration(&tree, &base); let error = load_trusted_configuration(request(&tree, config, BTreeMap::new())).unwrap_err(); assert_eq!(error.kind(), ConfigurationErrorKind::MissingEnvironment); } #[test] fn secret_environment_resolution_occurs_after_authority_validation() { let volume_tree = TestTree::new(); let body = valid_configuration(&volume_tree).replace("kind = \"directory\"", "kind = \"file\""); let config = write_configuration(&volume_tree, &body); assert_eq!( load_trusted_configuration(request(&volume_tree, config, BTreeMap::new())) .unwrap_err() .kind(), ConfigurationErrorKind::InvalidVolume ); let executable_tree = TestTree::new(); let executable = executable_tree.path().join("unsafe-tool"); fs::write(&executable, b"#!/bin/sh\nexit 0\n").unwrap(); fs::set_permissions(&executable, fs::Permissions::from_mode(0o722)).unwrap(); let body = valid_configuration(&executable_tree).replace( "[profiles.default.escalation]\nenabled = false", &format!( "[profiles.default.escalation]\nenabled = true\n[[profiles.default.escalation.auto_approve]]\nexecutable = \"{}\"", executable.display() ), ); let config = write_configuration(&executable_tree, &body); assert_eq!( load_trusted_configuration(request(&executable_tree, config, BTreeMap::new())) .unwrap_err() .kind(), ConfigurationErrorKind::InvalidAutoApproval ); } #[test] fn volume_symlinks_and_lazaret_owned_mode_drift_are_rejected() { let tree = TestTree::new(); let target = tree.path().join("target"); fs::create_dir(&target).unwrap(); let link = tree.path().join("volume-link"); symlink(&target, &link).unwrap(); let body = valid_configuration(&tree) .replace("{profile_dir}/agent-state", link.to_str().unwrap()) .replace("create = true", "create = false"); let config = write_configuration(&tree, &body); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); assert_eq!( load_trusted_configuration(request(&tree, config, environment)) .unwrap_err() .kind(), ConfigurationErrorKind::InvalidVolume ); let mode_tree = TestTree::new(); let data = mode_tree.path().join("data"); fs::create_dir(&data).unwrap(); fs::set_permissions(&data, fs::Permissions::from_mode(0o755)).unwrap(); let config = write_configuration(&mode_tree, &valid_configuration(&mode_tree)); let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); assert_eq!( load_trusted_configuration(request(&mode_tree, config, environment)) .unwrap_err() .kind(), ConfigurationErrorKind::InvalidPath ); } #[test] fn concurrent_loaders_share_exact_owned_directory_creation() { let tree = TestTree::new(); let config = write_configuration(&tree, &valid_configuration(&tree)); let barrier = Arc::new(Barrier::new(8)); std::thread::scope(|scope| { let handles = (0..8) .map(|_| { let barrier = Arc::clone(&barrier); let config = config.clone(); let tree = &tree; scope.spawn(move || { let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); barrier.wait(); load_trusted_configuration(request(tree, config, environment)) }) }) .collect::>(); for handle in handles { handle.join().unwrap().unwrap(); } }); } #[test] fn configuration_source_permissions_links_and_size_are_bounded() { for mutation in 0..3 { let tree = TestTree::new(); let config = write_configuration(&tree, &valid_configuration(&tree)); match mutation { 0 => fs::set_permissions(&config, fs::Permissions::from_mode(0o666)).unwrap(), 1 => fs::hard_link(&config, tree.path().join("config-link")).unwrap(), 2 => fs::write(&config, vec![b'x'; 1_048_577]).unwrap(), _ => unreachable!(), } let mut environment = BTreeMap::new(); environment.insert("HOST_TOKEN".into(), "value".into()); assert!( load_trusted_configuration(request(&tree, config, environment)).is_err(), "mutation {mutation} must fail" ); } } #[test] fn digest_vectors_are_framed_and_order_independent() { assert_eq!( derive_domain_id("default", b"/workspace/project") .unwrap() .as_bytes(), &[ 0x62, 0x64, 0xea, 0x6c, 0x25, 0x85, 0x2f, 0x5b, 0x77, 0x03, 0x90, 0x33, 0x9b, 0xb1, 0xd9, 0x75, 0x01, 0x9b, 0x70, 0xa6, 0x04, 0x55, 0xff, 0xfa, 0x2a, 0x1c, 0xf3, 0x0e, 0xf1, 0xc6, 0xbe, 0xf9, ] ); let mut record = ConfigurationFingerprintRecordV1 { selected_profile: "default".into(), storage_root: HostPath::new(b"/data".to_vec()), runtime_root: HostPath::new(b"/run/user/1000/lazaret".to_vec()), image: "example.invalid/agent:v1".into(), isolate_gitignored: true, persistent_dirs: vec!["/var".into(), "/root".into()], environment_references: vec![ EnvironmentReference { sandbox_name: "B".into(), host_name: "HOST_B".into(), }, EnvironmentReference { sandbox_name: "A".into(), host_name: "HOST_A".into(), }, ], volumes: vec![ResolvedVolume { canonical_host_source: HostPath::new(b"/source".to_vec()), container_path: "/root/source".into(), kind: VolumeKind::Directory, access: VolumeAccess::ReadOnly, create: false, }], effective_allow_prefixes: vec![([8, 8, 8, 8], 32), ([1, 1, 1, 1], 32)], effective_deny_prefixes: vec![([10, 0, 0, 0], 8)], network_readiness: ResolvedNetworkReadinessV1 { denied: vec![ResolvedNetworkProbeTargetV1 { address: [10, 0, 0, 1], port: 9, }], permitted: vec![ResolvedNetworkProbeTargetV1 { address: [1, 1, 1, 1], port: 443, }], dns: vec![ResolvedDnsProbeTargetV1 { resolver: [8, 8, 8, 8], query: "example.invalid".into(), }], }, escalation_enabled: true, auto_approval_rules: vec![ResolvedAutoApprovalRule { canonical_executable: HostPath::new(b"/usr/bin/true".to_vec()), args: vec!["a".into(), "b".into()], allow_extra_args: false, }], }; let first = derive_configuration_fingerprint(&record).unwrap(); assert_eq!( first.as_bytes(), &[ 0x22, 0x57, 0x11, 0xe7, 0xc9, 0x11, 0xba, 0x8e, 0x08, 0x11, 0xae, 0x56, 0x5b, 0x7d, 0x89, 0xb0, 0x73, 0x48, 0xaa, 0x73, 0x87, 0x19, 0x5e, 0xcd, 0xf8, 0xfa, 0xd1, 0x48, 0xc1, 0x5b, 0x90, 0x57, ] ); record.persistent_dirs.reverse(); record.environment_references.reverse(); record.effective_allow_prefixes.reverse(); assert_eq!(first, derive_configuration_fingerprint(&record).unwrap()); record.auto_approval_rules[0].args.reverse(); assert_ne!(first, derive_configuration_fingerprint(&record).unwrap()); } #[test] fn serialized_configuration_records_use_strict_postcard() { let reference = EnvironmentReference { sandbox_name: "TOKEN".into(), host_name: "HOST_TOKEN".into(), }; let mut encoded = encode(&reference).unwrap(); encoded.push(0); assert!(matches!( decode::(&encoded), Err(CodecError::TrailingPayload) )); }