lib.rs (6009B)
1 #![forbid(unsafe_code)] 2 3 mod cli; 4 mod error; 5 mod managed; 6 mod model; 7 #[cfg(any(target_os = "linux", target_os = "macos"))] 8 mod status; 9 10 pub use cli::{ManagedCliCommand, ManagedCliInvocation}; 11 pub use error::RadrootsRuntimeManagerError; 12 pub use managed::{ManagedRuntimeContext, ManagedRuntimeTarget, resolve_runtime_target}; 13 pub use model::{ 14 InstanceMetadataContract, LifecycleContract, ManagementDefaults, ManagementModeContract, 15 RadrootsRuntimeManagementContract, RuntimeGroups, 16 }; 17 #[cfg(any(target_os = "linux", target_os = "macos"))] 18 pub use status::{ManagedRuntimeStatusClient, ManagedServiceStatusV1}; 19 20 pub const RUNTIME_MANAGEMENT_SCHEMA: &str = "radroots-runtime-management"; 21 pub const RUNTIME_MANAGEMENT_SCHEMA_VERSION: u32 = 1; 22 pub const RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES: usize = 1_048_576; 23 24 pub(crate) const HARDENED_MANAGEMENT_CONTRACT: &str = 25 include_str!("../tests/fixtures/hardened_service_management.v1.toml"); 26 27 pub fn parse_contract_str( 28 raw: &str, 29 ) -> Result<RadrootsRuntimeManagementContract, RadrootsRuntimeManagerError> { 30 if raw.len() > RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES { 31 return Err(RadrootsRuntimeManagerError::ContractTooLarge); 32 } 33 let contract = toml::from_str::<RadrootsRuntimeManagementContract>(raw) 34 .map_err(|_| RadrootsRuntimeManagerError::Parse)?; 35 if contract.schema != RUNTIME_MANAGEMENT_SCHEMA { 36 return Err(RadrootsRuntimeManagerError::UnexpectedSchema); 37 } 38 if contract.schema_version != RUNTIME_MANAGEMENT_SCHEMA_VERSION { 39 return Err(RadrootsRuntimeManagerError::UnexpectedSchemaVersion); 40 } 41 validate_hardened_management_contract(&contract)?; 42 Ok(contract) 43 } 44 45 pub(crate) fn validate_hardened_management_contract( 46 contract: &RadrootsRuntimeManagementContract, 47 ) -> Result<(), RadrootsRuntimeManagerError> { 48 let expected = 49 toml::from_str::<RadrootsRuntimeManagementContract>(HARDENED_MANAGEMENT_CONTRACT) 50 .map_err(|_| RadrootsRuntimeManagerError::InvalidContract)?; 51 if contract != &expected { 52 return Err(RadrootsRuntimeManagerError::InvalidContract); 53 } 54 Ok(()) 55 } 56 57 #[cfg(test)] 58 mod tests { 59 use std::error::Error as _; 60 61 use super::{ 62 HARDENED_MANAGEMENT_CONTRACT, RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES, 63 RUNTIME_MANAGEMENT_SCHEMA, RadrootsRuntimeManagerError, parse_contract_str, 64 }; 65 66 const CONTRACT: &str = HARDENED_MANAGEMENT_CONTRACT; 67 68 #[test] 69 fn contract_parser_accepts_only_the_exact_static_inventory() { 70 let contract = parse_contract_str(CONTRACT).expect("contract"); 71 assert_eq!(contract.schema, RUNTIME_MANAGEMENT_SCHEMA); 72 assert_eq!(contract.service_targets.len(), 2); 73 assert_eq!(contract.managed_runtime_targets.active, ["myc", "rhi"]); 74 75 for raw in [ 76 CONTRACT.replace("schema_version = 1", "schema_version = 2"), 77 CONTRACT.replace( 78 "active = [\"myc\", \"rhi\"]", 79 "active = [\"myc\"]\ndefined = [\"rhi\"]", 80 ), 81 CONTRACT.replace( 82 "actions = [\"config_init\", \"config_validate\", \"state_init\", \"run\", \"status\", \"doctor\"]", 83 "actions = [\"start\"]", 84 ), 85 format!("{CONTRACT}\nunknown = true\n"), 86 ] { 87 assert!(parse_contract_str(&raw).is_err()); 88 } 89 assert!(parse_contract_str("schema = [").is_err()); 90 } 91 92 #[test] 93 fn contract_parser_caps_the_complete_document_before_toml_parsing() { 94 let mut exact = CONTRACT.to_owned(); 95 exact.push('#'); 96 exact.extend(std::iter::repeat_n( 97 'x', 98 RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES - exact.len(), 99 )); 100 assert_eq!(exact.len(), RUNTIME_MANAGEMENT_CONTRACT_MAX_UTF8_BYTES); 101 parse_contract_str(&exact).expect("exact maximum contract remains admissible"); 102 103 exact.push('x'); 104 assert_eq!( 105 parse_contract_str(&exact), 106 Err(RadrootsRuntimeManagerError::ContractTooLarge) 107 ); 108 109 let very_large = format!("{}#{}", CONTRACT, "x".repeat(4 * 1024 * 1024)); 110 assert_eq!( 111 parse_contract_str(&very_large), 112 Err(RadrootsRuntimeManagerError::ContractTooLarge) 113 ); 114 } 115 116 #[test] 117 fn contract_errors_are_value_free_and_source_free() { 118 for (raw, secret) in [ 119 ( 120 CONTRACT.replace( 121 "schema = \"radroots-runtime-management\"", 122 "schema = \"/sensitive/root/secret-schema\"", 123 ), 124 "/sensitive/root/secret-schema", 125 ), 126 ( 127 "credential = 'secret-value'\ninvalid = [".to_owned(), 128 "secret-value", 129 ), 130 ] { 131 let error = parse_contract_str(&raw).expect_err("invalid contract"); 132 let rendered = format!("{error} {error:?}"); 133 assert!(!rendered.contains(secret)); 134 assert!(error.source().is_none()); 135 } 136 } 137 138 #[test] 139 fn root_surface_exposes_no_legacy_runtime_authority() { 140 let source = include_str!("lib.rs") 141 .split("\n#[cfg(test)]") 142 .next() 143 .expect("production source"); 144 for forbidden in [ 145 "mod lifecycle", 146 "mod paths", 147 "mod registry", 148 "ManagedRuntimeArtifactName", 149 "ManagedRuntimeInstancePaths", 150 "ManagedRuntimeSharedPaths", 151 "ManagedRuntimeInstanceRegistry", 152 "ManagedRuntimeInstanceRecord", 153 "ManagedRuntimeLifecycleAction", 154 "load_registry", 155 "save_registry", 156 "start_process", 157 "stop_process", 158 "install_binary", 159 "extract_binary_archive", 160 "remove_instance_artifacts", 161 "write_instance_config", 162 ] { 163 assert!(!source.contains(forbidden), "root retained `{forbidden}`"); 164 } 165 } 166 }