rhi

Coordinated trade for connected markets
git clone https://radroots.dev/git/rhi.git
Log | Files | Refs | README | LICENSE

services_hardening_runtime_context.rs (10134B)


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