services_hardening_native_release.rs (12756B)
1 #![forbid(unsafe_code)] 2 3 use std::collections::BTreeSet; 4 5 use serde_json::json; 6 7 const CONTRACT: &str = include_str!("../contracts/services_hardening/native_release.v2.json"); 8 const ACTIVE_CONTRACT: &str = 9 include_str!("../contracts/services_hardening/native_release.v3.json"); 10 const MANIFEST: &str = include_str!("../Cargo.toml"); 11 const LOCK: &str = include_str!("../Cargo.lock"); 12 const FLAKE: &str = include_str!("../flake.nix"); 13 const FLAKE_LOCK: &str = include_str!("../flake.lock"); 14 const CARGO_CONFIG: &str = include_str!("../.cargo/config.toml"); 15 const SYSTEMD_UNIT: &str = include_str!("../packaging/systemd/myc@.service"); 16 17 const LIB_REVISION: &str = "055096853fca95e15d0f813d33a14aca13be3881"; 18 const LIB_REPOSITORY: &str = "https://github.com/radrootslabs/lib"; 19 20 #[test] 21 fn native_release_contract_and_manifest_metadata_are_exact() { 22 let contract: serde_json::Value = serde_json::from_str(CONTRACT).expect("release contract"); 23 assert_eq!( 24 contract, 25 json!({ 26 "schema": "radroots.myc.native-release", 27 "schema_version": 2, 28 "contract_version": 2, 29 "predecessor": { 30 "schema_version": 1, 31 "filename": "native_release.v1.json", 32 "transition": "forward_only_replace" 33 }, 34 "service": "myc", 35 "package": { 36 "name": "myc", 37 "binary": "myc", 38 "version": "0.1.0", 39 "repository": "https://github.com/radrootslabs/myc", 40 "publish_to_crates_io": false 41 }, 42 "generator": { 43 "command": "cargo xtask native-release", 44 "modes": ["check", "write"], 45 "required_arguments": [ 46 "mode", "target", "binary", "output", "source_date_epoch" 47 ], 48 "source_date_epoch_range": "1..=4294967295", 49 "clean_exact_head": true, 50 "target_binary_validation": "executable_elf64_little_endian_exact_machine", 51 "canonical_json": "compact_utf8_json_with_one_final_lf", 52 "deterministic_archives": true, 53 "output_directory_mode": "0755", 54 "output_file_mode": "0644", 55 "durability": "sync_files_then_output_directory_then_parent" 56 }, 57 "toolchain": { 58 "rust_version": "1.97.1", 59 "edition": "2024", 60 "resolver": "3", 61 "host_feature_profile": "service-host" 62 }, 63 "release_profile": { 64 "lto": "thin", 65 "codegen_units": 1, 66 "overflow_checks": true, 67 "strip": "symbols", 68 "panic": "unwind" 69 }, 70 "source_lock": { 71 "filename": "radroots.service.source-lock.v2.toml", 72 "schema": "radroots.service.source-lock.v2", 73 "lib_repository": LIB_REPOSITORY, 74 "architecture": "radroots.crates.release.v2" 75 }, 76 "contract_versions": { 77 "config": 1, 78 "state": 12, 79 "admin": 1, 80 "status": 1, 81 "provider": 1 82 }, 83 "native_targets": [ 84 { "target": "aarch64-unknown-linux-gnu", "posture": "target" }, 85 { "target": "x86_64-unknown-linux-gnu", "posture": "target" } 86 ], 87 "output_inventory": [ 88 "LICENSE", 89 "SHA256SUMS", 90 "THIRD-PARTY-NOTICES.txt", 91 "artifact-manifest.v1.json", 92 "binary.tar.gz", 93 "config.example.toml", 94 "config.schema.json", 95 "provenance-input.v1.json", 96 "radroots.service.source-lock.v2.toml", 97 "sbom.cdx.json", 98 "service-source.tar.gz", 99 "systemd.service" 100 ], 101 "signing_inputs": [ 102 "SHA256SUMS", 103 "artifact-manifest.v1.json", 104 "provenance-input.v1.json" 105 ], 106 "provenance_posture": "deterministic_unsigned_slsa_v1_input_external_keys_only", 107 "sbom_format": "cyclonedx_json_1_5_locked_cargo_graph", 108 "source_archive": "locked_offline_cargo_build_with_vendored_dependencies", 109 "checksum_format": "sha256_lower_hex_two_spaces_path_lf_sorted_by_path", 110 "protected_material_included": false, 111 "maximums": { 112 "text_input_bytes": 1048576, 113 "generated_document_bytes": 16777216, 114 "cargo_metadata_bytes": 33554432, 115 "binary_bytes": 536870912, 116 "source_archive_bytes": 1073741824, 117 "packages": 8192, 118 "tracked_files": 4096 119 }, 120 "deferred_through_rcld_rshr_170": [ 121 "nix_evaluation", 122 "nix_build", 123 "nixos_module_qualification", 124 "oci_artifact" 125 ], 126 "forbidden": [ 127 "nix_input", 128 "nixos_module_output", 129 "oci_input", 130 "oci_output", 131 "protected_material", 132 "parent_owned_human_docs", 133 "private_harness", 134 "local_or_path_lib_dependency", 135 "floating_or_branch_lib_dependency", 136 "mixed_lib_revision", 137 "crates_io_publication", 138 "signing", 139 "tagging", 140 "release_publication", 141 "deployment" 142 ] 143 }) 144 ); 145 146 let manifest: toml::Value = toml::from_str(MANIFEST).expect("Cargo manifest"); 147 let package = manifest["package"].as_table().expect("package"); 148 assert_eq!( 149 package["repository"].as_str(), 150 Some("https://github.com/radrootslabs/myc") 151 ); 152 assert_eq!(package["readme"].as_str(), Some("README")); 153 assert_eq!(package["publish"].as_bool(), Some(false)); 154 155 let metadata = &manifest["workspace"]["metadata"]["radroots"]; 156 assert_eq!( 157 metadata["service_source_lock"], 158 toml::Value::Table(toml::toml! { 159 service = "myc" 160 host_feature_profile = "service-host" 161 nix_material = "qualified" 162 config_contract_version = 1 163 state_contract_version = 12 164 admin_contract_version = 1 165 status_contract_version = 1 166 provider_contract_version = 1 167 }) 168 ); 169 assert_eq!( 170 metadata["service_release"], 171 toml::Value::Table(toml::toml! { 172 service = "myc" 173 service_package = "myc" 174 binary_name = "myc" 175 version = "0.1.0" 176 }) 177 ); 178 assert_eq!( 179 manifest["profile"]["release"], 180 toml::Value::Table(toml::toml! { 181 lto = "thin" 182 codegen-units = 1 183 overflow-checks = true 184 strip = "symbols" 185 panic = "unwind" 186 }) 187 ); 188 assert_eq!( 189 CARGO_CONFIG, 190 "[alias]\nxtask = \"run --locked -p myc_xtask --\"\n" 191 ); 192 for required in [ 193 "ExecStart=/usr/bin/myc --profile service-host --instance %i run", 194 "ConfigurationDirectory=radroots/services/myc/%i", 195 "StateDirectory=radroots/services/myc/%i", 196 "RuntimeDirectory=radroots/services/myc/%i", 197 "UMask=0077", 198 "NoNewPrivileges=yes", 199 "ProtectSystem=strict", 200 "CapabilityBoundingSet=", 201 ] { 202 assert!(SYSTEMD_UNIT.contains(required), "missing `{required}`"); 203 } 204 } 205 206 #[test] 207 fn every_radroots_dependency_is_exactly_source_locked() { 208 let manifest: toml::Value = toml::from_str(MANIFEST).expect("Cargo manifest"); 209 let dependencies = manifest["dependencies"].as_table().expect("dependencies"); 210 let radroots = dependencies 211 .iter() 212 .filter(|(name, _)| name.starts_with("radroots_")) 213 .collect::<Vec<_>>(); 214 assert_eq!(radroots.len(), 11); 215 for (name, dependency) in radroots { 216 let dependency = dependency.as_table().expect("detailed dependency"); 217 assert_eq!( 218 dependency.get("git").and_then(toml::Value::as_str), 219 Some(LIB_REPOSITORY), 220 "{name}" 221 ); 222 assert_eq!( 223 dependency.get("rev").and_then(toml::Value::as_str), 224 Some(LIB_REVISION), 225 "{name}" 226 ); 227 assert_eq!( 228 dependency.get("version").and_then(toml::Value::as_str), 229 Some("=0.1.0-alpha"), 230 "{name}" 231 ); 232 for forbidden in ["path", "branch", "tag"] { 233 assert!( 234 !dependency.contains_key(forbidden), 235 "{name} contains `{forbidden}`" 236 ); 237 } 238 } 239 assert!(!MANIFEST.contains("[patch.")); 240 241 let sources = LOCK 242 .lines() 243 .filter_map(|line| line.strip_prefix("source = \"git+")) 244 .filter_map(|line| line.strip_suffix('"')) 245 .filter(|source| source.contains("radrootslabs/lib")) 246 .collect::<BTreeSet<_>>(); 247 assert_eq!(sources.len(), 1); 248 let source = sources.into_iter().next().expect("Lib source"); 249 assert!(source.contains(&format!("?rev={LIB_REVISION}#{LIB_REVISION}"))); 250 251 for required in [ 252 "lib = {", 253 "url = \"github:radrootslabs/lib/055096853fca95e15d0f813d33a14aca13be3881\";", 254 "inputs.crane.follows = \"crane\";", 255 "systems = lib.lib.supportedSystems;", 256 "nixosModules.default", 257 ] { 258 assert!( 259 FLAKE.contains(required), 260 "flake source data is missing `{required}`" 261 ); 262 } 263 let flake_lock: serde_json::Value = 264 serde_json::from_str(FLAKE_LOCK).expect("flake source lock"); 265 assert_eq!(flake_lock["version"], 7); 266 assert_eq!(flake_lock["root"], "root"); 267 assert_eq!(flake_lock["nodes"]["root"]["inputs"]["lib"], "lib"); 268 assert_eq!( 269 flake_lock["nodes"]["lib"], 270 json!({ 271 "inputs": { 272 "crane": ["crane"], 273 "flake-parts": "flake-parts", 274 "nixpkgs": "nixpkgs", 275 "rust-overlay": "rust-overlay", 276 "treefmt-nix": "treefmt-nix" 277 }, 278 "locked": { 279 "lastModified": 1788739124_u64, 280 "narHash": "sha256-Aw8qbU1DrtxSYJKg0js6kexnKgVGFCjR34bgq+ZVAVo=", 281 "owner": "radrootslabs", 282 "repo": "lib", 283 "rev": LIB_REVISION, 284 "type": "github" 285 }, 286 "original": { 287 "owner": "radrootslabs", 288 "repo": "lib", 289 "rev": LIB_REVISION, 290 "type": "github" 291 } 292 }) 293 ); 294 } 295 296 #[test] 297 fn native_release_surfaces_remain_generated_outside_the_source_tree() { 298 let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); 299 assert!(!root.join("radroots.lib.source-lock.v1.toml").exists()); 300 assert!(!root.join("radroots.service.source-lock.v1.toml").exists()); 301 assert!(!root.join("radroots.service.source-lock.v2.toml").exists()); 302 assert!(root.join("radroots.service.source-lock.v3.toml").is_file()); 303 assert!( 304 !root 305 .join("contracts/services_hardening/native_release.v1.json") 306 .exists() 307 ); 308 assert!( 309 root.join("contracts/services_hardening/native_release.v2.json") 310 .is_file() 311 ); 312 assert!( 313 root.join("contracts/services_hardening/native_release.v3.json") 314 .is_file() 315 ); 316 for forbidden in [ 317 ".github", 318 "target", 319 "result", 320 "artifacts", 321 "dist", 322 "sbom.cdx.json", 323 "provenance-input.v1.json", 324 "oci-image.tar.gz", 325 ] { 326 assert!( 327 !root.join(forbidden).exists(), 328 "forbidden generated surface `{forbidden}` exists" 329 ); 330 } 331 assert!(!CONTRACT.contains("qualified")); 332 assert!(!CONTRACT.contains("production_ready")); 333 assert!(!CONTRACT.contains("oci-image")); 334 assert!(!CONTRACT.contains("nixos-module")); 335 let active: serde_json::Value = 336 serde_json::from_str(ACTIVE_CONTRACT).expect("active release contract"); 337 assert_eq!(active["schema_version"], 3); 338 assert_eq!(active["contract_version"], 3); 339 assert_eq!(active["predecessor"]["filename"], "native_release.v2.json"); 340 assert_eq!( 341 active["source_lock"]["filename"], 342 "radroots.service.source-lock.v3.toml" 343 ); 344 assert_eq!(active["source_lock"]["nix_material"], "qualified"); 345 assert_eq!(active["nix_outputs"]["bundled_sqlite"], true); 346 assert_eq!(active["nix_outputs"]["native_linkage_count"], 1); 347 }