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])
|
||||
|
||||
@@ -164,14 +164,13 @@
|
||||
<details>
|
||||
<summary class="disc"><span class="chev" aria-hidden="true">▾</span>What this page manages</summary>
|
||||
<p class="sub" style="margin: 4px 0 0; max-width: 900px; line-height: 1.6">
|
||||
The Secure Boot chain that dnsmasq hands out over TFTP: the Microsoft-signed shim (<span class="mono">ipxe-shim.efi</span>,
|
||||
and the same file as <span class="mono">snponly-shim.efi</span>) starts <span class="mono">ipxe.efi</span>, which is signed by the
|
||||
iPXE Secure Boot CA. <span class="mono">snponly.efi</span> is the same release built on the firmware's drivers (USB keyboards
|
||||
keep working). Updates come from the upstream iPXE release tarball on GitHub, plus the dual-signed shim (Microsoft UEFI CA
|
||||
2011 and 2023) from the separate ipxe/shim release; each download's sha256 is checked against GitHub's published digest and
|
||||
each file's signer chain is read with <span class="mono">sbverify</span> before it can be installed.
|
||||
<span class="mono">ipxe-bcm.efi</span>, the per-client folders, <span class="mono">dnsmasq.conf</span> and the Blancco files are
|
||||
never changed here.
|
||||
The Secure Boot chain dnsmasq hands every UEFI client over TFTP: the Microsoft-signed shim (<span class="mono">ipxe-shim.efi</span>,
|
||||
dual-signed under Microsoft UEFI CA 2011 and 2023) starts <span class="mono">ipxe.efi</span>, which is signed by the iPXE Secure
|
||||
Boot CA. <span class="mono">ipxe.efi</span> is the release's snponly build (firmware network and USB drivers, so USB keyboards keep
|
||||
working); the release's full build is refused. Updates come from the upstream iPXE release tarball on GitHub, plus the dual-signed
|
||||
shim from the separate ipxe/shim release; each download's sha256 is checked against GitHub's published digest and each file's
|
||||
signer chain is read with <span class="mono">sbverify</span> before it can be installed. <span class="mono">ipxe-bcm.efi</span>
|
||||
(the old build, kept as the rollback), <span class="mono">dnsmasq.conf</span> and the Blancco files are never changed here.
|
||||
</p>
|
||||
</details>
|
||||
|
||||
@@ -446,23 +445,20 @@ sbverify --list ipxeboot/x86_64-sb/snponly.efi</pre>
|
||||
is normal. Do not rely on <span class="mono">sbverify --cert</span>: it reports OK even for the wrong certificate.</li>
|
||||
<li><strong>Back up the current files.</strong> A backup made in this folder also shows in the Backups list above, with Restore.
|
||||
<pre class="cmd mono">B=/var/lib/pxe-webapp/updates/ipxe/backups/$(date +%Y%m%d-%H%M%S)
|
||||
mkdir -p "$B" && cp -p /srv/tftp/ipxe-shim.efi /srv/tftp/snponly-shim.efi /srv/tftp/ipxe.efi /srv/tftp/snponly.efi "$B"/</pre></li>
|
||||
mkdir -p "$B" && cp -p /srv/tftp/ipxe-shim.efi /srv/tftp/ipxe.efi "$B"/</pre></li>
|
||||
<li><strong>Install both files, from the same release.</strong> The shim only starts an <span class="mono">ipxe.efi</span> signed under the
|
||||
CA it carries, and it always loads <span class="mono">ipxe.efi</span> from the TFTP root, so the name must stay
|
||||
<span class="mono">ipxe.efi</span>. Copy to a temporary name and rename, so a client that is booting never reads half a file:
|
||||
<pre class="cmd mono">install -m 0644 ipxeboot/x86_64-sb/ipxe.efi /srv/tftp/.ipxe.efi.new && mv -f /srv/tftp/.ipxe.efi.new /srv/tftp/ipxe.efi
|
||||
install -m 0644 ipxeboot/x86_64-sb/snponly.efi /srv/tftp/.snponly.efi.new && mv -f /srv/tftp/.snponly.efi.new /srv/tftp/snponly.efi
|
||||
install -m 0644 ipxe-shimx64.efi /srv/tftp/.ipxe-shim.efi.new && mv -f /srv/tftp/.ipxe-shim.efi.new /srv/tftp/ipxe-shim.efi
|
||||
install -m 0644 ipxe-shimx64.efi /srv/tftp/.snponly-shim.efi.new && mv -f /srv/tftp/.snponly-shim.efi.new /srv/tftp/snponly-shim.efi</pre>
|
||||
<span class="sub">No dnsmasq restart is needed. Never replace <span class="mono">ipxe-bcm.efi</span>; it serves every other UEFI client.
|
||||
A per-client folder keeps its own copies: update its <span class="mono">ipxe.efi</span> (the snponly build) and shim the same way.</span></li>
|
||||
<li><strong>Who gets it.</strong> dnsmasq decides, in <span class="mono">/etc/dnsmasq.conf</span>: clients listed as
|
||||
<span class="mono">dhcp-host=<mac>,set:ipxe2</span> get the shim through
|
||||
<span class="mono">dhcp-boot=tag:ipxe2,tag:!ipxe,tag:!bios,ipxe-shim.efi</span>; every other UEFI client gets
|
||||
<span class="mono">ipxe-bcm.efi</span> through the untagged <span class="mono">dhcp-boot=tag:!bios,...,ipxe-bcm.efi</span> line. Clients tagged
|
||||
<span class="mono">ipxesnp</span> get <span class="mono">snponly-shim.efi</span>; since the shim asks for <span class="mono">ipxe.efi</span>
|
||||
whatever it is called, each of them also needs a per-client folder whose <span class="mono">ipxe.efi</span> is the snponly build.
|
||||
Who gets which file above shows the live lines. Test on one tagged machine before tagging more.</li>
|
||||
<pre class="cmd mono">install -m 0644 ipxeboot/x86_64-sb/snponly.efi /srv/tftp/.ipxe.efi.new && mv -f /srv/tftp/.ipxe.efi.new /srv/tftp/ipxe.efi
|
||||
install -m 0644 ipxe-shimx64.efi /srv/tftp/.ipxe-shim.efi.new && mv -f /srv/tftp/.ipxe-shim.efi.new /srv/tftp/ipxe-shim.efi</pre>
|
||||
<span class="sub">Note the first line: the snponly build goes in as <span class="mono">ipxe.efi</span>. Never install the release's
|
||||
full <span class="mono">x86_64-sb/ipxe.efi</span> (it kills USB keyboards), and never replace <span class="mono">ipxe-bcm.efi</span>
|
||||
(the rollback). No dnsmasq restart is needed.</span></li>
|
||||
<li><strong>Who gets it.</strong> dnsmasq decides, in <span class="mono">/etc/dnsmasq.conf</span>: every UEFI client gets the shim
|
||||
through <span class="mono">dhcp-boot=tag:!bios,tag:!ipxe,ipxe-shim.efi</span>. To roll back, point that line at
|
||||
<span class="mono">ipxe-bcm.efi</span> and restart dnsmasq; to keep one model on the old build, tag its MAC
|
||||
(<span class="mono">dhcp-host=<mac>,set:oldipxe</span>) and add <span class="mono">dhcp-boot=tag:oldipxe,tag:!ipxe,tag:!bios,ipxe-bcm.efi</span>
|
||||
above the default line. Who gets which file above shows the live lines.</li>
|
||||
<li><strong>Roll back.</strong> Use Restore in the Backups list, or copy the backed-up files back the same way (temporary name, then
|
||||
<span class="mono">mv -f</span>).</li>
|
||||
<li><strong>Normal log noise.</strong> While booting, the shim asks TFTP for optional files such as
|
||||
|
||||
Reference in New Issue
Block a user