RFC: UEFI HTTP booting

rfc

#1

UEFI supports new kind of booting workflow via HTTP instead of traditional (slow and clunky) TFTP. Implementing this in Foreman is possible with relatively small amount of changes.

Motivation

Foreman already supports iPXE HTTP booting which is used for booting virtual machines with iPXE firmware built-in and UEFI HTTP Boot feature will be essentially the same. The feature can enable both UEFI HTTP and iPXE HTTP booting as the implementation requires only two main steps described below.

Detailed design

  1. I propose to implement a simple HTTP/HTTPS service as a foreman-proxy plugin serving all files from TFTP directory. Only regular files are supported, listing of directories will be disabled.

  2. New feature called “HTTPBOOT” and smart proxy association must be created which will help to determine smart proxy hostname to use for the HTTP URL.

  3. New set of PXELoader options will be available: PXELinux EFI HTTP, PXEGrub EFI HTTP, PXEGrub2 EFI HTTP and they will provide DHCP filename options in a form of http://proxy_hostname/path/to/tftp/file/grub2.efi

  4. For unknown hosts and discovery support, we need to modify our dhcpd.conf file deployed by our puppet installer:

     option arch code 93 = unsigned integer 16; # RFC4578
    
     # support for UEFI HTTP Boot on Intel architectures
     class "httpclients" {
       match if substring (option vendor-class-identifier, 0, 9) = "HTTPClient";
       if option arch = 00:0F {
         filename "https://foreman.proxy.example.com:8443/grub2/bootia32.efi";
       } else if option arch = 00:10 {
         filename "https://foreman.proxy.example.com:8443/grub2/bootx64.efi";
       }
     }
    
     class "pxeclients" {
       match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
       next-server 10.0.0.1;
       if exists user-class and option user-class = "iPXE" {
         filename "https://foreman.example.com:443/unattended/iPXE";
       } else if option arch = 00:06 {
         filename "grub2/bootia32.efi";
       } else if option arch = 00:07 {
         filename "grub2/bootx64.efi";
       } else {
         filename "pxelinux.0";
       }
     }
    

Drawbacks

First, this is all theory, I haven’t tried and it can happen that grub2 bootloader will not be able to search files on HTTP. It is also possible that upstream version implemented this but version from RHEL7 won’t support this for now.

Exposing PXE configurations via HTTP/HTTPS adds new attack vector, but since the files are already readable by TFTP and we won’t provide directory listings, the attacker chances to read existing loader configuration are the same. We need to make sure we only read regular files (not symlinks) and only read from the root directory.

Links


UEFI & dhcpd.conf
#2

I had zero feedback so far, I was thinking maybe I can implement step (3) from the list so users can at least get it working manually (if they expose TFTP via HTTP). All the rest is just configuration.

I was also thinking that maybe step (2) is just an overkill - we can simply assume that TFTP feature is the one deciding which Capsule to route to.


#3

I created a PR!

https://projects.theforeman.org/issues/24622


#4

Just a note, in the example DHCP config it should be 0 to 10:

match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";

#5

I was able to get grub to boot over HTTP, with the Tianocore firmware for qemu. The dhcpd.conf needed some modifications, my full dhcpd.conf is at https://gist.github.com/stbenjam/ba0eed2df4a2c5cad82a0723d1cad9bb. I’ll try the end-to-end stuff with your PR in a bit.

nvram = [
  "/usr/share/edk2.git/ovmf-x64/OVMF_CODE-pure-efi.fd:/usr/share/edk2.git/ovmf-x64/OVMF_VARS-pure-efi.fd"
]
  • Create a VM manually and set the firmware to UEFI before booting.

Some things we’ll need to figure out:

  • Can we change the firmware from BIOS to UEFI with Fog for libvirt?

  • Tianocore always seems to try PXE first, I’m not sure how to disable that. I just removed the grub2 directory from tftp so it didn’t see it.


#6

I was not able to find anything UEFI-related in fog libvirt, ovirt or vmware.

I bet its one of these NVMRAM settings, it must be there but unsure how to access it.

Well, the idea behind my patch is to publish tftp directory over http(s) (you need to do this yourself), so if you delete grub2 from there it won’t be available over http. Now, there are couple of options:

  1. We can use apache httpd for that, it should be just one small configuration file and katello/pulp installs httpd anyway.

  2. We could code a small http(s) service as a smart-proxy module.

Which one do you prefer? The former is cleaner from administrator perspective, the latter is cleaner from programmer point of view because this enables us to create an extra proxy feature (e.g. “httpboot”) and we could drop port and path settings as they would be known. On the other hand, we’d need to make sure both tftp and httpboot features are always present (httpboot actually depends on tftp in order to work).


#7

Vsphere does support UEFI booting. I believe I added that some time ago. It‘s a flag that you need to set during vm creation. I believe Foreman currently takes the PXELoder into account to make an educated guess what firmware type to choose for the vm.


#8

While Katello proxy installs do have apache, and that’s probably a big percentage of external proxies, it’s still nicer if it just works for everyone. However, there are some limitations here. First, to be able to use this we have to wait until CentOS 7.6 at least, since grub2 won’t grab grub.cfg from the HTTP server in earlier versions. Fedora has the latest bits.

Also worth noting, it can’t handle relative paths. If you say linuxefi boot/vmlinuz, it doesn’t work. You need to say /full/path/to/boot/vmlinuz. I filed https://bugzilla.redhat.com/show_bug.cgi?id=1616395 for this one.

Also, it explicitly expects to load modules from a fixed path on the HTTP server (/EFI). We might want to file a separate bug for that? Especially if the smart proxy plugin would have things at something like /httpboot/EFI.


#9

Do you know if vmware can do UEFI HTTP boot? It’s relatively new.


#10

Great, for testing we can grab grub2 binary from Fedora! Do we need Rawhide or latest stable is just fine you think?

Damn, we’d need to change our templates. I was hoping to re-use existing grub2 templates.

Yeah let’s do that. But we can simply map the endpoint to /EFI that sounds unique enough. Maybe it’s in UEFI spec and that’s something we can’t change. Templates plugin does map to /unattended as well, no big deal.


#11

New version is up, it uses new feature called HTTPBoot, make sure to pick it. If it’s not picked, it falls back to Foreman unattended_url setting. Smart proxy with enabled “httpboot” module is required. I haven’t tested this end-to-and yet.

Foreman core part:

https://github.com/theforeman/foreman/pull/5950

Smart proxy part:

https://github.com/theforeman/smart-proxy/pull/605


#12

I’ve been able to get a full end-to-end test of this, this is awesome, thanks Lukas! Here’s how I got it working:

Libvirt

If you’re not testing on hardware, a great way to test this is using UEFI firmware on a QEMU vm. There’s a package called edk2-ovmf you can use. On Fedora, it’s got the latest and greatest. If you are on CentOS 7 or want to use the latest nightlies, you can get them from this fedora documentation. They don’t seem to have TLS support, which means you can’t enroll a TLS certificate to make HTTPS boot work. The Fedora 28 firmware does seem to support this.

On CentOS 7 only (Fedora already knows the paths), after installing the firmware package, you’ll also need to configure the nvram setting ini /etc/libvirt/qemu.conf:

nvram = [
  "/usr/share/edk2.git/ovmf-x64/OVMF_CODE-pure-efi.fd:/usr/share/edk2.git/ovmf-x64/OVMF_VARS-pure-efi.fd"
]

Once you do that, you can create an UEFI vm, by selecting the UEFI x86_64 firmware:

Foreman

You’ll need Lukas’ two PR’s above.

Configure the smart-proxy with the httpboot plugin, setting the directory to /var/lib/tftpboot. You’ll need a symlink for the boot directory:

ln -s /var/lib/tftpboot/boot /var/lib/tftpboot/grub2/boot

There’s an installer PR for this:

https://github.com/theforeman/puppet-foreman_proxy/pull/449

Once you’ve got that setup, create a Foreman host, and set the PXE loader to Grub2 UEFI HTTP. Power on the VM. Note: you’ll need to create the VM manually, as foreman automatically creates libvirt VM’s with BIOS.

grub2

Unfortunately the fully working grub2 won’t be in until Fedora 29 or CentOS 7.6 (at the earliest). But, you can build your own.

Checkout https://github.com/rhboot/grub2/pull/29, and let’s build it:

$ mkdir /tmp/grub
$ ./autogen.sh
$ ./configure --with-platform=efi --target=x86_64 --disable-werror --prefix=/tmp/grub
$ make && make install
$ /tmp/grub/bin/grub-mkimage -O x86_64-efi -o grubx64.efi -p /EFI/centos -d /tmp/grub/lib/grub/x86_64-efi all_video boot btrfs cat chain configfile echo efifwsetup efinet ext2 fat font gfxmenu gfxterm gzio halt hfsplus iso9660 jpeg loadenv lvm mdraid09 mdraid1x minicmd normal part_apple part_msdos part_gpt password_pbkdf2 png reboot search search_fs_uuid search_fs_file search_label sleep syslinuxcfg test tftp regexp video xfs linuxefi multiboot2 multiboot

Put the resulting grubx64.efi into your /var/lib/tftpboot/grub2 directory.


Serve TFTP files via a router
#13

Thank you so much testing this, writing the two patches was actually less work than testing this out I believe! Kudos.