Refactor dnsmasq to build as a container. Add signed iPXE image build
This commit is contained in:
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
# build-ipxe-oci.sh <snponly.efi> <image-ref> <version> [sb-key] [sb-cert] [push]
|
||||
#
|
||||
# Packages the iPXE network bootloader (snponly.efi) as a tiny FROM-scratch OCI
|
||||
# image for the PXE-only chainload path (firmware without UEFI HTTP boot, e.g.
|
||||
# HP EliteBook 820 G4). The dnsmasq serving container mounts this image and
|
||||
# copies the binary into its TFTP root — so the serving host never carries a
|
||||
# loose, unsigned .efi and the bootloader is digest-pinned + signed like the UKI.
|
||||
#
|
||||
# This is DELIBERATELY a separate, independently versioned image (NOT merged into
|
||||
# the FROM-scratch release-artifact image built by build-oci.sh): snponly.efi is
|
||||
# near-static (it changes only when iPXE is rebuilt/re-signed), whereas the
|
||||
# UKI/rootfs change every release. Keeping it separate matches the serving-stack
|
||||
# pattern (SPDK, nginx) and the artifact-image invariant (only the 5 release
|
||||
# files live there). See CLAUDE.md invariant #9 and docs/secure-boot-keys.md.
|
||||
#
|
||||
# Steps:
|
||||
# 1. sbsign snponly.efi with the `db` key/cert if supplied (same key that signs
|
||||
# the UKI — CI/HSM only, never in this repo or the build VM). Unsigned builds
|
||||
# are lab-only and keep a loud warning: a Secure-Boot-enforcing host rejects
|
||||
# an unsigned snponly.efi before iPXE ever runs.
|
||||
# 2. build a FROM-scratch OCI image containing ONLY:
|
||||
# /ipxe/snponly.efi (signed iPXE bootloader)
|
||||
# /ipxe/SHA256SUMS
|
||||
# No shell, no tools, no base image. Pin deployments by digest.
|
||||
#
|
||||
# Requires on the build host: sbsigntools (if signing) and buildah (preferred)
|
||||
# or docker. sbverify (optional) is used to confirm the signature when present.
|
||||
set -euo pipefail
|
||||
|
||||
SRC=${1:?usage: build-ipxe-oci.sh <snponly.efi> <image-ref> <version> [sb-key] [sb-cert] [push]}
|
||||
IMAGE=${2:?image ref required, e.g. registry.example.org/fleet-ipxe}
|
||||
VERSION=${3:?version required (the iPXE binary version/tag)}
|
||||
SB_KEY=${4:-}
|
||||
SB_CERT=${5:-}
|
||||
PUSH=${6:-false}
|
||||
|
||||
[ -f "$SRC" ] || { echo "ERROR: snponly.efi not found at $SRC" >&2; exit 1; }
|
||||
|
||||
STAGE=$(mktemp -d)
|
||||
trap 'rm -rf "$STAGE"' EXIT
|
||||
mkdir "$STAGE/ipxe"
|
||||
OUT="$STAGE/ipxe/snponly.efi"
|
||||
|
||||
# --- 1. Sign (or pass through with a loud warning) ----------------------------
|
||||
SIGNED=false
|
||||
if [ -n "$SB_KEY" ] && [ -n "$SB_CERT" ]; then
|
||||
command -v sbsign >/dev/null || { echo "ERROR: sbsigntools required for signing" >&2; exit 1; }
|
||||
sbsign --key "$SB_KEY" --cert "$SB_CERT" --output "$OUT" "$SRC"
|
||||
SIGNED=true
|
||||
if command -v sbverify >/dev/null; then
|
||||
sbverify --cert "$SB_CERT" "$OUT" >/dev/null \
|
||||
|| { echo "FATAL: signed snponly.efi fails sbverify against the db cert." >&2; exit 1; }
|
||||
echo "==> snponly.efi signed and verified against the db cert"
|
||||
else
|
||||
echo "==> snponly.efi signed (sbverify not present to confirm)"
|
||||
fi
|
||||
else
|
||||
cp "$SRC" "$OUT"
|
||||
echo "WARNING: building UNSIGNED snponly.efi image (no sb-key/sb-cert supplied)." >&2
|
||||
echo " Fine for the lab (Secure Boot OFF); a Secure-Boot-enforcing host" >&2
|
||||
echo " rejects an unsigned iPXE binary. Production must sign in CI/HSM." >&2
|
||||
fi
|
||||
( cd "$STAGE/ipxe" && sha256sum -- * > SHA256SUMS )
|
||||
|
||||
# --- 2. Build the OCI image ---------------------------------------------------
|
||||
REF="${IMAGE}:${VERSION}"
|
||||
LABELS=(
|
||||
"org.opencontainers.image.title=fleet-ipxe"
|
||||
"org.opencontainers.image.version=${VERSION}"
|
||||
"org.fleet.ipxe.signed=${SIGNED}"
|
||||
)
|
||||
|
||||
if command -v buildah >/dev/null; then
|
||||
# vfs storage: overlay-on-overlayfs is unsupported in-container (CI runner);
|
||||
# this image is tiny so vfs is fine. Override via STORAGE_DRIVER on a host
|
||||
# with native overlay support. (Same rationale as build-oci.sh.)
|
||||
export STORAGE_DRIVER="${STORAGE_DRIVER:-vfs}"
|
||||
|
||||
ctr=$(buildah from scratch)
|
||||
buildah copy "$ctr" "$STAGE/ipxe" /ipxe
|
||||
for l in "${LABELS[@]}"; do buildah config --label "$l" "$ctr"; done
|
||||
buildah commit --format oci --rm "$ctr" "$REF"
|
||||
DIGEST=$(buildah inspect --type image --format '{{.FromImageDigest}}' "$REF" || true)
|
||||
[ "$PUSH" = "true" ] && buildah push "$REF"
|
||||
elif command -v docker >/dev/null; then
|
||||
{ printf 'FROM scratch\nCOPY ipxe /ipxe\n'
|
||||
for l in "${LABELS[@]}"; do printf 'LABEL "%s"\n' "$l"; done
|
||||
} > "$STAGE/Dockerfile"
|
||||
docker build -t "$REF" "$STAGE"
|
||||
DIGEST=$(docker inspect --format '{{index .RepoDigests 0}}' "$REF" 2>/dev/null || true)
|
||||
[ "$PUSH" = "true" ] && docker push "$REF"
|
||||
else
|
||||
echo "ERROR: need buildah or docker to build the OCI image" >&2; exit 1
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
==> fleet-ipxe image built: $REF
|
||||
signed: $SIGNED
|
||||
digest: ${DIGEST:-"(available after push)"}
|
||||
|
||||
Deploy: set IPXE_IMAGE/IPXE_VERSION in serving/.env to this ref, then
|
||||
\`docker compose up -d dnsmasq\` (the entrypoint copies /ipxe/snponly.efi
|
||||
into the TFTP root). Pin by digest in production.
|
||||
EOF
|
||||
Reference in New Issue
Block a user