myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

services_hardening_diagnostics.rs (5628B)


      1 #![forbid(unsafe_code)]
      2 
      3 use std::process::Command;
      4 
      5 use myc::{
      6     MYC_DIAGNOSTICS_CONTRACT_VERSION, MYC_LOG_RECORD_MAX_UTF8_BYTES, MycLogEvent, MycLogLevel,
      7     MycLogRecord, MycProcessResult, MycServicePhase,
      8 };
      9 
     10 const CONTRACT: &str = include_str!("../contracts/services_hardening/diagnostics.v1.json");
     11 const OPERATOR_CONTRACT: &str =
     12     include_str!("../contracts/services_hardening/operator_contract.v1.json");
     13 
     14 fn process_results() -> [MycProcessResult; 7] {
     15     [
     16         MycProcessResult::Success,
     17         MycProcessResult::UnexpectedInternal,
     18         MycProcessResult::InputOrConfiguration,
     19         MycProcessResult::ServiceOrDependencyUnavailable,
     20         MycProcessResult::StateOrIdentityUnavailable,
     21         MycProcessResult::OperationRejectedOrConflict,
     22         MycProcessResult::DoctorRequiredCheckFailed,
     23     ]
     24 }
     25 
     26 #[test]
     27 fn machine_contract_exit_inventory_matches_the_operator_contract_exactly() {
     28     let contract: serde_json::Value = serde_json::from_str(CONTRACT).expect("contract");
     29     let operator: serde_json::Value =
     30         serde_json::from_str(OPERATOR_CONTRACT).expect("operator contract");
     31     assert_eq!(contract["schema"], "radroots.myc.diagnostics.v1");
     32     assert_eq!(
     33         contract["contract_version"],
     34         MYC_DIAGNOSTICS_CONTRACT_VERSION
     35     );
     36     assert_eq!(contract["step"], 156);
     37     assert_eq!(
     38         contract["log_record"]["maximum_utf8_bytes_excluding_newline"],
     39         MYC_LOG_RECORD_MAX_UTF8_BYTES
     40     );
     41 
     42     let diagnostics = contract["exit_codes"].as_array().expect("exit codes");
     43     let operator = operator["exit_codes"].as_array().expect("operator exits");
     44     assert_eq!(diagnostics.len(), operator.len());
     45     for ((result, diagnostic), operator) in
     46         process_results().into_iter().zip(diagnostics).zip(operator)
     47     {
     48         assert_eq!(diagnostic["code"], result.exit_code_u8());
     49         assert_eq!(diagnostic["name"], result.code());
     50         assert_eq!(diagnostic["code"], operator["code"]);
     51         assert_eq!(diagnostic["name"], operator["name"]);
     52     }
     53     assert_eq!(contract["public_error_policy"]["error_source"], "none");
     54     assert_eq!(contract["stream_policy"]["logs_and_diagnostics"], "stderr");
     55     assert_eq!(contract["stream_policy"]["file_logging"], false);
     56 }
     57 
     58 #[test]
     59 fn process_lifecycle_task_and_signal_records_are_exact_and_bounded() {
     60     for result in process_results() {
     61         let record = MycLogRecord::process_result(result);
     62         let rendered = record.to_string();
     63         assert_eq!(record.event(), MycLogEvent::ProcessResult);
     64         assert_eq!(record.code(), result.code());
     65         assert_eq!(record.process_exit(), Some(result));
     66         assert!(rendered.len() <= MYC_LOG_RECORD_MAX_UTF8_BYTES);
     67         assert_eq!(
     68             serde_json::from_str::<serde_json::Value>(&rendered).expect("record")["exit_code"],
     69             result.exit_code_u8()
     70         );
     71     }
     72 
     73     for (phase, level, code) in [
     74         (MycServicePhase::Starting, MycLogLevel::Info, "starting"),
     75         (MycServicePhase::Ready, MycLogLevel::Info, "ready"),
     76         (MycServicePhase::Degraded, MycLogLevel::Warn, "degraded"),
     77         (MycServicePhase::Unready, MycLogLevel::Warn, "unready"),
     78         (MycServicePhase::Stopping, MycLogLevel::Info, "stopping"),
     79         (MycServicePhase::Failed, MycLogLevel::Error, "failed"),
     80     ] {
     81         let record = MycLogRecord::lifecycle(phase);
     82         assert_eq!(record.level(), level);
     83         assert_eq!(record.event(), MycLogEvent::Lifecycle);
     84         assert_eq!(record.code(), code);
     85         assert_eq!(record.process_exit(), None);
     86         assert!(!record.to_string().contains("exit_code"));
     87     }
     88 
     89     for (record, event, code) in [
     90         (
     91             MycLogRecord::critical_task_failed(),
     92             MycLogEvent::CriticalTaskFailed,
     93             "critical_task_failed",
     94         ),
     95         (
     96             MycLogRecord::shutdown_requested(),
     97             MycLogEvent::ShutdownRequested,
     98             "first_signal",
     99         ),
    100         (
    101             MycLogRecord::shutdown_forced(),
    102             MycLogEvent::ShutdownForced,
    103             "second_signal",
    104         ),
    105     ] {
    106         assert_eq!(record.event(), event);
    107         assert_eq!(record.code(), code);
    108         assert!(record.to_string().len() <= MYC_LOG_RECORD_MAX_UTF8_BYTES);
    109     }
    110 }
    111 
    112 #[test]
    113 fn binary_writes_only_fixed_json_diagnostics_to_stderr() {
    114     let canary = "secret-canary-private-key-path-sql-relay-url";
    115     let invalid = Command::new(env!("CARGO_BIN_EXE_myc"))
    116         .arg(format!("--credential={canary}"))
    117         .output()
    118         .expect("invalid invocation");
    119     assert_eq!(invalid.status.code(), Some(2));
    120     assert!(invalid.stdout.is_empty());
    121     let invalid_stderr = String::from_utf8(invalid.stderr).expect("invalid stderr");
    122     assert_eq!(
    123         invalid_stderr,
    124         format!(
    125             "{}\n",
    126             MycLogRecord::process_result(MycProcessResult::InputOrConfiguration)
    127         )
    128     );
    129     assert!(!invalid_stderr.contains(canary));
    130 
    131     let repo_local = tempfile::tempdir().expect("repo-local root");
    132     let unconfigured = Command::new(env!("CARGO_BIN_EXE_myc"))
    133         .args(["--profile", "repo-local", "--instance", "primary"])
    134         .arg("--repo-local-root")
    135         .arg(repo_local.path())
    136         .arg("run")
    137         .output()
    138         .expect("admitted invocation");
    139     assert_eq!(unconfigured.status.code(), Some(2));
    140     assert!(unconfigured.stdout.is_empty());
    141     assert_eq!(
    142         String::from_utf8(unconfigured.stderr).expect("unconfigured stderr"),
    143         format!(
    144             "{}\n",
    145             MycLogRecord::process_result(MycProcessResult::InputOrConfiguration)
    146         )
    147     );
    148 }