Secure Boot iPXE chain is the default for every UEFI client
Live since 2026-10-14: dnsmasq hands every UEFI client ipxe-shim.efi (iPXE's shim, dual-signed under Microsoft UEFI CA 2011 + 2023), which loads ipxe.efi from the TFTP root, now the iPXE v2.0.0 snponly build (firmware network and USB drivers; the full build kills USB keyboards). The per-MAC test tags and folders are gone; the old Broadcom-signed 1.21.1 build stays as ipxe-bcm.efi, the rollback. - playbook: pinned sha256 vars, the new dnsmasq default line, and a verified, non-optional task that installs ipxe-shim.efi, ipxe.efi (snponly), ipxe.pxe and ipxe-bcm.efi (was: copy ipxe.efi and the never served undionly.kpxe, errors ignored). - scripts/fetch-ipxe.sh downloads the pinned tarball and shim into boot-files/ for the USB build; README/SETUP list the step. - iPXE page: ipxe.efi is staged from snponly.efi, a build that drives USB itself is refused, and the snponly-shim/snponly root files are gone. Tested: the playbook task against the real release files (installs the live hashes; a tampered file stops it before anything is replaced), ansible --syntax-check, staging, and the full-build refusal.
This commit is contained in:
@@ -1,21 +1,19 @@
|
||||
"""Keep the Secure Boot iPXE chain on the TFTP root current.
|
||||
|
||||
What dnsmasq serves (see /etc/dnsmasq.conf):
|
||||
* ipxe-shim.efi - Microsoft-signed shim, for the MACs tagged ipxe2. Over
|
||||
TFTP it always loads ipxe.efi from the TFTP root (it
|
||||
does not derive the name from its own, whatever the
|
||||
upstream docs say);
|
||||
* snponly-shim.efi - the same shim under the name dnsmasq hands the
|
||||
ipxesnp-tagged MACs;
|
||||
* ipxe.efi - iPXE signed by the iPXE Secure Boot CA, loaded by the
|
||||
shim. The full build drives USB itself, which kills a
|
||||
USB keyboard at the iPXE menu;
|
||||
* snponly.efi - the same release built to use the firmware's network
|
||||
and USB drivers (keyboard keeps working). Served to a
|
||||
client as ipxe.efi from its per-MAC folder;
|
||||
* ipxe-bcm.efi - the old 2021 Broadcom-signed build, everyone else.
|
||||
Never touched here;
|
||||
* ipxe.pxe - legacy BIOS iPXE (unsigned; BIOS has no Secure Boot).
|
||||
What dnsmasq serves (see /etc/dnsmasq.conf), since 2026-10-14:
|
||||
* ipxe-shim.efi - iPXE's Microsoft-signed shim, every UEFI client. Over
|
||||
TFTP it always loads ipxe.efi from the TFTP root (it does
|
||||
not derive the name from its own, whatever the upstream
|
||||
docs say);
|
||||
* ipxe.efi - iPXE signed by the iPXE Secure Boot CA, loaded by the
|
||||
shim. It is the release's SNPONLY build (x86_64-sb/
|
||||
snponly.efi): firmware network and USB drivers. The
|
||||
release's full ipxe.efi drives USB itself, which kills a
|
||||
USB keyboard at the iPXE menu, so it is never installed;
|
||||
* ipxe-bcm.efi - the old 2021 Broadcom-signed 1.21.1 build, kept as the
|
||||
rollback (point dnsmasq's UEFI line back at it). Never
|
||||
touched here;
|
||||
* ipxe.pxe - legacy BIOS iPXE (unsigned; BIOS has no Secure Boot).
|
||||
|
||||
Per-MAC folders: with tftp-unique-root=mac, /srv/tftp/<aa-bb-cc-dd-ee-ff>/
|
||||
wins over the root for that client, with NO fallback for a file missing
|
||||
@@ -71,14 +69,13 @@ JOB_UNIT = "pxe-webapp-ipxe-stage"
|
||||
# Secure Boot signature (and from whom).
|
||||
MANAGED = {
|
||||
"ipxe-shim.efi": ("ipxeboot/x86_64-sb/shimx64.efi", "microsoft"),
|
||||
"snponly-shim.efi": ("ipxeboot/x86_64-sb/shimx64.efi", "microsoft"),
|
||||
"ipxe.efi": ("ipxeboot/x86_64-sb/ipxe.efi", "ipxe"),
|
||||
"snponly.efi": ("ipxeboot/x86_64-sb/snponly.efi", "ipxe"),
|
||||
# The snponly build, installed under the name the shim loads.
|
||||
"ipxe.efi": ("ipxeboot/x86_64-sb/snponly.efi", "ipxe"),
|
||||
"ipxe.pxe": ("ipxeboot/x86_64/ipxe.pxe", None),
|
||||
}
|
||||
SHIMS = ("ipxe-shim.efi", "snponly-shim.efi")
|
||||
SHIMS = ("ipxe-shim.efi",)
|
||||
# Installed by default; ipxe.pxe (legacy BIOS) only when asked.
|
||||
DEFAULT_INSTALL = ("ipxe-shim.efi", "snponly-shim.efi", "ipxe.efi", "snponly.efi")
|
||||
DEFAULT_INSTALL = ("ipxe-shim.efi", "ipxe.efi")
|
||||
# Shown on the page for reference, never written.
|
||||
READ_ONLY = ("ipxe-bcm.efi",)
|
||||
|
||||
@@ -266,8 +263,7 @@ def installed():
|
||||
rows.append({"name": name, "managed": name in MANAGED, "info": info})
|
||||
# The shim only starts an iPXE signed under the CA it embeds.
|
||||
by = {r["name"]: r["info"] for r in rows}
|
||||
_check_chain(by.get("ipxe-shim.efi") or by.get("snponly-shim.efi"),
|
||||
[by.get("ipxe.efi"), by.get("snponly.efi")])
|
||||
_check_chain(by.get("ipxe-shim.efi"), [by.get("ipxe.efi")])
|
||||
return rows
|
||||
|
||||
|
||||
@@ -530,11 +526,13 @@ def stage(info=None):
|
||||
_use_dual_signed_shim(folder, files)
|
||||
# The shim must trust the CA this release's iPXE builds are signed by.
|
||||
shim = files.get("ipxe-shim.efi") or {}
|
||||
for name in ("ipxe.efi", "snponly.efi"):
|
||||
ipxe = files.get(name) or {}
|
||||
if shim.get("ok") and ipxe.get("ok") and shim.get("trusts"):
|
||||
if shim["trusts"] not in {i for _, i in ipxe["signers"]}:
|
||||
ipxe.update(ok=False, error=f"not signed under {shim['trusts']}, which this shim trusts")
|
||||
ipxe = files.get("ipxe.efi") or {}
|
||||
if shim.get("ok") and ipxe.get("ok") and shim.get("trusts"):
|
||||
if shim["trusts"] not in {i for _, i in ipxe["signers"]}:
|
||||
ipxe.update(ok=False, error=f"not signed under {shim['trusts']}, which this shim trusts")
|
||||
if ipxe.get("ok") and ipxe.get("drivers") == "native":
|
||||
# The full build would kill USB keyboards on every client.
|
||||
ipxe.update(ok=False, error="this build drives USB itself (not snponly); refusing it as ipxe.efi")
|
||||
record = {"tag": info["tag"], "published": info.get("published"), "folder": folder,
|
||||
"tarball_sha256": info["sha256"], "files": files, "staged": uc.now()}
|
||||
uc.write_json(os.path.join(folder, "staged.json"), record)
|
||||
@@ -775,12 +773,10 @@ def _install(tag, rec, names):
|
||||
if not any(n in names for n in SHIMS):
|
||||
# iPXE alone must still be one the installed shim will start.
|
||||
trusts = vendor_cert_cn(os.path.join(config.TFTP_ROOT, "ipxe-shim.efi"))
|
||||
for n in ("ipxe.efi", "snponly.efi"):
|
||||
if n not in names:
|
||||
continue
|
||||
issuers = {i for _, i in rec["files"][n].get("signers", [])}
|
||||
if "ipxe.efi" in names:
|
||||
issuers = {i for _, i in rec["files"]["ipxe.efi"].get("signers", [])}
|
||||
if trusts and trusts not in issuers:
|
||||
raise ValueError(f"the installed shim trusts {trusts}; this {n} is not signed "
|
||||
raise ValueError(f"the installed shim trusts {trusts}; this ipxe.efi is not signed "
|
||||
"under it, install the shim from the same release too")
|
||||
backup = _backup(names, f"before installing {tag}")
|
||||
_place_all([(os.path.join(rec["folder"], n), n, rec["files"][n]["sha256"]) for n in names])
|
||||
|
||||
Reference in New Issue
Block a user