From e003197e25ee1b38151534009bfb6a7f8b597668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20L=C3=B6ser?= Date: Tue, 28 Feb 2023 11:52:37 +0100 Subject: [PATCH] Fixes #36834 - New PXE loader "Grub2 UEFI SecureBoot (target OS)" This feature consists of two patches, one for foreman and one for smart-proxy. This patch introduces a new loader of kind `:PXEGrub2TargetOS` which allows to provide host-specific Network Bootstrap Programs (NPB) in order to enable network based installations for SecureBoot-enabled hosts. SecureBoot expects to follow a chain of trust from the start of the host to the loading of Linux kernel modules. The very first shim that is loaded basically determines which distribution is allowed to be booted or kexec'ed until next reboot. The existing "Grub2 UEFI SecureBoot" is not sufficient as it limits the possible installations to the vendor of the Foreman (Smart Proxy) host system. Providing shim and GRUB2 by the vendor of the to-be-installed operating system allows Foreman to install any operating system on SecureBoot-enabled hosts over network. To achieve this, the host's DHCP filename option is set to a shim path in a directory that is host-specific (contains MAC address). Corresponding shim and GRUB2 binaries are copied into that directory along with the generated GRUB2 configuration files as we know from "Grub2 UEFI". The required binaries must be provided once in the so called "bootloader universe". This directory can be configured via the settings file `/etc/foreman-proxy/settings.d/tftp.yml` and defaults to `/usr/local/share/bootloader-universe//`. These binaries can be manually retrieved from the installation media and is not part of this patch set. Full example: ------------- [root@vm ~]# hammer host info --id 241 | grep -E "(MAC address|Operating System)" MAC address: 00:50:56:b4:75:5e Operating System: Ubuntu 22.04 LTS [root@vm ~]# tree /usr/local/share/bootloader-universe/ /usr/local/share/bootloader-universe/ |-- centos | |-- grubx64.efi | `-- shimx64.efi `-- ubuntu |-- grubx64.efi `-- shimx64.efi [root@vm ~]# hammer host update --id 241 --build true [root@vm ~]# tree /var/lib/tftpboot/grub2/00-50-56-b4-75-5e/ /var/lib/tftpboot/grub2/00-50-56-b4-75-5e/ |-- grub.cfg |-- grub.cfg-00:50:56:b4:75:5e |-- grub.cfg-01-00-50-56-b4-75-5e |-- grubx64.efi |-- shimx64.efi `-- targetos [root@vm ~]# grep -B2 00-50-56-b4-75-5e /var/lib/dhcpd/dhcpd.leases hardware ethernet 00:50:56:b4:75:5e; fixed-address 192.168.145.84; supersede server.filename = "grub2/00-50-56-b4-75-5e/shimx64.efi"; [root@vm ~]# pesign -S -i /var/lib/tftpboot/grub2/00-50-56-b4-75-5e/grubx64.efi | grep Canonical The signer's common name is Canonical Ltd. Secure Boot Signing (2021 v1) --- app/models/concerns/orchestration/dhcp.rb | 4 ++++ app/models/concerns/orchestration/tftp.rb | 19 +++++++++++++++++-- app/models/concerns/pxe_loader_support.rb | 2 ++ app/services/proxy_api/tftp.rb | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/orchestration/dhcp.rb b/app/models/concerns/orchestration/dhcp.rb index a7899c1089cc..5938e0394519 100644 --- a/app/models/concerns/orchestration/dhcp.rb +++ b/app/models/concerns/orchestration/dhcp.rb @@ -125,6 +125,10 @@ def dhcp_attrs(record_mac) if provision? dhcp_attr[:nextServer] = boot_server unless host.pxe_loader == 'None' filename = operatingsystem.boot_filename(host) + if filename.include? "@@subdir@@" + mac = host.mac.downcase + filename = filename.gsub("@@subdir@@", mac.tr(':', '-').downcase) + end dhcp_attr[:filename] = filename if filename.present? if jumpstart? jumpstart_arguments = os.jumpstart_params host, model.vendor_class diff --git a/app/models/concerns/orchestration/tftp.rb b/app/models/concerns/orchestration/tftp.rb index 2665cab80f53..4c1183b86433 100644 --- a/app/models/concerns/orchestration/tftp.rb +++ b/app/models/concerns/orchestration/tftp.rb @@ -82,12 +82,20 @@ def default_pxe_render(kind) # Adds the host to the forward and reverse TFTP zones # +returns+ : Boolean true on success def setTFTP(kind) - content = generate_pxe_template(kind) + if kind.to_s == "PXEGrub2TargetOS" + content = generate_pxe_template("PXEGrub2".to_sym) + else + content = generate_pxe_template(kind) + end if content logger.info "Deploying TFTP #{kind} configuration for #{host.name}" each_unique_feasible_tftp_proxy do |proxy| mac_addresses_for_provisioning.each do |mac_addr| - proxy.set(kind, mac_addr, :pxeconfig => content) + targetos = nil + if kind.to_s == "PXEGrub2TargetOS" + targetos = host.operatingsystem.name.downcase + end + proxy.set(kind, mac_addr, {:pxeconfig => content, :targetos => targetos}) end end else @@ -132,6 +140,9 @@ def validate_tftp return unless tftp? || tftp6? return unless host.operatingsystem pxe_kind = host.operatingsystem.pxe_loader_kind(host) + if pxe_kind.to_s == "PXEGrub2TargetOS" + pxe_kind = "PXEGrub2".to_sym + end if pxe_kind && host.provisioning_template({:kind => pxe_kind}).nil? failure _("No %{template_kind} templates were found for this host, make sure you define at least one in your %{os} settings or change PXE loader") % { :template_kind => pxe_kind, :os => host.operatingsystem } @@ -146,7 +157,11 @@ def queue_tftp end def queue_tftp_create + pxe_kind = host.operatingsystem.pxe_loader_kind(host) host.operatingsystem.template_kinds.each do |kind| + if kind.to_s == "PXEGrub2" && pxe_kind.to_s == "PXEGrub2TargetOS" + queue.create(:name => _("Deploy TFTP SecureBoot %{kind} config for %{host}") % {:kind => kind, :host => self}, :priority => 20, :action => [self, :setTFTP, pxe_kind]) + end queue.create(:name => _("Deploy TFTP %{kind} config for %{host}") % {:kind => kind, :host => self}, :priority => 20, :action => [self, :setTFTP, kind]) end return unless build diff --git a/app/models/concerns/pxe_loader_support.rb b/app/models/concerns/pxe_loader_support.rb index c98632d7691b..3add5afd1ec8 100644 --- a/app/models/concerns/pxe_loader_support.rb +++ b/app/models/concerns/pxe_loader_support.rb @@ -5,6 +5,7 @@ module PxeLoaderSupport PXE_KINDS = { :PXELinux => /^(pxelinux.*|PXELinux (BIOS|UEFI))$/, :PXEGrub => /^(grub\/|Grub UEFI).*/, + :PXEGrub2TargetOS => /^(Grub2 UEFI SecureBoot \(target OS\))$/, :PXEGrub2 => /^(grub2\/|Grub2 (BIOS|UEFI|ELF)|http.*grub2\/).*/, :iPXE => /^((iPXE|http.*\/ipxe-).*|ipxe\.efi|undionly\.kpxe)$/, }.with_indifferent_access.freeze @@ -28,6 +29,7 @@ def all_loaders_map(precision = 'x64', httpboot_host = "httpboot_host") "Grub2 ELF" => "grub2/grub#{precision}.elf", "Grub2 UEFI" => "grub2/grub#{precision}.efi", "Grub2 UEFI SecureBoot" => "grub2/shim#{precision}.efi", + "Grub2 UEFI SecureBoot (target OS)" => "grub2/@@subdir@@/shim#{precision}.efi", "Grub2 UEFI HTTP" => "http://#{httpboot_host}/httpboot/grub2/grub#{precision}.efi", "Grub2 UEFI HTTPS" => "https://#{httpboot_host}/httpboot/grub2/grub#{precision}.efi", "Grub2 UEFI HTTPS SecureBoot" => "https://#{httpboot_host}/httpboot/grub2/shim#{precision}.efi", diff --git a/app/services/proxy_api/tftp.rb b/app/services/proxy_api/tftp.rb index dc4f0582ca7d..e692839779b9 100644 --- a/app/services/proxy_api/tftp.rb +++ b/app/services/proxy_api/tftp.rb @@ -10,6 +10,7 @@ def initialize(args) # [+mac+] : MAC address # [+args+] : Hash containing # :pxeconfig => String containing the configuration + # :targetos => String containing the lowercase target operating system name or nil # Returns : Boolean status def set(kind, mac, args) parse(post(args, "#{kind}/#{mac}"))