Replace external reachability with an owned canary

This commit is contained in:
ms2k 2026-07-19 22:39:31 +09:00
commit 31e9ab0f2d

View file

@ -6,21 +6,20 @@ use lazaret_core::identity::{
PidfsProcessAuthorityV1, PidfsProcessIdentityV1, ProcessStartTimeTicks, ReadinessFingerprint,
};
use lazaret_core::network::{
DnsQueryExpectation, NetworkValidationErrorKind, ReachabilityExpectation, ReadinessExpectation,
accept_network_readiness, canonical_readiness_identity_bytes, derive_policy,
readiness_fingerprint, readiness_identity, validate_network_readiness_evidence,
NetworkValidationErrorKind, ReadinessExpectation, accept_network_readiness,
canonical_readiness_identity_bytes, derive_policy, readiness_fingerprint, readiness_identity,
validate_network_readiness_evidence,
};
use lazaret_core::records::{
AddressFamily, AddressFamilyDisposition, AddressFamilyDispositionValue, DefaultAction,
DeniedDestinationObservation, DerivedNetworkPolicyFields, DnsObservation, IpAddress, IpNetwork,
NetworkObservation, NetworkReachabilityProbe, NetworkReadinessAttestation,
NetworkReadinessAttestationFields, PermittedDestinationObservation, PolicyAction,
PolicyDelivery, PrefixRule, Resolver,
DerivedNetworkPolicyFields, IpAddress, IpNetwork, NetworkCanaryObservation, NetworkObservation,
NetworkReadinessAttestation, NetworkReadinessAttestationFields, PolicyAction, PolicyDelivery,
PrefixRule, Resolver,
};
type AttestationMutation = fn(&mut NetworkReadinessAttestation);
type EvidenceMutation = fn(&mut lazaret_core::records::NetworkReadinessEvidence);
type ProbeMutation = fn(&mut NetworkReachabilityProbe);
type CanaryMutation = fn(&mut NetworkCanaryObservation);
fn repeated(value: u8) -> [u8; 32] {
[value; 32]
@ -107,8 +106,7 @@ struct Fixture {
runtime_supervisor: PidfsProcessIdentityV1,
connectivity_helper: PidfsProcessIdentityV1,
attestation: NetworkReadinessAttestation,
probe: NetworkReachabilityProbe,
reachability: ReachabilityExpectation,
canary: NetworkCanaryObservation,
}
impl Fixture {
@ -135,39 +133,34 @@ impl Fixture {
routes_attested: true,
},
});
let probe = NetworkReachabilityProbe {
let canary = NetworkCanaryObservation {
domain_id: DomainId::new(repeated(1)),
generation_id: GenerationId::new(2),
enforcement_namespace: namespace(),
denied_destinations: vec![DeniedDestinationObservation {
destination: IpAddress::V4([10, 1, 2, 3]),
unreachable: true,
}],
permitted_destinations: vec![PermittedDestinationObservation {
destination: IpAddress::V4([10, 20, 1, 2]),
reachable: true,
}],
dns_observations: vec![DnsObservation {
resolver: IpAddress::V4([192, 0, 2, 53]),
query_name: "sandbox.invalid".into(),
resolved_addresses: vec![IpAddress::V4([192, 0, 2, 10])],
}],
};
let reachability = ReachabilityExpectation {
denied_destinations: vec![IpAddress::V4([10, 1, 2, 3])],
permitted_destinations: vec![IpAddress::V4([10, 20, 1, 2])],
dns_queries: vec![DnsQueryExpectation {
resolver: IpAddress::V4([192, 0, 2, 53]),
query_name: "sandbox.invalid".into(),
}],
linux_policy_matches: true,
endpoints_authenticated_before_policy: true,
allowed_endpoint_authenticated: true,
denied_endpoint_unreachable: true,
imported_interface_matches: true,
tap_present: true,
ipv6_absent: true,
network_capabilities_empty: true,
route_mutation_refused: true,
cleanup_shape_matched: true,
responder_closed: true,
peer_namespace_closed: true,
host_interface_deleted: true,
allowed_endpoint_unreachable_after_cleanup: true,
tap_present_after_cleanup: true,
ipv6_unchanged_after_cleanup: true,
network_admin_dropped: true,
};
Self {
policy,
runtime_supervisor,
connectivity_helper,
attestation,
probe,
reachability,
canary,
}
}
@ -188,8 +181,7 @@ impl Fixture {
&self.policy,
&self.expectation(),
self.attestation.clone(),
self.probe.clone(),
&self.reachability,
self.canary.clone(),
)
.unwrap()
}
@ -344,8 +336,7 @@ fn every_attestation_identity_mismatch_is_refused() {
&fixture.policy,
&fixture.expectation(),
attestation,
fixture.probe.clone(),
&fixture.reachability,
fixture.canary.clone(),
)
.unwrap_err();
assert_eq!(
@ -392,13 +383,13 @@ fn every_evidence_identity_mismatch_is_refused() {
value.readiness_fingerprint = ReadinessFingerprint::new(repeated(90));
}),
("probe domain", |value| {
value.reachability_probe.domain_id = DomainId::new(repeated(91));
value.canary_observation.domain_id = DomainId::new(repeated(91));
}),
("probe generation", |value| {
value.reachability_probe.generation_id = GenerationId::new(92);
value.canary_observation.generation_id = GenerationId::new(92);
}),
("probe namespace", |value| {
value.reachability_probe.enforcement_namespace.inode = InodeId::new(93);
value.canary_observation.enforcement_namespace.inode = InodeId::new(93);
}),
];
for (name, mutate) in mutations {
@ -409,7 +400,6 @@ fn every_evidence_identity_mismatch_is_refused() {
&fixture.policy,
&fixture.expectation(),
&evidence,
&fixture.reachability,
)
.is_err(),
"accepted {name}"
@ -418,34 +408,35 @@ fn every_evidence_identity_mismatch_is_refused() {
}
#[test]
fn incomplete_or_failed_reachability_is_refused() {
fn incomplete_or_failed_canary_is_refused() {
let fixture = Fixture::new();
let mutations: [(&str, ProbeMutation); 6] = [
let mutations: [(&str, CanaryMutation); 6] = [
("deny reached", |value| {
value.denied_destinations[0].unreachable = false;
value.denied_endpoint_unreachable = false;
}),
("permit unreachable", |value| {
value.permitted_destinations[0].reachable = false;
value.allowed_endpoint_authenticated = false;
}),
("missing deny", |value| value.denied_destinations.clear()),
("missing permit", |value| {
value.permitted_destinations.clear();
("policy mismatch", |value| {
value.linux_policy_matches = false;
}),
("missing DNS", |value| value.dns_observations.clear()),
("empty DNS result", |value| {
value.dns_observations[0].resolved_addresses.clear();
("cleanup mismatch", |value| {
value.cleanup_shape_matched = false;
}),
("capability retained", |value| {
value.network_admin_dropped = false;
}),
("IPv6 present", |value| value.ipv6_absent = false),
];
for (name, mutate) in mutations {
let mut probe = fixture.probe.clone();
mutate(&mut probe);
let mut canary = fixture.canary.clone();
mutate(&mut canary);
assert!(
accept_network_readiness(
&fixture.policy,
&fixture.expectation(),
fixture.attestation.clone(),
probe,
&fixture.reachability,
canary,
)
.is_err(),
"accepted {name}"
@ -475,8 +466,7 @@ fn incomplete_or_malformed_attestation_is_refused() {
&fixture.policy,
&fixture.expectation(),
attestation,
fixture.probe.clone(),
&fixture.reachability,
fixture.canary.clone(),
)
.is_err(),
"accepted {name}"