Files
OpenPXE/scripts/fetch-shim.sh
T
Miles WardandClaude Opus 4.8 3a32d65fb7 v0.7.0: Secure Boot chain, boot rules + decision webhook, tokenized answer files
Three features, all zero-toggle and principle-clean (single static musl
binary, container-first, no test certs, no client trust-store changes).

Secure Boot via signed shim+GRUB (automatic):
- The v0.6.1 escalation ladder gains a third rung: Firmware -> Builtin
  -> Shim. Secure-Boot firmware downloads our unsigned iPXE but refuses
  to execute it — indistinguishable from a failed chainload — so after
  two unconfirmed attempts the MAC is offered Fedora's Microsoft-signed
  shimx64.efi, which loads the signed GRUB, which fetches a
  server-rendered grub.cfg. Fully signed chain, SB stays on.
- scripts/fetch-shim.sh pulls shim-x64/grub2-efi-x64 (+aa64 best-effort)
  from the official Fedora 43 packages and ships the EFI binaries
  byte-for-byte unmodified; Dockerfile fetch stage gained rpm2cpio/cpio.
- New grub_script renderer (Linux kernel entries only — signed GRUB only
  boots signed kernels; sanboot/wimboot have no signed equivalent and
  are omitted with an explanatory menu line).
- TFTP server gains a DynamicAsset hook for server-rendered names
  (grub.cfg); HTTP serves the same config under /ipxe/grub.cfg for
  native UEFI HTTP Boot chains. Arch-aware fallback walks back down the
  ladder where no shim exists (BIOS, IA32).

Boot rules + decision webhook (open 'Matrix Boot'):
- Ordered first-match-wins rules over MAC prefix + client arch (the DHCP
  proxy now bakes arch into the boot.ipxe chain URL), generalizing
  per-MAC pins. Persisted to boot_rules.json; GET/PUT /api/boot-rules;
  rules editor + webhook field on the Hosts tab.
- Optional pixiecore-style webhook: unmatched boots GET
  <url>?mac=&arch= and 200 {"target":"id"} chains to it. Fail-open
  with a 2s budget — a dead endpoint can never block PXE.
- Decision order: exact pin -> rules -> webhook -> menu. Empty config
  is byte-for-byte the previous behavior.

Tokenized answer files (the post-WDS/CVE-2026-0386 hardening):
- Every generated unattended URL (inst.ks / preseed url / autoinstall
  seed) now carries a 4h boot-scoped token; /unattended/{id} and the
  cloud-init seed routes require it (or an operator session) once an
  admin exists. Stops answer-file credential harvesting by anything
  else on the network. No toggle; setup-mode installs stay open.

Validation: clippy clean, fmt clean, 290 workspace tests green
(+18 new across boot_tokens, boot_rules, arch ladder, escalation,
grub renderer, and four new full-flow integration tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-09 20:17:18 -04:00

97 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
# Fetch Fedora's Microsoft-signed Secure Boot chain — shim + GRUB — and
# place the EFI binaries under assets/ipxe/ with the filenames OpenPXE's
# DriverMode::Shim mapping expects:
#
# shimx64.efi x86_64: Microsoft-signed shim (first stage)
# grubx64.efi x86_64: Fedora-signed GRUB (loaded by shim, fetches
# the server-rendered grub.cfg over TFTP/HTTP)
# shimaa64.efi arm64 equivalents (best-effort — see below)
# grubaa64.efi
#
# Why Fedora: a supply-chain decision made deliberately (v0.7.0) — one
# vendor, fast security turnaround, and the same chain most netboot
# projects redistribute. The binaries are extracted from the official
# distro RPMs and shipped BYTE-FOR-BYTE UNMODIFIED; their signatures are
# what make the chain work, and modifying them would break it. This is
# the standard documented netboot path for Secure Boot (Red Hat
# Satellite, SUSE HTTPBoot) and involves no test certificates and no
# client trust-store changes.
#
# Trust model matches fetch-ipxe.sh: HTTPS to the official distribution
# point, no sha pinning because we track the latest signed build (which
# rotates on SBAT revocations — pinning would mean shipping revoked
# shims). Mirror to your own artifact store for deterministic builds.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DEST="${1:-$ROOT/assets/ipxe}"
mkdir -p "$DEST"
FEDORA_RELEASE="${FEDORA_RELEASE:-43}"
BASE="${FEDORA_MIRROR:-https://dl.fedoraproject.org/pub/fedora/linux/releases/$FEDORA_RELEASE/Everything}"
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
# Find the newest RPM in a repo directory whose name starts with
# `$pattern` followed by a version digit (anchoring on the digit keeps
# `grub2-efi-x64` from matching `grub2-efi-x64-cdboot`).
latest_rpm() {
local dir_url="$1" pattern="$2"
curl -fsSL "$dir_url/" \
| grep -oE "href=\"${pattern}-[0-9][^\"]*\.rpm\"" \
| sed 's/^href="//; s/"$//' \
| sort -V | tail -1
}
# fetch_chain <repo-arch> <shim-pkg> <grub-pkg> <shim-out> <grub-out> <hard|soft>
fetch_chain() {
local arch="$1" shim_pkg="$2" grub_pkg="$3" shim_out="$4" grub_out="$5" mode="$6"
local pkg_base="$BASE/$arch/os/Packages"
local sdir="$pkg_base/${shim_pkg:0:1}" gdir="$pkg_base/${grub_pkg:0:1}"
local shim_rpm grub_rpm
shim_rpm="$(latest_rpm "$sdir" "$shim_pkg" || true)"
grub_rpm="$(latest_rpm "$gdir" "$grub_pkg" || true)"
if [ -z "$shim_rpm" ] || [ -z "$grub_rpm" ]; then
echo "!! could not locate $shim_pkg/$grub_pkg RPMs under $pkg_base"
[ "$mode" = "hard" ] && exit 2
echo " skipping $arch Secure Boot chain (best-effort)"
return 0
fi
echo ">> $arch: $shim_rpm + $grub_rpm"
local exdir="$WORK/$arch"
mkdir -p "$exdir"
curl -fsSL -o "$exdir/shim.rpm" "$sdir/$shim_rpm"
curl -fsSL -o "$exdir/grub.rpm" "$gdir/$grub_rpm"
( cd "$exdir" \
&& rpm2cpio shim.rpm | cpio -idm --quiet "./boot/efi/EFI/*/$shim_out" \
&& rpm2cpio grub.rpm | cpio -idm --quiet "./boot/efi/EFI/*/$grub_out" )
local shim_path grub_path
shim_path="$(find "$exdir/boot" -name "$shim_out" | head -1)"
grub_path="$(find "$exdir/boot" -name "$grub_out" | head -1)"
if [ -z "$shim_path" ] || [ -z "$grub_path" ]; then
echo "!! RPM layout changed — $shim_out/$grub_out not found inside the packages"
[ "$mode" = "hard" ] && exit 2
return 0
fi
cp "$shim_path" "$DEST/$shim_out"
cp "$grub_path" "$DEST/$grub_out"
echo " installed $shim_out + $grub_out"
}
# x86_64 is the headline Secure Boot audience — fail the build if it
# can't be assembled so a regression is loud, not silent.
fetch_chain x86_64 shim-x64 grub2-efi-x64 shimx64.efi grubx64.efi hard
# arm64 is best-effort: skipping just means no Shim escalation rung for
# that arch (logged at startup by ipxe-assets::log_availability).
fetch_chain aarch64 shim-aa64 grub2-efi-aa64 shimaa64.efi grubaa64.efi soft
echo
echo "Secure Boot chain assets now in $DEST:"
ls -lh "$DEST"/shim*.efi "$DEST"/grub*.efi 2>/dev/null || true