Replace external reachability with an owned canary

This commit is contained in:
ms2k 2026-07-19 22:39:31 +09:00
commit 31e9ab0f2d
8 changed files with 1409 additions and 1972 deletions

View file

@ -10,9 +10,8 @@ use sha2::{Digest, Sha256};
use crate::identity::{ConfigurationFingerprint, DeviceId, DomainId, HostPath, InodeId, MountId};
use crate::records::{
ConfigurationFingerprintRecordV1, MailboxRequest, ResolvedAutoApprovalRule,
ResolvedNetworkReadinessV1, ResolvedVolume, TRUSTED_HOST_CONFIGURATION_SCHEMA_ID,
TrustedHostConfigurationV1, VolumeKind,
ConfigurationFingerprintRecordV1, MailboxRequest, ResolvedAutoApprovalRule, ResolvedVolume,
TRUSTED_HOST_CONFIGURATION_SCHEMA_ID, TrustedHostConfigurationV1, VolumeKind,
};
use confined_io::{io_error, open_absolute_directory, validate_executable};
use validation::validate_sandbox_path;
@ -456,6 +455,7 @@ fn validate_relative_consumer_path(path: &Path) -> Result<(), ConfigurationError
Ok(())
}
#[derive(Clone)]
pub struct EnvironmentValue(OsString);
impl fmt::Debug for EnvironmentValue {
@ -656,11 +656,6 @@ impl ResolvedDomainConfiguration {
&self.effective_deny_prefixes
}
#[must_use]
pub const fn network_readiness(&self) -> &ResolvedNetworkReadinessV1 {
&self.fingerprint_record.network_readiness
}
#[must_use]
pub const fn ephemeral_environment(&self) -> &BTreeMap<String, EnvironmentValue> {
&self.ephemeral_environment

View file

@ -11,9 +11,7 @@ use rustix::fs::{FileType, Mode, OFlags, ResolveFlags, Stat, openat2};
use crate::identity::HostPath;
use crate::records::{
EnvironmentReference, NetworkProbeTargetConfigurationV1, NetworkReadinessConfigurationV1,
ProfileConfigurationV1, ResolvedAutoApprovalRule, ResolvedDnsProbeTargetV1,
ResolvedNetworkProbeTargetV1, ResolvedNetworkReadinessV1, ResolvedVolume,
EnvironmentReference, ProfileConfigurationV1, ResolvedAutoApprovalRule, ResolvedVolume,
TrustedHostConfigurationV1, VolumeAccess, VolumeKind,
};
@ -454,173 +452,6 @@ fn ipv4_prefixes_overlap(left: Ipv4Prefix, right: Ipv4Prefix) -> bool {
u32::from_be_bytes(left.address()) & mask == u32::from_be_bytes(right.address()) & mask
}
const MAX_READINESS_TARGETS: usize = 32;
pub(super) fn resolve_network_readiness(
readiness: &NetworkReadinessConfigurationV1,
allow: &[Ipv4Prefix],
deny: &[Ipv4Prefix],
) -> Result<ResolvedNetworkReadinessV1, ConfigurationError> {
if readiness.denied.is_empty() || readiness.permitted.is_empty() || readiness.dns.is_empty() {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"profile readiness requires denied, permitted, and DNS targets",
));
}
if readiness.denied.len() > MAX_READINESS_TARGETS
|| readiness.permitted.len() > MAX_READINESS_TARGETS
|| readiness.dns.len() > MAX_READINESS_TARGETS
{
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"profile readiness target count exceeds 32",
));
}
let denied = resolve_tcp_readiness_targets(&readiness.denied, allow, deny, false)?;
let permitted = resolve_tcp_readiness_targets(&readiness.permitted, allow, deny, true)?;
let dns = readiness
.dns
.iter()
.map(|target| {
Ok(ResolvedDnsProbeTargetV1 {
resolver: parse_probe_address(&target.resolver)?,
query: normalize_dns_query(&target.query)?,
})
})
.collect::<Result<BTreeSet<_>, ConfigurationError>>()?;
if dns.len() != readiness.dns.len() {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"profile readiness contains duplicate DNS targets",
));
}
Ok(ResolvedNetworkReadinessV1 {
denied: denied.into_iter().collect(),
permitted: permitted.into_iter().collect(),
dns: dns.into_iter().collect(),
})
}
fn resolve_tcp_readiness_targets(
targets: &[NetworkProbeTargetConfigurationV1],
allow: &[Ipv4Prefix],
deny: &[Ipv4Prefix],
expected_allowed: bool,
) -> Result<BTreeSet<ResolvedNetworkProbeTargetV1>, ConfigurationError> {
let resolved = targets
.iter()
.map(|target| {
if target.port == 0 {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"readiness TCP target has port zero",
));
}
let address = parse_probe_address(&target.address)?;
if is_effectively_allowed(address, allow, deny) != expected_allowed {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"readiness TCP target disagrees with effective profile policy",
));
}
Ok(ResolvedNetworkProbeTargetV1 {
address,
port: target.port,
})
})
.collect::<Result<BTreeSet<_>, _>>()?;
if resolved.len() != targets.len() {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"profile readiness contains duplicate TCP targets",
));
}
if resolved
.iter()
.map(|target| target.address)
.collect::<BTreeSet<_>>()
.len()
!= resolved.len()
{
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"readiness TCP targets repeat an observation address",
));
}
Ok(resolved)
}
fn parse_probe_address(value: &str) -> Result<[u8; 4], ConfigurationError> {
let address = value.parse::<std::net::Ipv4Addr>().map_err(|_| {
ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"readiness target address is not IPv4",
)
})?;
if address.is_unspecified() || address.is_multicast() || address.is_broadcast() {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"readiness target address is not unicast",
));
}
Ok(address.octets())
}
fn normalize_dns_query(value: &str) -> Result<String, ConfigurationError> {
let query = value.strip_suffix('.').unwrap_or(value);
if query.is_empty() || query.len() > 253 || !query.is_ascii() {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"DNS readiness query name is invalid",
));
}
for label in query.split('.') {
if label.is_empty()
|| label.len() > 63
|| label.starts_with('-')
|| label.ends_with('-')
|| !label
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
{
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidNetwork,
"DNS readiness query name is invalid",
));
}
}
Ok(query.to_ascii_lowercase())
}
fn is_effectively_allowed(address: [u8; 4], allow: &[Ipv4Prefix], deny: &[Ipv4Prefix]) -> bool {
let best_allow = allow
.iter()
.filter(|prefix| prefix_contains(**prefix, address))
.map(|prefix| prefix.prefix_length)
.max();
let best_deny = deny
.iter()
.filter(|prefix| prefix_contains(**prefix, address))
.map(|prefix| prefix.prefix_length)
.max();
match (best_allow, best_deny) {
(Some(allowed), Some(denied)) => allowed > denied,
(Some(_) | None, None) => true,
(None, Some(_)) => false,
}
}
fn prefix_contains(prefix: Ipv4Prefix, address: [u8; 4]) -> bool {
let mask = if prefix.prefix_length == 0 {
0
} else {
u32::MAX << (32 - prefix.prefix_length)
};
u32::from_be_bytes(address) & mask == u32::from_be_bytes(prefix.address)
}
pub(super) fn parse_ipv4_prefix(value: &str) -> Result<Ipv4Prefix, ConfigurationError> {
let (address, prefix) = value.split_once('/').ok_or_else(|| {
ConfigurationError::new(

View file

@ -3,8 +3,8 @@ use super::confined_io::{
host_path, io_error, is_strict_descendant, paths_intersect, read_stable_configuration,
require_clear_of_protected, require_pairwise_disjoint, require_pairwise_non_ancestral,
require_strict_child_of_one, resolve_environment_references, resolve_environment_values,
resolve_network, resolve_network_readiness, validate_configuration_stat, validate_executable,
validate_host_base, validate_persistent_destinations,
resolve_network, validate_configuration_stat, validate_executable, validate_host_base,
validate_persistent_destinations,
};
use super::validation::{
validate_profile_name, validate_sandbox_path, validate_structural_configuration, validate_text,
@ -153,11 +153,6 @@ pub fn load_trusted_configuration(
)?;
let (effective_allow_prefixes, effective_deny_prefixes) =
resolve_network(&configuration, selected)?;
let network_readiness = resolve_network_readiness(
&selected.readiness,
&effective_allow_prefixes,
&effective_deny_prefixes,
)?;
let environment_references = resolve_environment_references(selected)?;
let mut volumes = Vec::with_capacity(selected.volumes.len());
@ -360,7 +355,6 @@ pub fn load_trusted_configuration(
.iter()
.map(|prefix| (prefix.address, prefix.prefix_length))
.collect(),
network_readiness,
escalation_enabled: selected.escalation.enabled,
auto_approval_rules: resolved_auto_approval_records,
};

View file

@ -68,34 +68,6 @@ pub fn derive_configuration_fingerprint(
.iter()
.map(|prefix| Ok([prefix.0.as_slice(), &[prefix.1]].concat())),
)?;
push_sorted_list(
&mut preimage,
record.network_readiness.denied.iter().map(|target| {
Ok([
target.address.as_slice(),
target.port.to_be_bytes().as_slice(),
]
.concat())
}),
)?;
push_sorted_list(
&mut preimage,
record.network_readiness.permitted.iter().map(|target| {
Ok([
target.address.as_slice(),
target.port.to_be_bytes().as_slice(),
]
.concat())
}),
)?;
push_sorted_list(
&mut preimage,
record.network_readiness.dns.iter().map(|target| {
let mut item = target.resolver.to_vec();
push_bytes(&mut item, target.query.as_bytes())?;
Ok(item)
}),
)?;
push_bool(&mut preimage, record.escalation_enabled);
push_sorted_list(
&mut preimage,
@ -128,6 +100,12 @@ pub(super) fn validate_structural_configuration(
}
for (sandbox_name, reference) in &profile.env {
validate_environment_name(sandbox_name)?;
if sandbox_name == "LAZARET_SESSION_ID" {
return Err(ConfigurationError::new(
ConfigurationErrorKind::InvalidProfile,
"environment name is reserved by Lazaret",
));
}
let host_name = reference.strip_prefix("env:").ok_or_else(|| {
ConfigurationError::new(
ConfigurationErrorKind::InvalidProfile,

View file

@ -13,7 +13,7 @@ use crate::records::{
AddressFamily, AddressFamilyDisposition, AddressFamilyDispositionValue,
DERIVED_NETWORK_POLICY_SCHEMA_ID, DefaultAction, DerivedNetworkPolicy,
DerivedNetworkPolicyFields, IpAddress, IpNetwork, NETWORK_READINESS_IDENTITY_SCHEMA_ID,
NetworkObservation, NetworkReachabilityProbe, NetworkReadinessAttestation,
NetworkCanaryObservation, NetworkObservation, NetworkReadinessAttestation,
NetworkReadinessEvidence, NetworkReadinessIdentity, NetworkReadinessIdentityFields,
PolicyAction, PolicyDelivery, PrefixRule, Resolver,
};
@ -42,19 +42,6 @@ impl CanonicalDerivedNetworkPolicy {
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReachabilityExpectation {
pub denied_destinations: Vec<IpAddress>,
pub permitted_destinations: Vec<IpAddress>,
pub dns_queries: Vec<DnsQueryExpectation>,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct DnsQueryExpectation {
pub resolver: IpAddress,
pub query_name: String,
}
#[derive(Clone, Debug)]
pub struct ReadinessExpectation<'a> {
pub domain_id: DomainId,
@ -117,10 +104,9 @@ impl From<CodecError> for NetworkValidationError {
///
/// # Errors
///
/// Returns an error when resolver or canonical policy validation fails.
/// Returns an error when canonical policy validation fails.
pub fn derive_ipv4_policy(
configuration: &ResolvedDomainConfiguration,
resolvers: impl IntoIterator<Item = IpAddress>,
) -> Result<CanonicalDerivedNetworkPolicy, NetworkValidationError> {
let prefix_rules = configuration
.effective_allow_prefixes()
@ -163,13 +149,10 @@ pub fn derive_ipv4_policy(
action: PolicyAction::Allow,
}],
prefix_rules,
resolvers: resolvers
.into_iter()
.map(|address| Resolver {
family: AddressFamily::Ipv4,
address,
})
.collect(),
resolvers: vec![Resolver {
family: AddressFamily::Ipv4,
address: IpAddress::V4([10, 137, 54, 3]),
}],
})
}
@ -243,25 +226,19 @@ pub fn readiness_fingerprint(
)))
}
/// Binds validated attestation and reachability results into readiness evidence.
/// Binds validated attestation and owned-canary results into readiness evidence.
///
/// # Errors
///
/// Returns an error for any identity, observation, policy, or probe mismatch.
/// Returns an error for any identity, observation, policy, or canary mismatch.
pub fn accept_network_readiness(
policy: &CanonicalDerivedNetworkPolicy,
expectation: &ReadinessExpectation<'_>,
attestation: NetworkReadinessAttestation,
reachability: NetworkReachabilityProbe,
expected_reachability: &ReachabilityExpectation,
canary: NetworkCanaryObservation,
) -> Result<NetworkReadinessEvidence, NetworkValidationError> {
validate_attestation(policy, expectation, &attestation)?;
validate_reachability(
policy.policy(),
expectation,
&reachability,
expected_reachability,
)?;
validate_canary(expectation, &canary)?;
let identity = readiness_identity(&attestation);
let fingerprint = readiness_fingerprint(&identity)?;
Ok(NetworkReadinessEvidence {
@ -271,7 +248,7 @@ pub fn accept_network_readiness(
readiness_fingerprint: fingerprint,
attestation,
readiness_identity: identity,
reachability_probe: reachability,
canary_observation: canary,
})
}
@ -291,12 +268,11 @@ pub fn validate_network_attestation(
///
/// # Errors
///
/// Returns an error for any identity, projection, fingerprint, or probe mismatch.
/// Returns an error for any identity, projection, fingerprint, or canary mismatch.
pub fn validate_network_readiness_evidence(
policy: &CanonicalDerivedNetworkPolicy,
expectation: &ReadinessExpectation<'_>,
evidence: &NetworkReadinessEvidence,
expected_reachability: &ReachabilityExpectation,
) -> Result<(), NetworkValidationError> {
if evidence.domain_id != expectation.domain_id
|| evidence.generation_id != expectation.generation_id
@ -316,12 +292,7 @@ pub fn validate_network_readiness_evidence(
"network readiness projection or fingerprint mismatch",
));
}
validate_reachability(
policy.policy(),
expectation,
&evidence.reachability_probe,
expected_reachability,
)
validate_canary(expectation, &evidence.canary_observation)
}
fn validate_address_families(
@ -496,149 +467,40 @@ fn valid_observation(observation: &NetworkObservation) -> bool {
&& observation.routes_attested
}
fn validate_reachability(
policy: &DerivedNetworkPolicy,
fn validate_canary(
expected: &ReadinessExpectation<'_>,
probe: &NetworkReachabilityProbe,
expected_results: &ReachabilityExpectation,
canary: &NetworkCanaryObservation,
) -> Result<(), NetworkValidationError> {
if probe.domain_id != expected.domain_id
|| probe.generation_id != expected.generation_id
|| probe.enforcement_namespace != expected.enforcement_namespace
|| expected_results.denied_destinations.is_empty()
|| expected_results.permitted_destinations.is_empty()
|| expected_results.dns_queries.is_empty()
if canary.domain_id != expected.domain_id
|| canary.generation_id != expected.generation_id
|| canary.enforcement_namespace != expected.enforcement_namespace
{
return reachability_error("network probe identity or coverage mismatch");
return reachability_error("owned canary identity mismatch");
}
let denied = exact_observations(
&expected_results.denied_destinations,
&probe.denied_destinations,
|item| (item.destination, item.unreachable),
"denied destination observations differ",
)?;
let permitted = exact_observations(
&expected_results.permitted_destinations,
&probe.permitted_destinations,
|item| (item.destination, item.reachable),
"permitted destination observations differ",
)?;
if denied.iter().any(|(_, passed)| !passed) {
return reachability_error("a configured denied destination was reachable");
let passed = [
canary.linux_policy_matches,
canary.endpoints_authenticated_before_policy,
canary.allowed_endpoint_authenticated,
canary.denied_endpoint_unreachable,
canary.imported_interface_matches,
canary.tap_present,
canary.ipv6_absent,
canary.network_capabilities_empty,
canary.route_mutation_refused,
canary.cleanup_shape_matched,
canary.responder_closed,
canary.peer_namespace_closed,
canary.host_interface_deleted,
canary.allowed_endpoint_unreachable_after_cleanup,
canary.tap_present_after_cleanup,
canary.ipv6_unchanged_after_cleanup,
canary.network_admin_dropped,
];
if passed.into_iter().all(|predicate| predicate) {
Ok(())
} else {
reachability_error("owned canary predicate failed")
}
if permitted.iter().any(|(_, passed)| !passed) {
return reachability_error("a configured permitted destination was unreachable");
}
if expected_results
.denied_destinations
.iter()
.any(|address| effective_action(policy, *address) != Some(PolicyAction::Deny))
{
return reachability_error("a denied probe destination is not denied by policy");
}
if expected_results
.permitted_destinations
.iter()
.any(|address| effective_action(policy, *address) != Some(PolicyAction::Allow))
{
return reachability_error("a permitted probe destination is not allowed by policy");
}
let expected_dns: BTreeSet<_> = expected_results.dns_queries.iter().cloned().collect();
let actual_dns: BTreeSet<_> = probe
.dns_observations
.iter()
.map(|item| DnsQueryExpectation {
resolver: item.resolver,
query_name: item.query_name.clone(),
})
.collect();
let resolvers: BTreeSet<_> = policy
.resolvers
.iter()
.map(|resolver| resolver.address)
.collect();
if expected_dns.len() != expected_results.dns_queries.len()
|| actual_dns.len() != probe.dns_observations.len()
|| actual_dns != expected_dns
|| probe.dns_observations.iter().any(|item| {
item.query_name.is_empty()
|| !resolvers.contains(&item.resolver)
|| item.resolved_addresses.is_empty()
|| item
.resolved_addresses
.iter()
.any(|address| address_family(*address) != AddressFamily::Ipv4)
})
{
return reachability_error("DNS readiness observations differ or contain no IPv4 answer");
}
Ok(())
}
fn exact_observations<T, F>(
expected: &[IpAddress],
actual: &[T],
project: F,
mismatch: &'static str,
) -> Result<BTreeSet<(IpAddress, bool)>, NetworkValidationError>
where
F: Fn(&T) -> (IpAddress, bool),
{
let expected_items: BTreeSet<_> = expected.iter().copied().collect();
let actual_items: BTreeSet<_> = actual.iter().map(project).collect();
let actual_addresses: BTreeSet<_> = actual_items.iter().map(|(address, _)| *address).collect();
if expected_items.len() != expected.len()
|| actual_items.len() != actual.len()
|| expected_items.len() != actual_items.len()
|| expected_items.len() != actual_addresses.len()
|| actual_addresses != expected_items
{
return reachability_error(mismatch);
}
Ok(actual_items)
}
fn effective_action(policy: &DerivedNetworkPolicy, address: IpAddress) -> Option<PolicyAction> {
let family = address_family(address);
let disposition = policy
.address_families
.iter()
.find(|item| item.family == family)?;
if disposition.disposition != AddressFamilyDispositionValue::Enabled {
return None;
}
policy
.prefix_rules
.iter()
.filter(|rule| rule.family == family && network_contains(rule.network, address))
.max_by_key(|rule| rule.network.prefix_length)
.map(|rule| rule.action)
.or_else(|| {
policy
.default_actions
.iter()
.find(|item| item.family == family)
.map(|item| item.action)
})
}
fn network_contains(network: IpNetwork, address: IpAddress) -> bool {
let prefix_length = network.prefix_length;
let (network, address) = match (network.address, address) {
(IpAddress::V4(network), IpAddress::V4(address)) => (network.to_vec(), address.to_vec()),
(IpAddress::V6(network), IpAddress::V6(address)) => (network.to_vec(), address.to_vec()),
_ => return false,
};
let complete = usize::from(prefix_length / 8);
let remainder = prefix_length % 8;
if network[..complete] != address[..complete] {
return false;
}
remainder == 0
|| network[complete] & (u8::MAX << (8 - remainder))
== address[complete] & (u8::MAX << (8 - remainder))
}
const fn address_family(address: IpAddress) -> AddressFamily {

View file

@ -22,31 +22,6 @@ pub struct NetworkConfigurationV1 {
pub default_deny: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NetworkReadinessConfigurationV1 {
#[serde(default)]
pub denied: Vec<NetworkProbeTargetConfigurationV1>,
#[serde(default)]
pub permitted: Vec<NetworkProbeTargetConfigurationV1>,
#[serde(default)]
pub dns: Vec<DnsProbeTargetConfigurationV1>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NetworkProbeTargetConfigurationV1 {
pub address: String,
pub port: u16,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DnsProbeTargetConfigurationV1 {
pub resolver: String,
pub query: String,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum VolumeKind {
@ -106,8 +81,6 @@ pub struct ProfileConfigurationV1 {
#[serde(default)]
pub deny_egress: Vec<String>,
#[serde(default)]
pub readiness: NetworkReadinessConfigurationV1,
#[serde(default)]
pub env: BTreeMap<String, String>,
#[serde(default)]
pub volumes: Vec<VolumeConfigurationV1>,
@ -221,26 +194,6 @@ pub struct ConfigurationFingerprintRecordV1 {
pub volumes: Vec<ResolvedVolume>,
pub effective_allow_prefixes: Vec<([u8; 4], u8)>,
pub effective_deny_prefixes: Vec<([u8; 4], u8)>,
pub network_readiness: ResolvedNetworkReadinessV1,
pub escalation_enabled: bool,
pub auto_approval_rules: Vec<ResolvedAutoApprovalRule>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResolvedNetworkReadinessV1 {
pub denied: Vec<ResolvedNetworkProbeTargetV1>,
pub permitted: Vec<ResolvedNetworkProbeTargetV1>,
pub dns: Vec<ResolvedDnsProbeTargetV1>,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct ResolvedNetworkProbeTargetV1 {
pub address: [u8; 4],
pub port: u16,
}
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub struct ResolvedDnsProbeTargetV1 {
pub resolver: [u8; 4],
pub query: String,
}

View file

@ -302,33 +302,29 @@ impl<'de> Deserialize<'de> for NetworkReadinessIdentity {
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DeniedDestinationObservation {
pub destination: IpAddress,
pub unreachable: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PermittedDestinationObservation {
pub destination: IpAddress,
pub reachable: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DnsObservation {
pub resolver: IpAddress,
pub query_name: String,
pub resolved_addresses: Vec<IpAddress>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct NetworkReachabilityProbe {
#[allow(clippy::struct_excessive_bools)]
pub struct NetworkCanaryObservation {
pub domain_id: DomainId,
pub generation_id: GenerationId,
pub enforcement_namespace: NamespaceIdentity,
pub denied_destinations: Vec<DeniedDestinationObservation>,
pub permitted_destinations: Vec<PermittedDestinationObservation>,
pub dns_observations: Vec<DnsObservation>,
pub linux_policy_matches: bool,
pub endpoints_authenticated_before_policy: bool,
pub allowed_endpoint_authenticated: bool,
pub denied_endpoint_unreachable: bool,
pub imported_interface_matches: bool,
pub tap_present: bool,
pub ipv6_absent: bool,
pub network_capabilities_empty: bool,
pub route_mutation_refused: bool,
pub cleanup_shape_matched: bool,
pub responder_closed: bool,
pub peer_namespace_closed: bool,
pub host_interface_deleted: bool,
pub allowed_endpoint_unreachable_after_cleanup: bool,
pub tap_present_after_cleanup: bool,
pub ipv6_unchanged_after_cleanup: bool,
pub network_admin_dropped: bool,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@ -339,7 +335,7 @@ pub struct NetworkReadinessEvidence {
pub readiness_fingerprint: ReadinessFingerprint,
pub attestation: NetworkReadinessAttestation,
pub readiness_identity: NetworkReadinessIdentity,
pub reachability_probe: NetworkReachabilityProbe,
pub canary_observation: NetworkCanaryObservation,
}
impl NetworkReadinessEvidence {
@ -357,10 +353,10 @@ impl NetworkReadinessEvidence {
.is_ok_and(|canonical| canonical == readiness_fingerprint)
&& self.domain_id == self.attestation.domain_id
&& self.domain_id == self.readiness_identity.domain_id
&& self.domain_id == self.reachability_probe.domain_id
&& self.domain_id == self.canary_observation.domain_id
&& self.generation_id == self.attestation.generation_id
&& self.generation_id == self.readiness_identity.generation_id
&& self.generation_id == self.reachability_probe.generation_id
&& self.generation_id == self.canary_observation.generation_id
&& self.network_sidecar_id == self.attestation.network_sidecar_id
&& self.network_sidecar_id == self.readiness_identity.network_sidecar_id
&& self.attestation.configuration_fingerprint
@ -369,7 +365,7 @@ impl NetworkReadinessEvidence {
&& self.readiness_identity.derived_policy_digest == derived_policy_digest
&& self.attestation.enforcement_namespace == namespace
&& self.readiness_identity.enforcement_namespace == namespace
&& self.reachability_probe.enforcement_namespace == namespace
&& self.canary_observation.enforcement_namespace == namespace
&& self.attestation.runtime_supervisor == *runtime_supervisor
}
}

View file

@ -1,22 +1,21 @@
use super::{
AddressFamily, AddressFamilyDisposition, ArtifactLease, ArtifactState, ClientEnrollmentRequest,
ClientEnrollmentResponse, ConfigurationFingerprintRecordV1, ControlRequest, ControlResponse,
DefaultAction, DeniedDestinationObservation, DerivedNetworkPolicy, DeveloperDecision,
DeveloperDecisionKind, DnsObservation, Domain, DomainStore, DomainStoreMetadata,
EnvironmentReference, EscalationOutcome, Generation, GenerationIdentity, GitRoutingOwnership,
GuestProcessIdentity, HelperOwnership, HostProcessIdentity, HostProcessOwnershipSlot,
InteractiveSessionBatch, InteractiveSessionEvent, InteractiveSessionMessage,
InteractiveSessionRequest, Mailbox, MailboxCancellation, MailboxRequest, MailboxResult,
MailboxResultAck, MailboxResultChunk, MountIdentity, NamespaceIdentity, NetworkObservation,
NetworkOwnership, NetworkReachabilityProbe, NetworkReadinessAttestation,
NetworkReadinessEvidence, NetworkReadinessIdentity, NonInteractiveOutput,
NonInteractiveSessionMessage, NonInteractiveSessionRequest, Participant, ParticipantOwnership,
ParticipantState, PermittedDestinationObservation, PersistedHostProcessOwnership,
PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PolicyAction, PolicyDelivery, PrefixRule,
PrepareOperation, PrepareStageState, PreparedArtifact, PreparedLowerOwnership,
ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion, ProtocolCredential,
ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity, PublicationOwnership,
ResolvedAutoApprovalRule, ResolvedNetworkReadinessV1, ResolvedVolume, Resolver, ResultStream,
DefaultAction, DerivedNetworkPolicy, DeveloperDecision, DeveloperDecisionKind, Domain,
DomainStore, DomainStoreMetadata, EnvironmentReference, EscalationOutcome, Generation,
GenerationIdentity, GitRoutingOwnership, GuestProcessIdentity, HelperOwnership,
HostProcessIdentity, HostProcessOwnershipSlot, InteractiveSessionBatch,
InteractiveSessionEvent, InteractiveSessionMessage, InteractiveSessionRequest, Mailbox,
MailboxCancellation, MailboxRequest, MailboxResult, MailboxResultAck, MailboxResultChunk,
MountIdentity, NamespaceIdentity, NetworkCanaryObservation, NetworkObservation,
NetworkOwnership, NetworkReadinessAttestation, NetworkReadinessEvidence,
NetworkReadinessIdentity, NonInteractiveOutput, NonInteractiveSessionMessage,
NonInteractiveSessionRequest, Participant, ParticipantOwnership, ParticipantState,
PersistedHostProcessOwnership, PidfsProcessAuthorityV1, PidfsProcessIdentityV1, PolicyAction,
PolicyDelivery, PrefixRule, PrepareOperation, PrepareStageState, PreparedArtifact,
PreparedLowerOwnership, ProcessOwnershipCommitAck, ProcessOwnershipMessageVersion,
ProtocolCredential, ProtocolCredentialRole, ProtocolPublicationIdentity, PtyIdentity,
PublicationOwnership, ResolvedAutoApprovalRule, ResolvedVolume, Resolver, ResultStream,
SandboxCredentialBundle, SandboxOwnership, Session, SessionBinding, SessionClose, SessionExit,
SessionExitOutcome, SessionRegistrationRequest, SessionRegistrationResponse,
SessionRequestCapability, SessionTerminal, SessionTerminalEvent, SessionTerminalMessage,
@ -136,16 +135,12 @@ schema_types!(
"NetworkObservation" => NetworkObservation,
"PolicyDelivery" => PolicyDelivery,
"NetworkReadinessIdentity" => NetworkReadinessIdentity,
"NetworkReachabilityProbe" => NetworkReachabilityProbe,
"DeniedDestinationObservation" => DeniedDestinationObservation,
"PermittedDestinationObservation" => PermittedDestinationObservation,
"DnsObservation" => DnsObservation,
"NetworkCanaryObservation" => NetworkCanaryObservation,
"TrustedHostConfigurationV1" => TrustedHostConfigurationV1,
"ConfigurationFingerprintRecordV1" => ConfigurationFingerprintRecordV1,
"EnvironmentReference" => EnvironmentReference,
"ResolvedVolume" => ResolvedVolume,
"ResolvedAutoApprovalRule" => ResolvedAutoApprovalRule,
"ResolvedNetworkReadinessV1" => ResolvedNetworkReadinessV1,
"DomainStore" => DomainStore,
"StorageCatalog" => StorageCatalog,
"PrepareStageState" => PrepareStageState,