Add UEFI Secure Boot support using iPXE v2.0.0 signed binaries

Downloads pre-built Microsoft-signed iPXE Secure Boot binaries from
the iPXE v2.0.0 release (ipxeboot.tar.gz) and packages them with a
templated autoexec.ipxe that chains into the netboot.xyz menu system.

Boot flow: UEFI firmware validates iPXE shim (Microsoft-signed) which
loads iPXE (signed by iPXE Secure Boot CA) which auto-loads
autoexec.ipxe (text script, no SB validation needed) which chains to
https://boot.netboot.xyz/menu.ipxe.

Changes:
- New generate_disks_secureboot.yml task to download and package
  signed iPXE binaries for x86_64 and ARM64
- New autoexec.ipxe.j2 template with DHCP, failsafe menu, and
  HTTPS/HTTP fallback chain to boot.netboot.xyz
- Bootloader entries and index.html sections for Secure Boot binaries
  served from ipxe/secureboot-x86_64/ and ipxe/secureboot-arm64/
- Checksum generation updated to handle subdirectories
- Debian menu template updated with shim command for Secure Boot
  kernel validation (no-op on non-SB systems)
- Production overrides enable Secure Boot generation

Closes #1745
This commit is contained in:
Antony Messerli
2026-03-14 00:20:17 -05:00
parent 8216163deb
commit d059ddb7a8
9 changed files with 303 additions and 2 deletions
+35
View File
@@ -96,6 +96,38 @@ bootloaders:
ipxe_bin: snp.efi.dsk ipxe_bin: snp.efi.dsk
output_bin: -snp.efi.dsk output_bin: -snp.efi.dsk
type: Floppy-snp type: Floppy-snp
secureboot_x86_64:
- desc: Secure Boot x86_64 shim, loads iPXE with built-in NIC drivers
output_bin: ipxe-shim.efi
type: Secure Boot
- desc: Secure Boot x86_64 iPXE, built-in NIC drivers (loaded via shim)
output_bin: ipxe.efi
type: Secure Boot
- desc: Secure Boot x86_64 SNP only, boots from chained device (loaded via shim)
output_bin: snponly.efi
type: Secure Boot
- desc: Secure Boot x86_64 SNP shim, loads SNP-only iPXE
output_bin: snponly-shim.efi
type: Secure Boot
- desc: Secure Boot autoexec.ipxe boot script
output_bin: autoexec.ipxe
type: Script
secureboot_arm64:
- desc: Secure Boot ARM64 shim, loads iPXE with built-in NIC drivers
output_bin: ipxe-shim.efi
type: Secure Boot
- desc: Secure Boot ARM64 iPXE, built-in NIC drivers (loaded via shim)
output_bin: ipxe.efi
type: Secure Boot
- desc: Secure Boot ARM64 SNP only, boots from chained device (loaded via shim)
output_bin: snponly.efi
type: Secure Boot
- desc: Secure Boot ARM64 SNP shim, loads SNP-only iPXE
output_bin: snponly-shim.efi
type: Secure Boot
- desc: Secure Boot autoexec.ipxe boot script
output_bin: autoexec.ipxe
type: Script
cert_dir: /etc/netbootxyz/certs cert_dir: /etc/netbootxyz/certs
cert_file_filename: ca-netboot-xyz.crt cert_file_filename: ca-netboot-xyz.crt
checksums_filename: '{{ site_name }}-sha256-checksums.txt' checksums_filename: '{{ site_name }}-sha256-checksums.txt'
@@ -115,6 +147,7 @@ generate_disks_hybrid: false
generate_disks_legacy: true generate_disks_legacy: true
generate_disks_linux: false generate_disks_linux: false
generate_disks_rpi: false generate_disks_rpi: false
generate_disks_secureboot: false
generate_local_vars: true generate_local_vars: true
generate_menus: true generate_menus: true
generate_signatures: false generate_signatures: false
@@ -122,6 +155,8 @@ generate_version_file: true
ipxe_branch: master ipxe_branch: master
ipxe_ca_filename: ca-ipxe-org.crt ipxe_ca_filename: ca-ipxe-org.crt
ipxe_ca_url: http://ca.ipxe.org/ca.crt ipxe_ca_url: http://ca.ipxe.org/ca.crt
ipxe_secureboot_version: v2.0.0
ipxe_secureboot_archive_url: "https://github.com/ipxe/ipxe/releases/download/{{ ipxe_secureboot_version }}/ipxeboot.tar.gz"
ipxe_debug_enabled: false ipxe_debug_enabled: false
ipxe_debug_options: httpcore,tls ipxe_debug_options: httpcore,tls
ipxe_repo: https://github.com/ipxe/ipxe ipxe_repo: https://github.com/ipxe/ipxe
+36 -1
View File
@@ -1,6 +1,6 @@
--- ---
- name: Register a listing of all created iPXE bootloaders - name: Register a listing of all created iPXE bootloaders
ansible.builtin.command: ls -I {{ checksums_filename }} {{ netbootxyz_root }}/ipxe/ ansible.builtin.shell: ls -p -I {{ checksums_filename }} {{ netbootxyz_root }}/ipxe/ | grep -v /
register: netboot_disks register: netboot_disks
- name: Generate date - name: Generate date
@@ -15,6 +15,41 @@
chdir: "{{ netbootxyz_root }}/ipxe/" chdir: "{{ netbootxyz_root }}/ipxe/"
register: netboot_disks_stat register: netboot_disks_stat
- name: Register Secure Boot x86_64 bootloaders
ansible.builtin.shell: ls {{ netbootxyz_root }}/ipxe/secureboot-x86_64/ 2>/dev/null || true
register: secureboot_x86_64_disks
when: generate_disks_secureboot | default(false) | bool
- name: Gather Secure Boot x86_64 checksums
ansible.builtin.command: sha256sum -b {{ item }}
with_items:
- "{{ secureboot_x86_64_disks.stdout_lines | default([]) }}"
args:
chdir: "{{ netbootxyz_root }}/ipxe/secureboot-x86_64/"
register: secureboot_x86_64_disks_stat
when:
- generate_disks_secureboot | default(false) | bool
- secureboot_x86_64_disks.stdout_lines | default([]) | length > 0
- name: Register Secure Boot ARM64 bootloaders
ansible.builtin.shell: ls {{ netbootxyz_root }}/ipxe/secureboot-arm64/ 2>/dev/null || true
register: secureboot_arm64_disks
when:
- generate_disks_secureboot | default(false) | bool
- generate_disks_arm | default(false) | bool
- name: Gather Secure Boot ARM64 checksums
ansible.builtin.command: sha256sum -b {{ item }}
with_items:
- "{{ secureboot_arm64_disks.stdout_lines | default([]) }}"
args:
chdir: "{{ netbootxyz_root }}/ipxe/secureboot-arm64/"
register: secureboot_arm64_disks_stat
when:
- generate_disks_secureboot | default(false) | bool
- generate_disks_arm | default(false) | bool
- secureboot_arm64_disks.stdout_lines | default([]) | length > 0
- name: Generate ipxe disk checksums - name: Generate ipxe disk checksums
ansible.builtin.template: ansible.builtin.template:
src: checksums.txt.j2 src: checksums.txt.j2
@@ -34,3 +34,9 @@
when: when:
- generate_disks_hybrid | default(false) | bool - generate_disks_hybrid | default(false) | bool
- bootloader_filename == "netboot.xyz" - bootloader_filename == "netboot.xyz"
- name: Generate Secure Boot iPXE bootloaders
ansible.builtin.include_tasks: generate_disks_secureboot.yml
when:
- generate_disks_secureboot | default(false) | bool
- bootloader_filename == "netboot.xyz"
@@ -0,0 +1,64 @@
---
- name: Create Secure Boot output directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
with_items:
- "{{ netbootxyz_root }}/ipxe/secureboot-x86_64"
- "{{ netbootxyz_root }}/ipxe/secureboot-arm64"
- name: Download iPXE Secure Boot archive
ansible.builtin.get_url:
url: "{{ ipxe_secureboot_archive_url }}"
dest: "/tmp/ipxeboot.tar.gz"
- name: Extract iPXE Secure Boot archive
ansible.builtin.unarchive:
src: "/tmp/ipxeboot.tar.gz"
dest: "/tmp"
remote_src: true
- name: Copy x86_64 Secure Boot binaries to output directory
ansible.builtin.copy:
src: "/tmp/ipxeboot/x86_64-sb/{{ item }}"
dest: "{{ netbootxyz_root }}/ipxe/secureboot-x86_64/{{ item }}"
remote_src: true
with_items:
- ipxe.efi
- ipxe-shim.efi
- shimx64.efi
- snponly.efi
- snponly-shim.efi
- name: Copy ARM64 Secure Boot binaries to output directory
ansible.builtin.copy:
src: "/tmp/ipxeboot/arm64-sb/{{ item }}"
dest: "{{ netbootxyz_root }}/ipxe/secureboot-arm64/{{ item }}"
remote_src: true
with_items:
- ipxe.efi
- ipxe-shim.efi
- shimaa64.efi
- snponly.efi
- snponly-shim.efi
when: generate_disks_arm | default(false) | bool
- name: Template autoexec.ipxe for x86_64 Secure Boot
ansible.builtin.template:
src: "disks/autoexec.ipxe.j2"
dest: "{{ netbootxyz_root }}/ipxe/secureboot-x86_64/autoexec.ipxe"
- name: Template autoexec.ipxe for ARM64 Secure Boot
ansible.builtin.template:
src: "disks/autoexec.ipxe.j2"
dest: "{{ netbootxyz_root }}/ipxe/secureboot-arm64/autoexec.ipxe"
when: generate_disks_arm | default(false) | bool
- name: Clean up Secure Boot archive
ansible.builtin.file:
path: "{{ item }}"
state: absent
with_items:
- "/tmp/ipxeboot.tar.gz"
- "/tmp/ipxeboot"
@@ -4,3 +4,21 @@
{% for item in netboot_disks_stat.results %} {% for item in netboot_disks_stat.results %}
{{ item.stdout }} {{ item.stdout }}
{% endfor %} {% endfor %}
{% if secureboot_x86_64_disks_stat is defined and secureboot_x86_64_disks_stat.results is defined %}
# Secure Boot x86_64 (iPXE {{ ipxe_secureboot_version }})
{% for item in secureboot_x86_64_disks_stat.results %}
{% if item.stdout is defined %}
{{ item.stdout | replace('*', '*secureboot-x86_64/') }}
{% endif %}
{% endfor %}
{% endif %}
{% if secureboot_arm64_disks_stat is defined and secureboot_arm64_disks_stat.results is defined %}
# Secure Boot ARM64 (iPXE {{ ipxe_secureboot_version }})
{% for item in secureboot_arm64_disks_stat.results %}
{% if item.stdout is defined %}
{{ item.stdout | replace('*', '*secureboot-arm64/') }}
{% endif %}
{% endfor %}
{% endif %}
@@ -0,0 +1,98 @@
#!ipxe
#
# {{ site_name }} - Secure Boot autoexec.ipxe
#
# This script is loaded automatically by iPXE v2.0.0+ Secure Boot
# binaries from the same directory. It replaces the embedded script
# used in self-compiled netboot.xyz bootloaders.
#
set esc:hex 1b
set bold ${esc:string}[1m
set boldoff ${esc:string}[22m
set fg_gre ${esc:string}[32m
set fg_cya ${esc:string}[36m
set fg_whi ${esc:string}[37m
set HTTPS_ERR HTTPS appears to have failed... attempting HTTP
set HTTP_ERR HTTP has failed, localbooting...
set site_name {{ site_name }}
set boot_domain {{ boot_domain }}
set ipxe_version ${version}
set version {{ boot_version }}
set conn_type https
:start
echo ${bold}${fg_gre}${site_name} - ${fg_whi}v${version} (Secure Boot)${boldoff}
iseq ${site_name} netboot.xyz || echo ${bold}${fg_whi}Powered by ${fg_gre}netboot.xyz${fg_whi}${boldoff}
prompt --key m --timeout 4000 Hit the ${bold}m${boldoff} key to open failsafe menu... && goto failsafe || goto dhcp
:dhcp
echo
dhcp || goto netconfig
goto menu
:failsafe
menu ${boot_domain} Failsafe Menu
item localboot Boot to local drive
item netconfig Manual network configuration
item retry Retry boot
item debug iPXE Debug Shell
item reboot Reboot System
choose failsafe_choice || exit
goto ${failsafe_choice}
:netconfig
echo Network Configuration:
echo Available interfaces...
ifstat
imgfree
echo -n Set network interface number [0 for net0, defaults to 0]: ${} && read net
isset ${net} || set net 0
echo -n IP: && read net${net}/ip
echo -n Subnet mask: && read net${net}/netmask
echo -n Gateway: && read net${net}/gateway
echo -n DNS: && read dns
ifopen net${net}
echo Attempting chainload of ${boot_domain}...
goto menu || goto failsafe
:menu
{% if bootloader_https_enabled | bool %}
:menu_https
set conn_type https
goto menu_start
{% endif %}
{% if bootloader_http_enabled | bool %}
:menu_http
set conn_type http
goto menu_start
{% endif %}
:menu_start
isset ${netX/dns6} && goto menu_v6 || goto menu_v4
:menu_v6
isset ${netX/dns6_bak} && set netX/dns6 ${netX/dns6_bak} ||
set netX/dns6_bak ${netX/dns6}
echo Attempting ${conn_type} boot over IPv6...
chain --autofree ${conn_type}://${boot_domain}/menu.ipxe || echo ${conn_type} IPv6 failed... attempting IPv4...
clear netX/dns6
:menu_v4
echo Attempting ${conn_type} boot over IPv4...
chain --autofree ${conn_type}://${boot_domain}/menu.ipxe || echo ${conn_type} IPv4 failed...
iseq ${conn_type} https && goto menu_http || goto localboot
:localboot
exit
:retry
goto start
:reboot
reboot
goto start
:debug
echo Type "exit" to return to menu
shell
goto failsafe
+42
View File
@@ -135,6 +135,48 @@ exit
</table> </table>
{% endif %} {% endif %}
{% if generate_disks_secureboot == true %}
<p>x86_64 UEFI Secure Boot iPXE Bootloaders (place all files from directory together)</p>
<table style="width:100%">
<!-- table header -->
<tr>
<th style="width:10%;"> Type </th>
<th style="width:20%;"> Bootloader </th>
<th style="width:70%;"> Description </th>
</tr>
<!-- table rows -->
{% for item in bootloaders.secureboot_x86_64 %}
<tr>
<td> {{ item.type }} </td>
<td> <a href="ipxe/secureboot-x86_64/{{ item.output_bin }}">{{ item.output_bin }}</a> </td>
<td> {{ item.desc }} </td>
</tr>
{% endfor %}
</table>
{% if generate_disks_arm == true %}
<p>ARM64 UEFI Secure Boot iPXE Bootloaders (place all files from directory together)</p>
<table style="width:100%">
<!-- table header -->
<tr>
<th style="width:10%;"> Type </th>
<th style="width:20%;"> Bootloader </th>
<th style="width:70%;"> Description </th>
</tr>
<!-- table rows -->
{% for item in bootloaders.secureboot_arm64 %}
<tr>
<td> {{ item.type }} </td>
<td> <a href="ipxe/secureboot-arm64/{{ item.output_bin }}">{{ item.output_bin }}</a> </td>
<td> {{ item.desc }} </td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endif %}
{% if generate_disks_rpi == true %} {% if generate_disks_rpi == true %}
<p>Raspberry Pi iPXE Bootloaders</p> <p>Raspberry Pi iPXE Bootloaders</p>
@@ -85,6 +85,8 @@ initrd ${debian_mirror}/${dir}/initrd.gz
echo echo
echo MD5sums: echo MD5sums:
md5sum linux initrd.gz md5sum linux initrd.gz
iseq ${os_arch} amd64 && shim ${debian_mirror}/${dir}/bootnetx64.efi ||
iseq ${os_arch} arm64 && shim ${debian_mirror}/${dir}/bootnetaa64.efi ||
boot boot
:debian_exit :debian_exit
+1
View File
@@ -5,6 +5,7 @@ generate_disks_arm: true
generate_disks_hybrid: true generate_disks_hybrid: true
generate_disks_linux: true generate_disks_linux: true
generate_disks_rpi: false generate_disks_rpi: false
generate_disks_secureboot: true
generate_version_file: true generate_version_file: true
generate_local_vars: false generate_local_vars: false
make_num_jobs: 4 make_num_jobs: 4