diff --git a/dag/extdeps/firmware/ipxe.dag b/dag/extdeps/firmware/ipxe.dag new file mode 100644 index 00000000000..6d266ba76da --- /dev/null +++ b/dag/extdeps/firmware/ipxe.dag @@ -0,0 +1,36 @@ +module extdeps.firmware.ipxe + +import std.types { NonEmptyStr } +import extdeps.uri { Uri, Https } +import extdeps.external_authority { ExternalAuthority } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "ipxe.org/docs" + } +} + +data ipxe_chainloading_authority: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "ipxe.org/howto/chainloading" + } +} + +// iPXE's own build grammar: `make /` from src/, e.g. bin-arm64-efi/snponly.efi. +// snponly.efi drives the NIC through the firmware's SNP protocol instead of a native iPXE driver, +// which is the image the chainloading how-to names for UEFI. +type IpxeBuildTarget { + platform: NonEmptyStr + image: NonEmptyStr +} + +data ipxe_arm64_efi_snponly: IpxeBuildTarget = IpxeBuildTarget { + platform: "bin-arm64-efi", + image: "snponly.efi", +} + +fn ipxe_build_target_path(target: IpxeBuildTarget) -> NonEmptyStr { + join([target.platform as String, "/", target.image as String], "") as NonEmptyStr +} diff --git a/dag/extdeps/formats/dnsmasq.dag b/dag/extdeps/formats/dnsmasq.dag index 8e0041609fa..63cd36e470b 100644 --- a/dag/extdeps/formats/dnsmasq.dag +++ b/dag/extdeps/formats/dnsmasq.dag @@ -17,13 +17,17 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { } } +// A TAGGED dhcp-boot CARRIES AT LEAST ONE TAG BY CONSTRUCTION (first_tag): an untagged dhcp-boot +// applies to every client on the segment, so an empty tag list would silently invert a restricted +// boot line into a permissive one. type DnsmasqDirective = DnsmasqComment { text: String } | DnsmasqPort { value: Int } | DnsmasqDhcpRangeProxy { network_address: String } | DnsmasqDhcpMatchSet { tag: String, option: String, value: String } | DnsmasqDhcpOptionForceTag { tag: String, option: String, value: String } - | DnsmasqDhcpBootTag { tag: String, target: String } + | DnsmasqDhcpHostSetTag { mac: String, tag: String } + | DnsmasqDhcpBootTags { first_tag: String, more_tags: List, target: String, server_name: String, server_address: String } data dnsmasq_protocol: LayoutProtocol = LayoutProtocol { indent_unit: "" } @@ -36,8 +40,14 @@ fn dnsmasq_directive_line(d: DnsmasqDirective) -> String { concat(concat(concat("dhcp-match=set:", tag), concat(",", option)), concat(",", value)) DnsmasqDhcpOptionForceTag { tag: tag, option: option, value: value } => concat(concat(concat("dhcp-option-force=tag:", tag), concat(",", option)), concat(",", value)) - DnsmasqDhcpBootTag { tag: tag, target: target } => - concat(concat("dhcp-boot=tag:", tag), concat(",", target)) + DnsmasqDhcpHostSetTag { mac: mac, tag: tag } => + concat(concat("dhcp-host=", mac), concat(",set:", tag)) + DnsmasqDhcpBootTags { first_tag: first, more_tags: more, target: target, server_name: name, server_address: address } => + join([ + "dhcp-boot=tag:", first, ",", + fold(more, init: "", f: (acc, t) => concat(acc, concat(concat("tag:", t), ","))), + target, ",", name, ",", address, + ], "") } } diff --git a/dag/gunbc/census_closure_frontier.dag b/dag/gunbc/census_closure_frontier.dag index a268f623c93..e1255910640 100644 --- a/dag/gunbc/census_closure_frontier.dag +++ b/dag/gunbc/census_closure_frontier.dag @@ -49,6 +49,7 @@ import gunbc.cloudflare.r2_token_mint { cloudflare_r2_token_mint_frontier_rows } import extdeps.cloudflare.account_api_tokens { cloudflare_account_api_tokens_frontier_rows } import extdeps.cloudflare.r2 { cloudflare_r2_frontier_rows } import gunbc.network_boot_delivery { network_boot_delivery_frontier_rows } +import gunbc.site_uefi_arm64_pxe_edge { site_uefi_arm64_pxe_edge_frontier_rows } import product.supplier.cloudflare_r2 { cloudflare_r2_supplier_frontier_rows } import extdeps.backblaze.b2 { backblaze_b2_frontier_rows } import extdeps.cloud.gcp.storage { gcs_storage_frontier_rows } @@ -112,6 +113,7 @@ fn census_closure_frontier_row_groups() -> List> { cloudflare_account_api_tokens_frontier_rows, cloudflare_r2_frontier_rows, network_boot_delivery_frontier_rows, + site_uefi_arm64_pxe_edge_frontier_rows, cloudflare_r2_supplier_frontier_rows, backblaze_b2_frontier_rows, gcs_storage_frontier_rows, diff --git a/dag/gunbc/site_uefi_arm64_pxe_edge.dag b/dag/gunbc/site_uefi_arm64_pxe_edge.dag new file mode 100644 index 00000000000..3239eb67e7a --- /dev/null +++ b/dag/gunbc/site_uefi_arm64_pxe_edge.dag @@ -0,0 +1,328 @@ +module gunbc.site_uefi_arm64_pxe_edge + +import std.types { Bool, EpochMs, Int, List, NonEmptyStr, String } +import extdeps.uri { Uri, Tftp } +import std.content_hash { ContentHash } +import std.decl_ref { DeclarationRef, decl_ref } +import std.roster_frontier { FrontierRow, frontier_row_decl } +import std.dissolution { unbound_dissolution } +import extdeps.network.mac { Eui48Address, MacParseResult, parse_mac_address, render_mac_address } +import extdeps.toolchain.types { Architecture, Aarch64 } +import product.placement_supply { SiteIdentity } +import extdeps.network.ipv4 { Ipv4Address, render_ipv4_address } +import extdeps.firmware.ipxe { ipxe_chainloading_authority, ipxe_arm64_efi_snponly, ipxe_build_target_path } +import extdeps.formats.dnsmasq { + DnsmasqDirective, + DnsmasqComment, + DnsmasqPort, + DnsmasqDhcpRangeProxy, + DnsmasqDhcpMatchSet, + DnsmasqDhcpHostSetTag, + DnsmasqDhcpBootTags, + serialize_dnsmasq +} +import gunbc.network_boot_delivery { + SitePxeEdge, + SitePxeEdgeStanding, + SitePxeEdgeEstablished, + SitePxeEdgeUnestablished, + SitePxeEdgeClientArchitectureNotAdmitted, + SitePxeEdgeChainloaderMismatch, + SiteBootstrapDiscovery, + SiteBootstrapTransport, + ArchitectureChainloader, + BootDiscoveryHint, + DhcpFilenameHint, + GlobalHttpsReachability, + GlobalHttpsEndpointReachable, + GlobalHttpsEndpointUnreachable, + site_pxe_edge_standing +} +import extdeps.provisioning.dhcp_client_arch { UefiArm64, dhcp_processor_architecture_code } +import gunbc.machine_intake_mtcollins1_netboot_client_observation { mtcollins1_host_nic_mac } + +// THE SITE DISCOVERY EDGE for the first onboarding segment, UEFI ARM64 only. It consumes +// gunbc.network_boot_delivery site_pxe_edge_standing and adds the three things that function +// takes as given: HOW discovery was observed to answer, WHETHER the global HTTPS endpoint is +// reachable from the boot network (as three independent observations, not one), and WHICH +// machine is asking. Only the aarch64 chainloader lives here (extdeps.firmware.ipxe +// ipxe_chainloading_authority, cited in the rendered config); every later stage is fetched over +// global HTTPS. + +// DISCOVERY STANDING IS AN OBSERVED OFFER, NOT A CONFIGURED DAEMON. A mode (native DHCP, +// ProxyDHCP, relay) answers only when an offer carrying the boot filename was seen reaching +// the client on this segment. An offer with no boot filename is the mtcollins1 segment today +// (gunbc.machine_intake_mtcollins1_netboot_client_observation: address, gateway, DNS, no option +// 67), and it establishes nothing. +type SiteDhcpOfferObservation + = BootOfferObserved { mode: SiteBootstrapDiscovery, offered_filename: NonEmptyStr, observation: DeclarationRef, observed_at: EpochMs } + | AddressOnlyOfferObserved { observation: DeclarationRef, observed_at: EpochMs } + | NoOfferObserved + +// WHY DISCOVERY DID NOT ANSWER IS KEPT, not collapsed into SiteDiscoveryUnobserved: no offer at all, +// an address-only offer (the measured mtcollins1 segment), and an offer naming a different boot file +// are three different facts about the segment with three different remedies, and the two observed +// ones carry the observation that established them. +type SiteDiscoveryRefusal + = NoBootOfferObserved + | AddressOnlyOffer { observation: DeclarationRef, observed_at: EpochMs } + | OfferedFilenameMismatch { offered: NonEmptyStr, observation: DeclarationRef, observed_at: EpochMs } + +type SiteDiscoveryOutcome + = SiteDiscoveryAnswered { mode: SiteBootstrapDiscovery } + | SiteDiscoveryRefused { refusal: SiteDiscoveryRefusal } + +fn site_discovery_outcome(offer: SiteDhcpOfferObservation, hint: BootDiscoveryHint) -> SiteDiscoveryOutcome { + match offer { + NoOfferObserved => SiteDiscoveryRefused { refusal: NoBootOfferObserved } + AddressOnlyOfferObserved { observation: o, observed_at: t } => + SiteDiscoveryRefused { refusal: AddressOnlyOffer { observation: o, observed_at: t } } + BootOfferObserved { mode: mode, offered_filename: f, observation: o, observed_at: t } => + if offered_filename_matches_hint(offered: f, hint: hint) { + SiteDiscoveryAnswered { mode: mode } + } else { + SiteDiscoveryRefused { refusal: OfferedFilenameMismatch { offered: f, observation: o, observed_at: t } } + } + } +} + +fn offered_filename_matches_hint(offered: NonEmptyStr, hint: BootDiscoveryHint) -> Bool { + match hint { + DhcpFilenameHint { filename: expected } => offered as String == expected as String + } +} + +// REACHABILITY FROM THE BOOT NETWORK is three facts with three producers: a resolver on the +// segment answered for the endpoint host, the segment has a default route, and an HTTPS fetch +// of the endpoint completed from the segment. None implies another -- a resolver can answer +// with no route out, a route can exist with no resolver -- so each is observed separately and +// GlobalHttpsEndpointReachable is minted only when all three are. +type BootNetworkProbe + = BootNetworkProbeSucceeded { observation: DeclarationRef, observed_at: EpochMs } + | BootNetworkProbeFailed { observation: DeclarationRef, observed_at: EpochMs } + | BootNetworkProbeUnobserved + +// The probes name the endpoints they were aimed at, so the reachability the edge mints says WHICH +// broker and origin answered from the boot network; the join then requires those to equal the +// fleet receipt's https_boot_endpoint and origin_custom_domain. +type BootNetworkReachabilityObservation { + broker_endpoint: Uri + artifact_origin: Uri + dns_resolution: BootNetworkProbe + default_route: BootNetworkProbe + global_https_fetch: BootNetworkProbe +} + +type BootNetworkReachabilityAxis + = BootNetworkDns + | BootNetworkDefaultRoute + | BootNetworkGlobalHttps + +// A DEFICIT CARRIES ITS CAUSE: a probe that ran and failed keeps the observation that established the +// failure, and an axis nobody probed says so. "Not observed" is a missing reading, not the deduced +// fact "unreachable", so the two are never reported as one. +type BootNetworkReachabilityDeficit + = BootNetworkAxisProbeFailed { axis: BootNetworkReachabilityAxis, observation: DeclarationRef, observed_at: EpochMs } + | BootNetworkAxisUnobserved { axis: BootNetworkReachabilityAxis } + +fn reachability_axis_missing(probe: BootNetworkProbe, axis: BootNetworkReachabilityAxis) -> List { + match probe { + BootNetworkProbeSucceeded { observation: _, observed_at: _ } => [] + BootNetworkProbeFailed { observation: o, observed_at: t } => [BootNetworkAxisProbeFailed { axis: axis, observation: o, observed_at: t }] + BootNetworkProbeUnobserved => [BootNetworkAxisUnobserved { axis: axis }] + } +} + +fn boot_network_reachability_missing(obs: BootNetworkReachabilityObservation) -> List { + concat( + concat( + reachability_axis_missing(probe: obs.dns_resolution, axis: BootNetworkDns), + reachability_axis_missing(probe: obs.default_route, axis: BootNetworkDefaultRoute), + ), + reachability_axis_missing(probe: obs.global_https_fetch, axis: BootNetworkGlobalHttps), + ) +} + +fn global_https_reachability_from_boot_network(obs: BootNetworkReachabilityObservation) -> GlobalHttpsReachability { + match obs.global_https_fetch { + BootNetworkProbeFailed { observation: _, observed_at: _ } => GlobalHttpsEndpointUnreachable + BootNetworkProbeUnobserved => GlobalHttpsEndpointUnreachable + BootNetworkProbeSucceeded { observation: o, observed_at: t } => + if (length(boot_network_reachability_missing(obs: obs)) == 0) { + GlobalHttpsEndpointReachable { broker_endpoint: obs.broker_endpoint, artifact_origin: obs.artifact_origin, observation: o, observed_at: t } + } else { + GlobalHttpsEndpointUnreachable + } + } +} + +// THE CLIENT AS IT DESCRIBED ITSELF: the MAC it sent from and its DHCP option 93 architecture +// code. Only the RFC 4578 UEFI ARM64 code is recognized (the code mtcollins1 presented is COMPARED +// against it, never the source of it); any other code is carried as read and refused -- it is never +// guessed onto an Architecture. +type EdgeClientRequest { + mac: Eui48Address + arch_code: Int +} + +data uefi_arm64_client_arch_code: Int = dhcp_processor_architecture_code(a: UefiArm64) + +// Known machines are named by the edge, not inferred from reaching it: a client absent from +// this list gets no admitted boot even with the right architecture. The roster carries only +// what the edge AUTHORS; discovery and reachability are never authored here, they are derived +// from the observations beside them. +type SiteEdgeRoster { + site: SiteIdentity + admitted_architectures: List + chainloader_digest: ContentHash + tftp_server_name: NonEmptyStr + tftp_server_address: Ipv4Address + known_client_macs: List +} + +// What was OBSERVED on the segment for one admission, kept apart from what the edge authors so the +// config (which renders only the roster) never needs an observation to exist. +type SiteEdgeObservation { + offer: SiteDhcpOfferObservation + reachability: BootNetworkReachabilityObservation + observation: DeclarationRef + observed_at: EpochMs +} + +type SiteEdgeClientAdmission + = SiteEdgeClientAdmitted { standing: SitePxeEdgeStanding } + | SiteEdgeUnknownMachineRefused { mac: Eui48Address } + | SiteEdgeUnknownArchitectureRefused { arch_code: Int } + | SiteEdgeClientArchitectureNotAdmitted { architecture: Architecture } + | SiteEdgeChainloaderMismatch { client: Architecture, chainloader: Architecture } + | SiteEdgeDiscoveryRefused { refusal: SiteDiscoveryRefusal } + | SiteEdgeNotEstablished { standing: SitePxeEdgeStanding, unreachable: List } + +fn client_is_known(roster: SiteEdgeRoster, mac: Eui48Address) -> Bool { + roster.known_client_macs |> any(m => m == mac) +} + +// THE TFTP ROUTE IDENTITY IS DERIVED FROM THE SERVER THE CONFIG NAMES. The dhcp-boot line renders +// tftp_server_name and tftp_server_address, and the transport's tftp_root (which the join compares to +// the manifest) is built from that same address, so the route the model checks is the route the +// config tells firmware to use. +fn site_edge_tftp_root(roster: SiteEdgeRoster) -> Uri { + Uri { scheme: Tftp, locator: concat(render_ipv4_address(addr: roster.tftp_server_address) as String, "/") } +} + +// THE EDGE'S ONE CHAINLOADER, DERIVED. The architecture and the offered filename are the +// extdeps.firmware.ipxe build target this edge serves, never authored beside it: the rendered +// dhcp-boot line and the filename an observed offer is checked against are both this transport. +fn site_edge_arm64_chainloader_transport(roster: SiteEdgeRoster) -> SiteBootstrapTransport { + ArchitectureChainloader { + architecture: Aarch64, + filename_hint: DhcpFilenameHint { filename: ipxe_arm64_efi_snponly.image }, + chainloader_digest: roster.chainloader_digest, + tftp_root: site_edge_tftp_root(roster: roster), + } +} + +fn observed_edge(roster: SiteEdgeRoster, observed: SiteEdgeObservation, discovery: SiteBootstrapDiscovery) -> SitePxeEdge { + SitePxeEdge { + site: roster.site, + admitted_architectures: roster.admitted_architectures, + discovery: discovery, + bootstrap_transport: site_edge_arm64_chainloader_transport(roster: roster), + global_endpoint_reachability: global_https_reachability_from_boot_network(obs: observed.reachability), + } +} + +fn chainloader_hint(transport: SiteBootstrapTransport) -> BootDiscoveryHint { + match transport { + ArchitectureChainloader { architecture: _, filename_hint: h, chainloader_digest: _, tftp_root: _ } => h + } +} + +fn site_edge_client_admission(roster: SiteEdgeRoster, observed: SiteEdgeObservation, request: EdgeClientRequest) -> SiteEdgeClientAdmission { + if (request.arch_code != uefi_arm64_client_arch_code) { + SiteEdgeUnknownArchitectureRefused { arch_code: request.arch_code } + } else { + if client_is_known(roster: roster, mac: request.mac) == false { + SiteEdgeUnknownMachineRefused { mac: request.mac } + } else { + match site_discovery_outcome(offer: observed.offer, hint: chainloader_hint(transport: site_edge_arm64_chainloader_transport(roster: roster))) { + SiteDiscoveryRefused { refusal: r } => SiteEdgeDiscoveryRefused { refusal: r } + SiteDiscoveryAnswered { mode: mode } => + site_edge_admission_of_standing( + standing: site_pxe_edge_standing(edge: observed_edge(roster: roster, observed: observed, discovery: mode), client_architecture: Aarch64, observation: observed.observation, observed_at: observed.observed_at), + unreachable: boot_network_reachability_missing(obs: observed.reachability), + ) + } + } + } +} + +fn site_edge_admission_of_standing(standing: SitePxeEdgeStanding, unreachable: List) -> SiteEdgeClientAdmission { + match standing { + SitePxeEdgeEstablished { receipt: _ } => SiteEdgeClientAdmitted { standing: standing } + SitePxeEdgeUnestablished => SiteEdgeNotEstablished { standing: standing, unreachable: unreachable } + SitePxeEdgeClientArchitectureNotAdmitted { architecture: a } => SiteEdgeClientArchitectureNotAdmitted { architecture: a } + SitePxeEdgeChainloaderMismatch { client: c, chainloader: l } => SiteEdgeChainloaderMismatch { client: c, chainloader: l } + } +} + +// THE PROXYDHCP REALIZATION, rendered through extdeps.formats.dnsmasq as a FORMAT: it is what an +// edge on a segment with an existing DHCP server must say, and it is never evidence -- standing +// comes only from the observed offer above. Only the aarch64 tag gets a boot line, and that line +// also requires the known-machine tag, so the rendered edge offers nothing to an unknown machine +// or architecture. +data site_edge_arm64_tag: String = "efi-arm64" + +data site_edge_known_tag: String = "onboarding-known" + +fn site_edge_proxy_dhcp_directives(network_address: Ipv4Address, roster: SiteEdgeRoster) -> List { + concat( + [ + DnsmasqComment { text: join(["gunbc.site_uefi_arm64_pxe_edge: ProxyDHCP, UEFI ARM64 chainloader only: iPXE ", ipxe_build_target_path(target: ipxe_arm64_efi_snponly) as String, " per ", ipxe_chainloading_authority.uri.locator], "") }, + DnsmasqPort { value: 0 }, + DnsmasqDhcpRangeProxy { network_address: render_ipv4_address(addr: network_address) as String }, + DnsmasqDhcpMatchSet { tag: site_edge_arm64_tag, option: "option:client-arch", value: to_string(uefi_arm64_client_arch_code) }, + ], + concat( + map(roster.known_client_macs, m => DnsmasqDhcpHostSetTag { mac: render_mac_address(addr: m) as String, tag: site_edge_known_tag }), + [DnsmasqDhcpBootTags { first_tag: site_edge_arm64_tag, more_tags: [site_edge_known_tag], target: site_edge_offered_filename(roster: roster) as String, server_name: roster.tftp_server_name as String, server_address: render_ipv4_address(addr: roster.tftp_server_address) as String }], + ), + ) +} + +// The config renders the SAME roster admission gates on: its known-machine tags are the roster's MACs +// and its boot line is the roster's derived transport, so the offered file and the checked file are +// one value. +fn site_edge_proxy_dhcp_config(network_address: Ipv4Address, roster: SiteEdgeRoster) -> String { + serialize_dnsmasq(directives: site_edge_proxy_dhcp_directives(network_address: network_address, roster: roster)) +} + +fn site_edge_offered_filename(roster: SiteEdgeRoster) -> NonEmptyStr { + match chainloader_hint(transport: site_edge_arm64_chainloader_transport(roster: roster)) { + DhcpFilenameHint { filename: f } => f + } +} + +// The first onboarding segment's only known client, parsed from the measured observation's wire text +// rather than re-authored as octets, so the roster and the observation cannot disagree. A parse +// failure is a MacParseFailed every consumer must refuse on, never an empty roster. +data first_onboarding_known_client_mac: MacParseResult = parse_mac_address(text: mtcollins1_host_nic_mac as String) + +// The two production routes this module provides and nothing in this change executes, as counted +// rows (enrolled in gunbc.census_closure_frontier) rather than prose. +data site_uefi_arm64_pxe_edge_frontier_rows: List = [ + frontier_row_decl( + ref: decl_ref(module_path: "gunbc.site_uefi_arm64_pxe_edge", decl_name: "site_edge_client_admission"), + reason: "no route binds an observed srv4 boot offer into gunbc.network_boot_delivery NetworkBootJoinInputs.site; the admission verdict is constructed only by witnesses over supplied rosters", + dissolution: unbound_dissolution( + description: "dissolves when the first boot offer observed from the converged srv4 edge is minted as a SiteDhcpOfferObservation and bound through site_edge_client_admission into NetworkBootJoinInputs.site on a real boot attempt; a witness over supplied offers does not fire this" + ) + ), + frontier_row_decl( + ref: decl_ref(module_path: "gunbc.site_uefi_arm64_pxe_edge", decl_name: "site_edge_proxy_dhcp_config"), + reason: "no production route writes the rendered ProxyDHCP config to the edge host in this change", + dissolution: unbound_dissolution( + description: "dissolves when gunbc.site_pxe_edge_converge site_pxe_edge_config_file writes this rendering to srv4 through the fleet-converge site_pxe_edge_converge mode (PXE-FABRIC 0C follow-up)" + ) + ), +] diff --git a/dag/test/claim/site_uefi_arm64_pxe_edge_witness_test.dag b/dag/test/claim/site_uefi_arm64_pxe_edge_witness_test.dag new file mode 100644 index 00000000000..d44a5b0da5e --- /dev/null +++ b/dag/test/claim/site_uefi_arm64_pxe_edge_witness_test.dag @@ -0,0 +1,311 @@ +module test.claim.site_uefi_arm64_pxe_edge_witness + +import std.types { Bool, List, NonEmptyStr, String } +import extdeps.network.mac { Eui48Address, MacParsed, MacParseFailed, mac_address } +import std.content_hash { content_hash_of_value } +import extdeps.uri { Uri, Https, Tftp } +import std.decl_ref { DeclarationRef, WholeDeclaration } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } +import extdeps.toolchain.types { Aarch64, X86_64 } +import product.placement_supply { SiteIdentity } +import extdeps.network.ipv4 { ipv4_address } +import extdeps.firmware.ipxe { ipxe_arm64_efi_snponly } +import extdeps.provisioning.network_boot { ProxyDhcp } +import gunbc.network_boot_delivery { + SitePxeEdgeEstablished, + ArchitectureChainloader, + SiteBootstrapDiscovery, + SiteAnsweredDhcp, + SiteDhcpRelay, + DhcpFilenameHint, + first_slice_admitted_architectures +} +import gunbc.machine_intake_mtcollins1_netboot_client_observation { + mtcollins1_pxe_arch_code +} +import gunbc.site_uefi_arm64_pxe_edge { + SiteDhcpOfferObservation, + BootOfferObserved, + AddressOnlyOfferObserved, + BootNetworkProbe, + BootNetworkProbeSucceeded, + BootNetworkProbeFailed, + BootNetworkProbeUnobserved, + BootNetworkReachabilityObservation, + BootNetworkReachabilityAxis, + BootNetworkReachabilityDeficit, + BootNetworkAxisProbeFailed, + BootNetworkAxisUnobserved, + BootNetworkDns, + BootNetworkDefaultRoute, + BootNetworkGlobalHttps, + EdgeClientRequest, + SiteEdgeRoster, + SiteEdgeObservation, + SiteEdgeClientAdmission, + SiteEdgeClientAdmitted, + SiteEdgeUnknownMachineRefused, + SiteEdgeUnknownArchitectureRefused, + SiteEdgeNotEstablished, + SiteEdgeClientArchitectureNotAdmitted, + SiteEdgeChainloaderMismatch, + SiteEdgeDiscoveryRefused, + AddressOnlyOffer, + OfferedFilenameMismatch, + first_onboarding_known_client_mac, + site_edge_client_admission, + site_edge_proxy_dhcp_config +} + +data proxy_dhcp: SiteBootstrapDiscovery = SiteAnsweredDhcp { direction: ProxyDhcp } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data obs: DeclarationRef = DeclarationRef { + module_path: "test.claim.site_uefi_arm64_pxe_edge_witness", + decl_name: "obs", + field: WholeDeclaration, +} + +data known_mac: Eui48Address = mac_address(octet1: 2, octet2: 0, octet3: 0, octet4: 0, octet5: 12, octet6: 1) + +data unknown_mac: Eui48Address = mac_address(octet1: 2, octet2: 0, octet3: 0, octet4: 0, octet5: 12, octet6: 255) + +data chainloader_filename: NonEmptyStr = ipxe_arm64_efi_snponly.image + +fn ok() -> BootNetworkProbe { + BootNetworkProbeSucceeded { observation: obs, observed_at: 1788000000000 } +} + +fn failed() -> BootNetworkProbe { + BootNetworkProbeFailed { observation: obs, observed_at: 1788000000000 } +} + +fn reach(dns: BootNetworkProbe, route: BootNetworkProbe, https: BootNetworkProbe) -> BootNetworkReachabilityObservation { + BootNetworkReachabilityObservation { broker_endpoint: Uri { scheme: Https, locator: "boot.example.test/manifest" }, artifact_origin: Uri { scheme: Https, locator: "boot-origin.example.test" }, dns_resolution: dns, default_route: route, global_https_fetch: https } +} + +fn boot_offer(mode: SiteBootstrapDiscovery, filename: NonEmptyStr) -> SiteDhcpOfferObservation { + BootOfferObserved { mode: mode, offered_filename: filename, observation: obs, observed_at: 1788000000000 } +} + +type EdgeCase { + roster: SiteEdgeRoster + observed: SiteEdgeObservation +} + +fn roster_of(macs: List) -> SiteEdgeRoster { + SiteEdgeRoster { + site: "onboarding-segment-0c" as SiteIdentity, + admitted_architectures: first_slice_admitted_architectures, + chainloader_digest: content_hash_of_value(value: "ipxe-aarch64-fixture"), + tftp_server_name: "srv4", + tftp_server_address: ipv4_address(octet1: 192, octet2: 168, octet3: 1, octet4: 196), + known_client_macs: macs, + } +} + +fn roster(macs: List, offer: SiteDhcpOfferObservation, reachability: BootNetworkReachabilityObservation) -> EdgeCase { + EdgeCase { + roster: roster_of(macs: macs), + observed: SiteEdgeObservation { offer: offer, reachability: reachability, observation: obs, observed_at: 1788000000000 }, + } +} + +fn healthy_roster() -> EdgeCase { + roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: chainloader_filename), reachability: reach(dns: ok(), route: ok(), https: ok())) +} + +fn arm64_request(mac: Eui48Address) -> EdgeClientRequest { + EdgeClientRequest { mac: mac, arch_code: 11 } +} + +fn not_established_with(admission: SiteEdgeClientAdmission, expected: List) -> Bool { + match admission { + SiteEdgeNotEstablished { standing: _, unreachable: u } => u == expected + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + } +} + +test fn known_arm64_client_on_observed_proxy_edge_is_admitted() -> Bool { + match site_edge_client_admission(roster: healthy_roster().roster, observed: healthy_roster().observed, request: arm64_request(mac: known_mac)) { + SiteEdgeClientAdmitted { standing: SitePxeEdgeEstablished { receipt: r } } => r.edge.discovery == proxy_dhcp + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + } +} + +test fn relay_offer_is_admitted_as_relay() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: SiteDhcpRelay, filename: chainloader_filename), reachability: reach(dns: ok(), route: ok(), https: ok())) + match site_edge_client_admission(roster: r.roster, observed: r.observed, request: arm64_request(mac: known_mac)) { + SiteEdgeClientAdmitted { standing: SitePxeEdgeEstablished { receipt: rc } } => rc.edge.discovery == SiteDhcpRelay + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + } +} + +test fn unknown_machine_gets_no_boot() -> Bool { + match site_edge_client_admission(roster: healthy_roster().roster, observed: healthy_roster().observed, request: arm64_request(mac: unknown_mac)) { + SiteEdgeUnknownMachineRefused { mac: m } => m == unknown_mac + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + } +} + +test fn unknown_architecture_gets_no_boot_even_when_known() -> Bool { + match site_edge_client_admission(roster: healthy_roster().roster, observed: healthy_roster().observed, request: EdgeClientRequest { mac: known_mac, arch_code: 7 }) { + SiteEdgeUnknownArchitectureRefused { arch_code: c } => c == 7 + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + } +} + +test fn dns_failure_alone_unestablishes_the_edge() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: chainloader_filename), reachability: reach(dns: failed(), route: ok(), https: ok())) + not_established_with(admission: site_edge_client_admission(roster: r.roster, observed: r.observed, request: arm64_request(mac: known_mac)), expected: [BootNetworkAxisProbeFailed { axis: BootNetworkDns, observation: obs, observed_at: 1788000000000 }]) +} + +test fn missing_default_route_alone_unestablishes_the_edge() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: chainloader_filename), reachability: reach(dns: ok(), route: BootNetworkProbeUnobserved, https: ok())) + not_established_with(admission: site_edge_client_admission(roster: r.roster, observed: r.observed, request: arm64_request(mac: known_mac)), expected: [BootNetworkAxisUnobserved { axis: BootNetworkDefaultRoute }]) +} + +test fn failed_https_fetch_alone_unestablishes_the_edge() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: chainloader_filename), reachability: reach(dns: ok(), route: ok(), https: failed())) + not_established_with(admission: site_edge_client_admission(roster: r.roster, observed: r.observed, request: arm64_request(mac: known_mac)), expected: [BootNetworkAxisProbeFailed { axis: BootNetworkGlobalHttps, observation: obs, observed_at: 1788000000000 }]) +} + +test fn offer_with_other_filename_is_refused_with_the_offered_name() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: "ipxe.efi"), reachability: reach(dns: ok(), route: ok(), https: ok())) + match site_edge_client_admission(roster: r.roster, observed: r.observed, request: arm64_request(mac: known_mac)) { + SiteEdgeDiscoveryRefused { refusal: OfferedFilenameMismatch { offered: f, observation: _, observed_at: _ } } => f as String == "ipxe.efi" + SiteEdgeDiscoveryRefused { refusal: _ } => false + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + } +} + +// THE REAL ROUTE, on the measured facts: mtcollins1's own MAC and option-93 code against the +// first segment's roster. The segment's router answered with an address and no boot filename, +// so the known ARM64 client is refused as an ADDRESS-ONLY OFFER carrying that observation -- on +// discovery, not on identity or architecture. +test fn mtcollins1_on_its_measured_segment_is_not_yet_admitted() -> Bool { + match first_onboarding_known_client_mac { + MacParseFailed { error: _ } => false + MacParsed { address: host } => { + let r = roster( + macs: [host], + offer: AddressOnlyOfferObserved { observation: obs, observed_at: 1788620040000 }, + reachability: reach(dns: ok(), route: ok(), https: ok()), + ) + match site_edge_client_admission(roster: r.roster, observed: r.observed, request: EdgeClientRequest { mac: host, arch_code: mtcollins1_pxe_arch_code }) { + SiteEdgeDiscoveryRefused { refusal: AddressOnlyOffer { observation: _, observed_at: t } } => t == 1788620040000 + SiteEdgeDiscoveryRefused { refusal: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + } + } + } +} + +// THE ROSTER IS THE MEASURED HOST NIC, PARSED: 28:c1:3c:8a:d6:4a, not re-authored octets. +test fn the_landed_roster_parses_to_the_measured_host_nic() -> Bool { + match first_onboarding_known_client_mac { + MacParseFailed { error: _ } => false + MacParsed { address: a } => a == mac_address(octet1: 40, octet2: 193, octet3: 60, octet4: 138, octet5: 214, octet6: 74) + } +} + +test fn proxy_dhcp_config_boots_only_known_arm64() -> Bool { + let cfg = site_edge_proxy_dhcp_config(network_address: ipv4_address(octet1: 192, octet2: 168, octet3: 1, octet4: 0), roster: roster_of(macs: [known_mac])) + contains(cfg, "port=0") + && contains(cfg, "dhcp-range=192.168.1.0,proxy") + && contains(cfg, "dhcp-match=set:efi-arm64,option:client-arch,11") + && contains(cfg, "dhcp-host=02:00:00:00:0c:01,set:onboarding-known") + && contains(cfg, "dhcp-boot=tag:efi-arm64,tag:onboarding-known,snponly.efi,srv4,192.168.1.196") + && contains(cfg, "iPXE bin-arm64-efi/snponly.efi per ipxe.org/howto/chainloading") + && length(split(cfg, "dhcp-boot=")) == 2 +} + +// THE OTHER ARCHITECTURE REFUSAL, located on its own subject: an edge that admits no aarch64 client +// refuses the client's architecture, not the chainloader's. +test fn an_edge_admitting_no_arm64_refuses_the_client_architecture() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: chainloader_filename), reachability: reach(dns: ok(), route: ok(), https: ok())) + let closed = EdgeCase { + roster: SiteEdgeRoster { + site: r.roster.site, + admitted_architectures: [X86_64], + chainloader_digest: r.roster.chainloader_digest, + tftp_server_name: r.roster.tftp_server_name, + tftp_server_address: r.roster.tftp_server_address, + known_client_macs: r.roster.known_client_macs, + }, + observed: r.observed, + } + match site_edge_client_admission(roster: closed.roster, observed: closed.observed, request: arm64_request(mac: known_mac)) { + SiteEdgeClientArchitectureNotAdmitted { architecture: a } => a == Aarch64 + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + } +} + +// A PROBE THAT FAILED AND A PROBE NOBODY RAN ARE DIFFERENT DEFICITS on the same axis. +test fn an_unprobed_dns_axis_is_unobserved_not_failed() -> Bool { + let r = roster(macs: [known_mac], offer: boot_offer(mode: proxy_dhcp, filename: chainloader_filename), reachability: reach(dns: BootNetworkProbeUnobserved, route: ok(), https: ok())) + not_established_with(admission: site_edge_client_admission(roster: r.roster, observed: r.observed, request: arm64_request(mac: known_mac)), expected: [BootNetworkAxisUnobserved { axis: BootNetworkDns }]) +} + +// THE ROUTE THE JOIN CHECKS IS THE SERVER THE CONFIG NAMES: an admitted edge's transport carries a +// tftp_root built from the roster's tftp_server_address, the same address the dhcp-boot line renders. +test fn admitted_edge_tftp_route_is_the_rendered_server() -> Bool { + let c = healthy_roster() + match site_edge_client_admission(roster: c.roster, observed: c.observed, request: arm64_request(mac: known_mac)) { + SiteEdgeClientAdmitted { standing: SitePxeEdgeEstablished { receipt: r } } => + match r.edge.bootstrap_transport { + ArchitectureChainloader { architecture: _, filename_hint: _, chainloader_digest: _, tftp_root: route } => + route == Uri { scheme: Tftp, locator: "192.168.1.196/" } + } + SiteEdgeClientAdmitted { standing: _ } => false + SiteEdgeUnknownMachineRefused { mac: _ } => false + SiteEdgeUnknownArchitectureRefused { arch_code: _ } => false + SiteEdgeNotEstablished { standing: _, unreachable: _ } => false + SiteEdgeClientArchitectureNotAdmitted { architecture: _ } => false + SiteEdgeChainloaderMismatch { client: _, chainloader: _ } => false + SiteEdgeDiscoveryRefused { refusal: _ } => false + } +}