Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #36833 - Add SecureBoot support for arbitrary operating systems to "Grub2 UEFI" PXE loaders #877

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 109 additions & 6 deletions modules/tftp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ def delete_file(file)
logger.debug "TFTP: Skipping a request to delete a file which doesn't exists"
end
end

def delete_host_dir(mac)
host_dir = File.join(path, 'host-config', dashed_mac(mac).downcase)
logger.debug "TFTP: Removing directory '#{host_dir}'."
FileUtils.rm_rf host_dir
end

def setup_bootloader(mac:, os:, release:, arch:, bootfile_suffix:)
end

def dashed_mac(mac)
mac.tr(':', '-')
end
end

class Syslinux < Server
Expand All @@ -75,7 +88,7 @@ def pxe_default
end

def pxeconfig_file(mac)
["#{pxeconfig_dir}/01-" + mac.tr(':', "-").downcase]
["#{pxeconfig_dir}/01-" + dashed_mac(mac).downcase]
end
end
class Pxelinux < Syslinux; end
Expand All @@ -90,21 +103,111 @@ def pxe_default
end

def pxeconfig_file(mac)
["#{pxeconfig_dir}/menu.lst.01" + mac.delete(':').upcase, "#{pxeconfig_dir}/01-" + mac.tr(':', '-').upcase]
["#{pxeconfig_dir}/menu.lst.01" + mac.delete(':').upcase, "#{pxeconfig_dir}/01-" + dashed_mac(mac).upcase]
end
end

class Pxegrub2 < Server
def pxeconfig_dir
"#{path}/grub2"
def bootloader_path(os, release, arch)
[release, "default"].each do |version|
bootloader_path = File.join(path, 'bootloader-universe/pxegrub2', os, version, arch)

logger.debug "TFTP: Checking if bootloader universe is configured for OS '#{os}' version '#{version}' (#{arch})."

if Dir.exist?(bootloader_path)
logger.debug "TFTP: Directory '#{bootloader_path}' exists."
return bootloader_path
end

logger.debug "TFTP: Directory '#{bootloader_path}' does not exist."
end
nil
end

def bootloader_universe_symlinks(bootloader_path, pxeconfig_dir_mac)
Dir.glob(File.join(bootloader_path, '*.efi')).map do |source_file|
{ source: source_file, symlink: File.join(pxeconfig_dir_mac, File.basename(source_file)) }
end
end

def default_symlinks(bootfile_suffix, pxeconfig_dir_mac)
pxeconfig_dir = pxeconfig_dir()

grub_source = "grub#{bootfile_suffix}.efi"
shim_source = "shim#{bootfile_suffix}.efi"

[
{ source: grub_source, symlink: "boot.efi" },
{ source: grub_source, symlink: grub_source },
{ source: shim_source, symlink: "boot-sb.efi" },
{ source: shim_source, symlink: shim_source },
].map do |link|
{ source: File.join(pxeconfig_dir, link[:source]), symlink: File.join(pxeconfig_dir_mac, link[:symlink]) }
end
end

def create_symlinks(symlinks)
goarsna marked this conversation as resolved.
Show resolved Hide resolved
symlinks.each do |link|
relative_source_path = Pathname.new(link[:source]).relative_path_from(Pathname.new(link[:symlink]).parent).to_s

logger.debug "TFTP: Creating relative symlink: #{link[:symlink]} -> #{relative_source_path}"
FileUtils.ln_s(relative_source_path, link[:symlink], force: true)
end
end

# Configures bootloader files for a host in its host-config directory
#
# @param mac [String] The MAC address of the host
# @param os [String] The lowercase name of the operating system of the host
# @param release [String] The major and minor version of the operating system of the host
# @param arch [String] The architecture of the operating system of the host
# @param bootfile_suffix [String] The architecture specific boot filename suffix
def setup_bootloader(mac:, os:, release:, arch:, bootfile_suffix:)
pxeconfig_dir_mac = pxeconfig_dir(mac)

logger.debug "TFTP: Deploying host specific bootloader files to '#{pxeconfig_dir_mac}'."

FileUtils.mkdir_p(pxeconfig_dir_mac)
FileUtils.rm_f(Dir.glob("#{pxeconfig_dir_mac}/*.efi"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we delete these binaries here if we are already using force: true in FileUtils.ln_s, which overwrites existing symlinks?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this to guarantee that we have a clean directory state on each rebuild of a host in case the files required in the host_config directory change in future.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only applies if the OS changes, as the bootloader names remain the same if the OS is unchanged, so there's no issue in that case. My goal is to minimize the risk of accidentally deleting necessary files (e.g., the binaries in /var/lib/tftpboot/grub2). However, since we validate the mac address beforehand (see here), I believe we’re safe in this regard.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took another and again deeper look at what happens regarding MAC address validation when the setup_bootloader method is called and yes: The way setup_bootloader is called guarantees that the FileUtils.rm_f command can and will only delete *.efi files in the :tftproot:/host-config/<MAC_address>/ directory.

Although I have to admit that the cases I am thinking about here might be really rare corner cases [1], I would prefer to stick to deleting the present *.efi files on updating the symlinks.

[1] An example would be when an OS vendor decides to follow another naming scheme for the shim and GRUB2 binaries.


bootloader_path = bootloader_path(os, release, arch)

if bootloader_path
logger.debug "TFTP: Creating symlinks from bootloader universe."
symlinks = bootloader_universe_symlinks(bootloader_path, pxeconfig_dir_mac)
else
logger.debug "TFTP: Creating symlinks from default bootloader files."
symlinks = default_symlinks(bootfile_suffix, pxeconfig_dir_mac)
end
create_symlinks(symlinks)
end

def del(mac)
super mac
delete_host_dir mac
end

def pxeconfig_dir(mac = nil)
goarsna marked this conversation as resolved.
Show resolved Hide resolved
if mac
File.join(path, 'host-config', dashed_mac(mac).downcase, 'grub2')
else
File.join(path, 'grub2')
end
end

def pxe_default
["#{pxeconfig_dir}/grub.cfg"]
end

def pxeconfig_file(mac)
["#{pxeconfig_dir}/grub.cfg-01-" + mac.tr(':', '-').downcase, "#{pxeconfig_dir}/grub.cfg-#{mac.downcase}"]
pxeconfig_dir_mac = pxeconfig_dir(mac)
[
"#{pxeconfig_dir_mac}/grub.cfg",
"#{pxeconfig_dir_mac}/grub.cfg-01-#{dashed_mac(mac).downcase}",
"#{pxeconfig_dir_mac}/grub.cfg-#{mac.downcase}",
"#{pxeconfig_dir}/grub.cfg-01-" + dashed_mac(mac).downcase,
"#{pxeconfig_dir}/grub.cfg-#{mac.downcase}",
]
end
end

Expand Down Expand Up @@ -146,7 +249,7 @@ def pxe_default
end

def pxeconfig_file(mac)
["#{pxeconfig_dir}/01-" + mac.tr(':', "-").downcase + ".ipxe"]
["#{pxeconfig_dir}/01-" + dashed_mac(mac).downcase + ".ipxe"]
end
end

Expand Down
5 changes: 3 additions & 2 deletions modules/tftp/tftp_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ def instantiate(variant, mac = nil)
Object.const_get("Proxy").const_get('TFTP').const_get(variant.capitalize).new
end

def create(variant, mac)
def create(variant, mac, os: nil, release: nil, arch: nil, bootfile_suffix: nil)
tftp = instantiate variant, mac
log_halt(400, "TFTP: Failed to setup host specific bootloader directory: ") { tftp.setup_bootloader(mac: mac, os: os, release: release, arch: arch, bootfile_suffix: bootfile_suffix) }
log_halt(400, "TFTP: Failed to create pxe config file: ") { tftp.set(mac, (params[:pxeconfig] || params[:syslinux_config])) }
end

Expand Down Expand Up @@ -48,7 +49,7 @@ def create_default(variant)
end

post "/:variant/:mac" do |variant, mac|
create variant, mac
create variant, mac, os: params[:targetos], release: params[:release], arch: params[:arch], bootfile_suffix: params[:bootfile_suffix]
end

delete "/:variant/:mac" do |variant, mac|
Expand Down
2 changes: 2 additions & 0 deletions modules/tftp/tftp_plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ module Proxy::TFTP
class Plugin < ::Proxy::Plugin
plugin :tftp, ::Proxy::VERSION

capability :bootloader_universe
goarsna marked this conversation as resolved.
Show resolved Hide resolved

rackup_path File.expand_path("http_config.ru", __dir__)

default_settings :tftproot => '/var/lib/tftpboot',
stejskalleos marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
2 changes: 1 addition & 1 deletion test/tftp/integration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def test_features
mod = response['tftp']
refute_nil(mod)
assert_equal('running', mod['state'], Proxy::LogBuffer::Buffer.instance.info[:failed_modules][:tftp])
assert_equal([], mod['capabilities'])
assert_equal(["bootloader_universe"], mod['capabilities'])

expected_settings = { 'tftp_servername' => 'tftp.example.com' }
assert_equal(expected_settings, mod['settings'])
Expand Down
8 changes: 7 additions & 1 deletion test/tftp/tftp_server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ class TftpPxegrub2ServerTest < Test::Unit::TestCase

def setup_paths
@subject = Proxy::TFTP::Pxegrub2.new
@pxe_config_files = ["grub2/grub.cfg-01-aa-bb-cc-dd-ee-ff", "grub2/grub.cfg-aa:bb:cc:dd:ee:ff"]
@pxe_config_files = [
"host-config/aa-bb-cc-dd-ee-ff/grub2/grub.cfg",
"host-config/aa-bb-cc-dd-ee-ff/grub2/grub.cfg-01-aa-bb-cc-dd-ee-ff",
"host-config/aa-bb-cc-dd-ee-ff/grub2/grub.cfg-aa:bb:cc:dd:ee:ff",
"grub2/grub.cfg-01-aa-bb-cc-dd-ee-ff",
"grub2/grub.cfg-aa:bb:cc:dd:ee:ff",
]
@pxe_default_files = ["grub2/grub.cfg"]
end
end
Expand Down