services_hardening_admin_identity_offline.rs (3581B)
1 #![forbid(unsafe_code)] 2 3 use rhi::RhiAdminRoute; 4 use serde_json::Value; 5 6 const OFFLINE_IDENTITY_CONTRACT: &str = 7 include_str!("../contracts/services_hardening/admin_identity_offline.v1.json"); 8 const OPERATOR_CONTRACT: &str = 9 include_str!("../contracts/services_hardening/operator_contract.v1.json"); 10 const ADMIN_SOURCE: &str = include_str!("../src/admin_v1.rs"); 11 const ROOT_SOURCE: &str = include_str!("../src/lib.rs"); 12 13 #[test] 14 fn final_identity_policy_is_offline_only_and_inventory_is_exact() { 15 let policy: Value = 16 serde_json::from_str(OFFLINE_IDENTITY_CONTRACT).expect("offline identity contract"); 17 let operator: Value = serde_json::from_str(OPERATOR_CONTRACT).expect("operator contract"); 18 19 assert_eq!(policy["schema"], "radroots.rhi.admin-identity-offline.v1"); 20 assert_eq!(policy["contract_version"], 1); 21 assert_eq!(policy["final_inventory"]["route_count"], 20); 22 assert_eq!(policy["final_inventory"]["model_count"], 33); 23 assert_eq!( 24 policy["identity_rotation"]["mode"], 25 "offline_create_new_configuration_apply_restart" 26 ); 27 assert_eq!(policy["identity_rotation"]["unix_admin_mutation"], false); 28 assert_eq!( 29 policy["peer_authorization"]["grants_identity_provider_authority"], 30 false 31 ); 32 assert_eq!( 33 policy["peer_authorization"]["grants_direct_sqlite_authority"], 34 false 35 ); 36 37 let routes = operator["admin"]["routes"].as_array().expect("routes"); 38 let models = operator["admin"]["models"].as_object().expect("models"); 39 let types = operator["admin"]["types"].as_object().expect("types"); 40 assert_eq!(routes.len(), 20); 41 assert_eq!(models.len(), 33); 42 assert_eq!(RhiAdminRoute::ALL.len(), 20); 43 assert_eq!(RhiAdminRoute::ACTIVE, RhiAdminRoute::ALL); 44 45 for removed in policy["removed_live_routes"] 46 .as_array() 47 .expect("removed routes") 48 { 49 let path = removed["path"].as_str().expect("removed path"); 50 let operation_id = removed["operation_id"] 51 .as_str() 52 .expect("removed operation ID"); 53 assert!(routes.iter().all(|route| route["path"] != path)); 54 assert!( 55 routes 56 .iter() 57 .all(|route| route["operation_id"] != operation_id) 58 ); 59 } 60 for removed in policy["removed_models"].as_array().expect("removed models") { 61 assert!(!models.contains_key(removed.as_str().expect("model name"))); 62 } 63 for removed in policy["removed_operator_types"] 64 .as_array() 65 .expect("removed operator types") 66 { 67 assert!(!types.contains_key(removed.as_str().expect("type name"))); 68 } 69 } 70 71 #[test] 72 fn removed_live_identity_surface_cannot_reenter_the_adapter_or_root_api() { 73 for forbidden in [ 74 "IdentityRekey", 75 "IdentityReplace", 76 "identity_rekey_request_v1", 77 "identity_replace_request_v1", 78 "identity_mutation_receipt_v1", 79 "build_rhi_identity", 80 "direct_sqlite", 81 ] { 82 assert!( 83 !ADMIN_SOURCE.contains(forbidden), 84 "forbidden admin surface `{forbidden}`" 85 ); 86 assert!( 87 !ROOT_SOURCE.contains(forbidden), 88 "forbidden root surface `{forbidden}`" 89 ); 90 } 91 for required in [ 92 "pub const ALL: [Self; 20]", 93 "pub const ACTIVE: [Self; 20] = Self::ALL", 94 "Live identity rekey and replace are absent by final offline-only policy", 95 ] { 96 assert!( 97 ADMIN_SOURCE.contains(required), 98 "missing guard `{required}`" 99 ); 100 } 101 }