services_hardening_runtime_context.rs (10895B)
1 #![forbid(unsafe_code)] 2 3 use std::error::Error; 4 use std::path::{Path, PathBuf}; 5 6 use myc::{ 7 MycBootstrapProfileV1, MycRuntimeContextErrorKind, RadrootsHostEnvironment, 8 RadrootsPathProfile, RadrootsPathResolver, RadrootsPlatform, RuntimeContextSource, 9 parse_myc_cli_v1_from, resolve_myc_runtime_context, 10 }; 11 12 const MANIFEST: &str = include_str!("../Cargo.toml"); 13 const LIB_SOURCE: &str = include_str!("../src/lib.rs"); 14 const CONTEXT_SOURCE: &str = include_str!("../src/runtime_context.rs"); 15 16 fn resolve( 17 resolver: &RadrootsPathResolver, 18 profile: &str, 19 instance: &str, 20 repo_local_root: Option<&str>, 21 config_path: Option<&str>, 22 ) -> myc::MycRuntimeContext { 23 let mut arguments = vec!["myc", "--profile", profile, "--instance", instance]; 24 if let Some(root) = repo_local_root { 25 arguments.extend(["--repo-local-root", root]); 26 } 27 if let Some(path) = config_path { 28 arguments.extend(["--config", path]); 29 } 30 arguments.push("run"); 31 let invocation = parse_myc_cli_v1_from(arguments).expect("validated CLI selection"); 32 resolve_myc_runtime_context(resolver, &invocation).expect("Myc runtime context") 33 } 34 35 fn assert_exact_roots(context: &myc::MycRuntimeContext, expected: [&str; 6]) { 36 let paths = context.context().paths(); 37 assert_eq!(paths.config(), Path::new(expected[0])); 38 assert_eq!(paths.state(), Path::new(expected[1])); 39 assert_eq!(paths.cache(), Path::new(expected[2])); 40 assert_eq!(paths.logs(), Path::new(expected[3])); 41 assert_eq!(paths.run(), Path::new(expected[4])); 42 assert_eq!(paths.secrets(), Path::new(expected[5])); 43 } 44 45 #[test] 46 fn repo_local_context_binds_typed_identity_sources_and_exact_artifacts() { 47 let resolver = 48 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 49 let primary = resolve( 50 &resolver, 51 "repo-local", 52 "primary", 53 Some("/repo/.local/radroots"), 54 None, 55 ); 56 let secondary = resolve( 57 &resolver, 58 "repo-local", 59 "secondary", 60 Some("/repo/.local/radroots"), 61 None, 62 ); 63 64 assert_eq!(primary.context().service().as_str(), "myc"); 65 assert_eq!(primary.context().instance().as_str(), "primary"); 66 assert_eq!(primary.profile(), MycBootstrapProfileV1::RepoLocal); 67 assert_eq!(primary.context().profile(), RadrootsPathProfile::RepoLocal); 68 assert_eq!( 69 primary.context().sources().service(), 70 RuntimeContextSource::SafeDefault 71 ); 72 assert_eq!( 73 primary.context().sources().instance(), 74 RuntimeContextSource::BootstrapCli 75 ); 76 assert_eq!( 77 primary.context().sources().profile(), 78 RuntimeContextSource::BootstrapCli 79 ); 80 assert_eq!( 81 primary.context().sources().repo_local_root(), 82 Some(RuntimeContextSource::BootstrapCli) 83 ); 84 assert_eq!( 85 primary.context().sources().paths(), 86 RuntimeContextSource::DerivedPath 87 ); 88 assert_exact_roots( 89 &primary, 90 [ 91 "/repo/.local/radroots/config/services/myc/primary", 92 "/repo/.local/radroots/data/services/myc/primary", 93 "/repo/.local/radroots/cache/services/myc/primary", 94 "/repo/.local/radroots/logs/services/myc/primary", 95 "/repo/.local/radroots/run/services/myc/primary", 96 "/repo/.local/radroots/secrets/services/myc/primary", 97 ], 98 ); 99 assert_eq!( 100 primary.artifacts().config(), 101 Path::new("/repo/.local/radroots/config/services/myc/primary/config.toml") 102 ); 103 assert_eq!( 104 primary.artifacts().state_database(), 105 Path::new("/repo/.local/radroots/data/services/myc/primary/state.sqlite") 106 ); 107 assert_eq!( 108 primary.artifacts().state_lock(), 109 Path::new("/repo/.local/radroots/data/services/myc/primary/state.lock") 110 ); 111 assert_eq!( 112 primary.artifacts().admin_socket(), 113 Path::new("/repo/.local/radroots/run/services/myc/primary/admin.sock") 114 ); 115 assert_eq!(primary.selected_config_path(), primary.artifacts().config()); 116 assert_ne!(primary.context().paths(), secondary.context().paths()); 117 assert_eq!(secondary.context().instance().as_str(), "secondary"); 118 } 119 120 #[test] 121 fn service_host_and_interactive_profiles_use_the_shared_exact_roots() { 122 let service_host = resolve( 123 &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()), 124 "service-host", 125 "primary", 126 None, 127 None, 128 ); 129 assert_exact_roots( 130 &service_host, 131 [ 132 "/etc/radroots/services/myc/primary", 133 "/var/lib/radroots/services/myc/primary", 134 "/var/cache/radroots/services/myc/primary", 135 "/var/log/radroots/services/myc/primary", 136 "/run/radroots/services/myc/primary", 137 "/etc/radroots/secrets/services/myc/primary", 138 ], 139 ); 140 assert_eq!( 141 service_host.artifacts().admin_socket(), 142 Path::new("/run/radroots/services/myc/primary/admin.sock") 143 ); 144 145 let linux_interactive = resolve( 146 &RadrootsPathResolver::new( 147 RadrootsPlatform::Linux, 148 RadrootsHostEnvironment { 149 home_dir: Some(PathBuf::from("/home/operator")), 150 xdg_config_home: Some(PathBuf::from("/xdg/config")), 151 xdg_data_home: Some(PathBuf::from("/xdg/data")), 152 xdg_state_home: Some(PathBuf::from("/xdg/state")), 153 xdg_cache_home: Some(PathBuf::from("/xdg/cache")), 154 xdg_runtime_dir: Some(PathBuf::from("/xdg/run")), 155 ..RadrootsHostEnvironment::default() 156 }, 157 ), 158 "interactive", 159 "primary", 160 None, 161 None, 162 ); 163 assert_exact_roots( 164 &linux_interactive, 165 [ 166 "/xdg/config/radroots/services/myc/primary", 167 "/xdg/data/radroots/services/myc/primary", 168 "/xdg/cache/radroots/services/myc/primary", 169 "/xdg/state/radroots/logs/services/myc/primary", 170 "/xdg/run/radroots/services/myc/primary", 171 "/xdg/config/radroots/secrets/services/myc/primary", 172 ], 173 ); 174 } 175 176 #[test] 177 fn interactive_macos_and_windows_roots_remain_exact_and_injected() { 178 let macos = resolve( 179 &RadrootsPathResolver::new( 180 RadrootsPlatform::Macos, 181 RadrootsHostEnvironment { 182 home_dir: Some(PathBuf::from("/Users/operator")), 183 ..RadrootsHostEnvironment::default() 184 }, 185 ), 186 "interactive", 187 "primary", 188 None, 189 None, 190 ); 191 assert_exact_roots( 192 &macos, 193 [ 194 "/Users/operator/Library/Application Support/Radroots/config/services/myc/primary", 195 "/Users/operator/Library/Application Support/Radroots/data/services/myc/primary", 196 "/Users/operator/Library/Caches/Radroots/services/myc/primary", 197 "/Users/operator/Library/Logs/Radroots/services/myc/primary", 198 "/Users/operator/Library/Application Support/Radroots/run/services/myc/primary", 199 "/Users/operator/Library/Application Support/Radroots/secrets/services/myc/primary", 200 ], 201 ); 202 203 let windows = resolve( 204 &RadrootsPathResolver::new( 205 RadrootsPlatform::Windows, 206 RadrootsHostEnvironment { 207 appdata_dir: Some(PathBuf::from(r"C:\Users\operator\AppData\Roaming")), 208 localappdata_dir: Some(PathBuf::from(r"C:\Users\operator\AppData\Local")), 209 ..RadrootsHostEnvironment::default() 210 }, 211 ), 212 "interactive", 213 "primary", 214 None, 215 None, 216 ); 217 assert_exact_roots( 218 &windows, 219 [ 220 r"C:\Users\operator\AppData\Roaming/Radroots/config/services/myc/primary", 221 r"C:\Users\operator\AppData\Local/Radroots/data/services/myc/primary", 222 r"C:\Users\operator\AppData\Local/Radroots/cache/services/myc/primary", 223 r"C:\Users\operator\AppData\Local/Radroots/logs/services/myc/primary", 224 r"C:\Users\operator\AppData\Local/Radroots/run/services/myc/primary", 225 r"C:\Users\operator\AppData\Roaming/Radroots/secrets/services/myc/primary", 226 ], 227 ); 228 } 229 230 #[test] 231 fn explicit_config_path_overrides_only_the_common_config_artifact_selection() { 232 let resolver = 233 RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()); 234 let context = resolve( 235 &resolver, 236 "repo-local", 237 "primary", 238 Some("/repo/.local/radroots"), 239 Some("/operator/config/myc.toml"), 240 ); 241 assert_eq!( 242 context.selected_config_path(), 243 Path::new("/operator/config/myc.toml") 244 ); 245 assert_eq!( 246 context.artifacts().config(), 247 Path::new("/repo/.local/radroots/config/services/myc/primary/config.toml") 248 ); 249 } 250 251 #[test] 252 fn unsupported_profile_platform_and_diagnostics_fail_safely() { 253 let invocation = parse_myc_cli_v1_from([ 254 "myc", 255 "--profile", 256 "service-host", 257 "--instance", 258 "secret-instance", 259 "--config", 260 "/private/secret-config.toml", 261 "run", 262 ]) 263 .expect("valid CLI"); 264 let error = resolve_myc_runtime_context( 265 &RadrootsPathResolver::new(RadrootsPlatform::Macos, RadrootsHostEnvironment::default()), 266 &invocation, 267 ) 268 .expect_err("unsupported service-host platform"); 269 assert_eq!(error.kind(), MycRuntimeContextErrorKind::PathSelection); 270 assert!(Error::source(&error).is_none()); 271 let rendered = format!("{error} {error:?}"); 272 assert!(!rendered.contains("secret-instance")); 273 assert!(!rendered.contains("secret-config")); 274 275 let context = resolve( 276 &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()), 277 "repo-local", 278 "secret-instance", 279 Some("/private/secret-root"), 280 Some("/private/secret-config.toml"), 281 ); 282 let debug = format!("{context:?}"); 283 assert!(!debug.contains("secret-instance")); 284 assert!(!debug.contains("secret-root")); 285 assert!(!debug.contains("secret-config")); 286 } 287 288 #[test] 289 fn shared_runtime_paths_are_the_only_path_policy_and_identity_authority() { 290 assert!(MANIFEST.contains( 291 "radroots_runtime_paths = { git = \"https://github.com/radrootslabs/lib\", rev = \"055096853fca95e15d0f813d33a14aca13be3881\", version = \"=0.1.0-alpha\" }" 292 )); 293 assert!(LIB_SOURCE.contains("mod runtime_context;")); 294 assert!(!LIB_SOURCE.contains("pub mod runtime_context;")); 295 assert!(CONTEXT_SOURCE.contains("RuntimeContext::resolve(")); 296 assert!(CONTEXT_SOURCE.contains("default_service_instance_artifacts(")); 297 let root = Path::new(env!("CARGO_MANIFEST_DIR")); 298 assert!(!root.join("src/paths.rs").exists()); 299 assert!(!root.join("src/config.rs").exists()); 300 }