UEFI HTTPBoot with OVMF

Jump to: navigation, search
UEFI HTTPBoot with OVMF
UEFI HTTPBoot with OVMF
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.



Please refer to Help:Editing in order to write a quality approved article.

Introduction

HTTPBoot was added into UEFI SPEC since 2.5. It aims to replace PXE and provides more features. Actually, the concept of HTTPBoot is similar to PXE. It starts with the HTTP URL from the DHCP server and fetches the data with the HTTP protocol. The key difference between HTTPBoot and PXE is the support of DNS. With DNS, the firmware and the bootloader can resolve the domain name so it's possible to pass the well-known URL to download the image instead of the explicit IP URL. Besides, HTTP is designed to cross different domains, while tftp (PXE) is only for the local network. This article provides the HOWTO to set up the simplest scheme of HTTPBoot: one server and one client. The server is the host computer and the client is the guest virtual machine.


Preparation

There are several networking settings in qemu. The tap networking is the best choice since it utilizes the virtual interface to achieve the two way communication between the host and the guest. It provides a complete isolated network as long as we don't bridge the virtual interface the any real one. This is very important since we are going to setup the dhcp server which could easily mess up the local network.

[host] <--> (tap0) <--> [guest]

This article uses the IP subnets 192.168.111.0/24 (v4) and 2001:db8:f00f:cafe::/64 (v6). Please adjust the related settings in case any conflict exists.

The Server

As a HTTPBoot server, the host computer has to install the following packages: dhcp-server, lighttpd (or apache2), dnsmasq, and tunctl.

The Client

To set up a virtual machine with the latest UEFI, the host needs qemu and OVMF. OVMF is the UEFI implementation for qemu. For the better support of HTTPBoot, it's recommended to use ovmf >= r18743.

The latest OVMF is available in https://build.opensuse.org/package/show/Virtualization/ovmf

Just install qemu-ovmf-x86_64 which contains the firmware files for qemu.

OS Image

SUSE Linux Enterprise starts to support HTTP Boot since 12-SP3 and HTTPS Boot since 15. For openSUSE, you need Leap 15 or higher to support HTTP Boot or HTTPS Boot.


Configure The Server

Network Interface

First, we set up the a tap interface for the communication between the host and the guest.

 # tunctl -u <username> -t tap0

If the firewall is enabled, it's recommended to add tap0 to the "Internal Zone", so the ports won't be blocked.

Add the IP address with ip:

 # ip link set dev tap0 up
 # ip addr add 192.168.111.1/24 dev tap0
 # ip addr -6 add 2001:db8:f00f:cafe::1/64 dev tap0

In case ip complains "Permission Denied" when adding the ipv6 address, try to enable ipv6 for tap0 with

 # sysctl net.ipv6.conf.tap0.disable_ipv6=0

DNS Server

DNS is optional but it's nice to give your server a well-known name. To set up the DNS server, add the following lines to /etc/dnsmasq.conf

 interface=tap0
 addn-hosts=/etc/dnsmasq.d/hosts.conf

Then, create the mapping of the domain name of the IP address in /etc/dnsmasq.d/hosts.conf

 192.168.111.1 www.httpboot.local
 2001:db8:f00f:cafe::1 www.httpboot.local

Now, it's time to start the DNS server.

 # systemctl start dnsmasq

NOTE: UEFI 2.7 changes the device path of HTTPBoot and inserts a DNS node, and the shim bootloader is currently incompatible with the change. The patch is now pending in upstream (https://github.com/rhboot/shim/pull/103). To work around this issue, just remove the name server from the dhcp server settings and replace the domain name with the server IP.

DHCPv4 Server

Here we will set up the mixed DHCPv4 server for both PXEBoot and HTTPBoot. Add the following lines to /etc/dhcpd.conf:

 option domain-name-servers 192.168.111.1;
 option routers 192.168.111.1;
 default-lease-time 14400;
 ddns-update-style none;
 subnet 192.168.111.0 netmask 255.255.255.0 {
   range dynamic-bootp 192.168.111.100 192.168.111.120;
   default-lease-time 14400;
   max-lease-time 172800;
   class "pxeclients" {
     match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
     next-server 192.168.111.1;
     filename "/bootx64.efi";
   }
   class "httpclients" {
     match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";
     option vendor-class-identifier "HTTPClient";
     filename "http://www.httpboot.local/sle/EFI/BOOT/bootx64.efi";
   }
 }

First, we match the identifier from the client to tell what kind of service it requests. For HTTPBoot, the DHCPv4 server MUST use "HTTPClient" for option 60 since the client use it to identify whether this is a HTTPBoot offer or not.

Start the dhcp daemon:

 # systemctl start dhcpd

NOTE: Due to a bug(*) in shim, you might see "Invalid Parameter" from shim if the gateway (option routers) is not set.

(*) https://github.com/rhboot/shim/pull/136

DHCPv6 Server

To set up the DHCPv6 server, add the following lines to /etc/dhcpd6.conf:

 option dhcp6.bootfile-url code 59 = string;
 option dhcp6.vendor-class code 16 = {integer 32, integer 16, string};
 subnet6 2001:db8:f00f:cafe::/64 {
         range6 2001:db8:f00f:cafe::42:10 2001:db8:f00f:cafe::42:99;
         option dhcp6.bootfile-url "http://www.httpboot.local/sle/EFI/BOOT/bootx64.efi";
         option dhcp6.name-servers 2001:db8:f00f:cafe::1;
         option dhcp6.vendor-class 0 10 "HTTPClient";
 }

First, we define the type of the boot URL and the vendor class and then set the details. Like the DHCPv4 settings, we have to assign the boot URL. Please note that we have to use the IPv6 name in the boot URL. Similar to option 60 in DHCPv4, we have to specify the vendor class. The vendor class option in DHCPv6 consists of the enterprise number and the vendor class data (length and the content). The HTTPBoot driver doesn't care the enterprise number, so we just use 0. The content of the vendor class data has to be "HTTPClient", or the client will just ignore the offer.

For the older HTTPBoot implementation, it doesn't follow RFC 3315 and will need a different setting like this:

 option dhcp6.bootfile-url code 59 = string;
 option dhcp6.vendor-class code 16 = string;
 subnet6 2001:db8:f00f:cafe::/64 {
         range6 2001:db8:f00f:cafe::42:10 2001:db8:f00f:cafe::42:99;
         option dhcp6.bootfile-url "http://www.httpboot.local/sle/EFI/BOOT/bootx64.efi";
         option dhcp6.name-servers 2001:db8:f00f:cafe::1;
         option dhcp6.vendor-class "HTTPClient";
 }

Now start the DHCPv6 daemon.

 # systemctl start dhcpd6

It's also possible to setup a DHCP6 server for both PXEBoot and HTTPBoot. Like this:

 option dhcp6.bootfile-url code 59 = string;
 option dhcp6.vendor-class code 16 = {integer 32, integer 16, string};
 
 subnet6 2001:db8:f00f:cafe::/64 {
         range6 2001:db8:f00f:cafe::42:10 2001:db8:f00f:cafe::42:99;
 
         class "PXEClient" {
                 match substring (option dhcp6.vendor-class, 6, 9);
         }
 
         subclass "PXEClient" "PXEClient" {
                 option dhcp6.bootfile-url "tftp://[2001:db8:f00f:cafe::1]/bootloader.efi";
         }
 
         class "HTTPClient" {
                 match substring (option dhcp6.vendor-class, 6, 10);
         }
 
         subclass "HTTPClient" "HTTPClient" {
                 option dhcp6.bootfile-url "http://www.httpboot.local/sle/EFI/BOOT/bootx64.efi";
                 option dhcp6.name-servers 2001:db8:f00f:cafe::1;
                 option dhcp6.vendor-class 0 10 "HTTPClient";
         }
 }

It could also go further to match the vendor-class for different architecture. For example, "HTTPClient:Arch:00016" means a x86_64 HTTPBoot client, and it can be rewritten as:

         class "HTTPClient" {
                 match substring (option dhcp6.vendor-class, 6, 21);
         }
 
         subclass "HTTPClient" "HTTPClient:Arch:00016" {
                 option dhcp6.bootfile-url "http://www.httpboot.local/sle/EFI/BOOT/bootx64.efi";
                 option dhcp6.name-servers 2001:db8:f00f:cafe::1;
                 option dhcp6.vendor-class 0 10 "HTTPClient";
         }

Then the server can serve different architectures at the same time.

Reference: https://www.mail-archive.com/edk2-devel@lists.01.org/msg14683.html

Firewall

The DHCP6 packets may be dropped by the firewall due to the RP filter in SLE/openSUSE Leap 15+. If you found "rpfilter_DROP" in the firewall log, edit /etc/firewalld/firewalld.conf:

 IPv6_rpfilter=no

TFTP server(Optional)

In case you need to support both PXE and HTTPBoot, a tftp server is necessary. Just install tftp and start the service:

 # systemctl start tftp.socket
 # systemctl start tftp.service

openSUSE/SLE now provide a special package, tftpboot-installation, for PXE. For example, just install tftpboot-installation-openSUSE-Tumbleweed-x86_64 and the Tumbleweed bootloader and related files will be installed in /srv/tftpboot/openSUSE-Tumbleweed-x86_64/. Read /srv/tftpboot/openSUSE-Tumbleweed-x86_64/README for more information.

HTTP Server

For the complete SLE/openSUSE HTTPBoot installation, copy everything in the first iso image to /srv/www/htdocs/sle/. Then, edit /srv/www/htdocs/sle/EFI/BOOT/grub.cfg to fit your needs. An example of grub.cfg:

 timeout=60
 default=1
 
 menuentry 'Installation IPv4' --class opensuse --class gnu-linux --class gnu --class os {
   set gfxpayload=keep
   echo 'Loading kernel ...'
   linuxefi /sle/boot/x86_64/loader/linux install=http://www.httpboot.local/sle
   echo 'Loading initial ramdisk ...'
   initrdefi /sle/boot/x86_64/loader/initrd
 }
 
 menuentry 'Installation IPv6' --class opensuse --class gnu-linux --class gnu --class os {
   set gfxpayload=keep
   echo 'Loading kernel ...'
   linuxefi /sle/boot/x86_64/loader/linux install=http://www.httpboot.local/sle ipv6only=1 ifcfg=*=dhcp6,DHCLIENT6_MODE=managed
   echo 'Loading initial ramdisk ...'
   initrdefi /sle/boot/x86_64/loader/initrd
 }


The SLE Deployment Guide(*) may provide more information for the installation server setup.

(*) https://www.suse.com/documentation/sles-12/book_sle_deployment/data/part_installserver.html

lighttpd

There are some modification is necessary to make lighttpd support both IPv4 and IPv6. Edit /etc/lighttpd/lighttpd.conf

 ##
 ## Use IPv6?
 ##
 #server.use-ipv6 = "enable"
 $SERVER["socket"] == "[::]:80" {  }

When server.use-ipv6 is enabled, it would make lighttpd only listen to IPv6, so we remove it and add an new IPv6 section with the default options.

Start the lighttpd daemon:

 # systemctl start lighttpd

apache2

Start the apache daemon:

 # systemctl start apache2

SSL Support for HTTP Server(Optional)

The TLS protocol was written in the UEFI spec since 2.5, and the latest OVMF already supports HTTPS Boot. For testing, we can create a self-signed certificate with openssl:

 $ openssl req -newkey rsa:4096 -nodes -keyout server.key -x509 -days 365 -out server.crt

Since we choose "www.httpboot.local" as our domain name, please use "www.httpboot.local" for "Common Name".

Convert the certificate into DER format for the client:

 $ openssl x509 -in server.crt -outform der -out server.der

Please note that this certificate is only for testing and NEVER use this certificate in the production machine.

Enroll the Certificate into OVMF

You have to enroll the server certificate (server.der) in the client side before using HTTPS Boot or the client would fail to make the connection to the server. For OVMF, the menu to enroll the server certificate is in "Device Manager" -> "Tls Auth Configuration".

Besides enrolling the certificate manually, it's possible to configure the trusted certificates for OVMF with "-fw_cfg". Since 9c7d0d49929, OVMF supports reading the certificate list from the fw_cfg entry, etc/edk2/https/cacerts. The certificate list has to be in the format of Signature Database.

To create the certificate list with efisiglist:

 $ efisiglist -a server.der -o certs.db

After creating the certificate list, append the following command to specify the fw_cfg entry:

 $ qemu ... -fw_cfg name=etc/edk2/https/cacerts,file=certs.db

Check more details in the "HTTPS Boot" section of OVMF README.

lighttpd

Since lighttpd needs the private key and the certificate in the same file, we have to unify them first.

 $ cat server.crt server.key > server-almighty.pem

Copy "server-almighty.pem" to /etc/ssl/private/.

 # cp server-almighty.pem /etc/ssl/private/
 # chown -R root:lighttpd /etc/ssl/private/server-almighty.pem
 # chmod 640 /etc/ssl/private/server-almighty.pem

Then, check /etc/lighttpd/modules.conf whether "mod_openssl" is in the "server.modules" section or not. For example:

 server.modules = (
   "mod_access",
   "mod_openssl",
 )

Next, add the following lines to "SSL Support" section in /etc/lighttpd/lighttpd.conf:

  # For IPv4
  $SERVER["socket"] == ":443" {
      ssl.engine                 = "enable"
      ssl.pemfile                = "/etc/ssl/private/server-almighty.pem"
  }
  # For IPv6
  $SERVER["socket"] == "[::]:443" {
      ssl.engine                 = "enable"
      ssl.pemfile                = "/etc/ssl/private/server-almighty.pem"
  }

Restart lighttpd to activate SSL support.

 # systemctl restart lighttpd

apache2

Before we start to configure apache, we have to check /etc/sysconfig/apache2 first.

The SSL support of apache is controlled by APACHE_SERVER_FLAGS, so we have to add the SSL flag.

 APACHE_SERVER_FLAGS="SSL"

Besides, make sure that ssl is in APACHE_MODULES. For example:

 APACHE_MODULES="actions alias auth_basic authn_file authz_host authz_groupfile authz_core authz_user autoindex cgi dir env expires include log_config mime negotiation setenvif ssl socache_shmcb userdir reqtimeout authn_core headers proxy proxy_http proxy_wstunnel"

Next, copy the private key and the certificate to /etc/apache2/.

 # cp server.key /etc/apache2/ssl.key/
 # chown wwwrun /etc/apache2/ssl.key/server.key
 # chmod 600 /etc/apache2/ssl.key/server.key
 # cp server.crt /etc/apache2/ssl.crt/

Create the ssl vhost configuration.

 # cd /etc/apache2/vhosts.d
 # cp vhost-ssl.template vhost-ssl.conf

Edit /etc/apache2/vhosts.d/vhost-ssl.conf to change the private key and the certificate:

 SSLCertificateFile /etc/apache2/ssl.crt/server.crt
 SSLCertificateKeyFile /etc/apache2/ssl.key/server.key

Restart apache to activate the SSL support:

 # systemctl restart apache2

Modify DHCP configuration

Last, remember to replace the "http://" prefix with "https://" in the dhcpd.conf/dhcpd6.conf and restart the dhcp server.

 # systemctl restart dhcpd
 # systemctl restart dhcpd6

Changes in grub.cfg

Since we create a self-signed certificate for our HTTPS server, if we specify the HTTPS url in grub.cfg, the SLE/openSUSE installation system may fail to verify the certificate and refuse to download files from our HTTPS server.

There are two possible solutions.

1. Add ssl.certs=0 to disable the certificate verification. For example:

 linuxefi /sle/boot/x86_64/loader/linux install=https://www.httpboot.local/sle ssl.certs=0

2. Create an initrd containing the server certificate. By default, the installation system searches the trusted certificates in /var/lib/ca-certificates/openssl/ and /var/lib/ca-certificates/pem/, so we have to create an initrd containing our server certificate in those directory. Here is the example of commands:

 ## Create new directories containing the target path
 $ mkdir -p initrd-new/var/lib/ca-certificates/openssl/
 $ mkdir -p initrd-new/var/lib/ca-certificates/pem/
 
 ## Copy our server certificate to the target paths
 $ cp server.crt initrd-new/var/lib/ca-certificates/openssl/my-ca.pem
 $ cp server.crt initrd-new/var/lib/ca-certificates/pem/my-ca.pem
 
 ## Create the link file, <hash>.0, to make the certificate be trusted
 $ ln -sr initrd-new/var/lib/ca-certificates/openssl/my-ca.pem initrd-new/var/lib/ca-certificates/openssl/`openssl x509 -hash -noout -in server.crt`.0
 $ ln -sr initrd-new/var/lib/ca-certificates/pem/my-ca.pem initrd-new/var/lib/ca-certificates/pem/`openssl x509 -hash -noout -in server.crt`.0
 
 ## Change the working directory to 'initrd-new'
 $ cd initrd-new
 
 ## Make sure the whole path is owned by root
 $ sudo chown -R root:root var
 
 ## Create initrd (ssl.img)
 $ find . | cpio --quiet -H newc -o | gzip -9 -n > ../ssl.img
 
 ## Back to the upper directory for the further actions
 $ cd ..

You can check the content of ssl.img with lsinitrd. For example:

 $ lsinitrd ssl.img
 Image: ssl.img: 4.0K
 ========================================================================
 Version: 
 
 Arguments: 
 dracut modules:
 ========================================================================
 drwxr-xr-x   3 johndoe  users           0 Sep  3 10:09 .
 drwxr-xr-x   3 root     root            0 Sep  3 16:27 var
 drwxr-xr-x   3 root     root            0 Sep  3 16:27 var/lib
 drwxr-xr-x   3 root     root            0 Sep  3 16:27 var/lib/ca-certificates
 drwxr-xr-x   2 root     root            0 Sep  3 16:28 var/lib/ca-certificates/openssl
 lrwxrwxrwx   1 root     root            9 Sep  3 16:28 var/lib/ca-certificates/openssl/c6bea024.0 -> my-ca.pem
 -rw-r--r--   1 root     root         2114 Sep  3 16:28 var/lib/ca-certificates/openssl/my-ca.pem
 drwxr-xr-x   2 root     root            0 Sep  3 16:28 var/lib/ca-certificates/pem
 lrwxrwxrwx   1 root     root            9 Sep  3 16:28 var/lib/ca-certificates/pem/c6bea024.0 -> my-ca.pem
 -rw-r--r--   1 root     root         2114 Sep  3 16:28 var/lib/ca-certificates/pem/my-ca.pem
 ========================================================================

Now we need to copy ssl.img to the directory of initrd, e.g. /srv/www/htdocs/sle/boot/x86_64/loader/, and modify grub.cfg to add ssl.img. For example:

 echo 'Loading kernel ...'
 linuxefi /sle/boot/x86_64/loader/linux install=https://www.httpboot.local/sle
 echo 'Loading initial ramdisk ...'
 initrdefi /sle/boot/x86_64/loader/initrd /sle/boot/x86_64/loader/ssl.img

NOTE: A simple bash script to create initrd is available: https://github.com/lcp/uefi-fun/blob/master/packcert/packcert.sh

Usage:

 $ sh packcert.sh server.crt ssl.img

Container Services

For the quick test, mightyboot(*) can create the services with containers. Just edit env to match the host network configuration and type

 $ docker-compose up

If the settings are good, it will bring up dhcp, dhcp6, dnsmasq, and lighttpd services on the given network interface and make them ready to test.

(*) https://github.com/lcp/mightyboot


Launch The Client

Now it's time to set up a virtual machine as the HTTPBoot client. There are a few qemu options needed to create a UEFI virtual machine.

  1. Specify the firmware
 -drive if=pflash,format=raw,readonly,file=/usr/share/qemu/ovmf-x86_64-code.bin \
 -drive if=pflash,format=raw,file=ovmf-x86_64-vars.bin

Please note that ovmf-x86_64-vars.bin must be writable. Just copy /usr/share/qemu/ovmf-x86_64-vars.bin to the working directory.

  1. Specify the network device
 -netdev tap,id=hostnet0,ifname=tap0,script=no,downscript=no \
 -device virtio-net-pci,romfile=,netdev=hostnet0

Since the host uses tap0 to communicate with the guest, we must specify the interface. "romfile=" is to disable the iPXE support so the virtual machine will use the native PXE and HTTPBoot functions from OVMF instead from iPXE.

The complete qemu command would be like this:

 $ qemu-system-x86_64 -enable-kvm \
                      -drive if=pflash,format=raw,readonly,file=/usr/share/qemu/ovmf-x86_64-code.bin \
                      -drive if=pflash,format=raw,file=ovmf-vars.bin \
                      -hda fat:hda-contents/ -monitor stdio \
                      -netdev tap,id=hostnet0,ifname=tap0,script=no,downscript=no \
                      -device virtio-net-pci,romfile=,netdev=hostnet0

If you are using libvirt, then you can use "rom bar" to disable iPXE support. For example:

    <interface type='bridge'>
      <mac address='00:11:22:33:44:55'/>
      <source bridge='br0'/>
      <model type='virtio'/>
      <rom bar='off'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
    </interface>

When the TianoCore logo shows, press "ESC" to enter the firmware menu.

Press "DOWN" to go to "Boot Manager" and press "Enter".

There will be several boot options, and two "EFI Network" options are the HTTPBoot IPv4 and HTTPBoot IPv6. Although the name of the boot options doesn't hint HTTPBoot, the device path does. If there is a URI() node appended in the end of the device path, it's a HTTPBoot boot option.

For example:

HTTPBoot IPv4

HTTPBoot IPv6

Just choose one of the boot options, and the firmware will start to download your UEFI application and execute it!


Another way to test http boot is to download a iso image from a URL. The benefit of this way is that you don't need to setup DHCP on host machine, and it only needs http server.

The first step is putting iso file to /srv/www/htdocs on server side:

e.g. /srv/www/htdocs/openSUSE-Leap-42.1-NET-x86_64.iso

On client code, you should launch UEFI firmware UI:

Device Manager -> Network Device List

e.g. IPv4

Enable DHCP or you can set static ip

Create a boot option by "HTTP Boot Configuration"

Edit "Boot URI", point to the position of ISO file

Select "UEFI HTTP" to boot to the ISO image


See also

Related articles

External links