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)
nofaralfasi 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"))
nofaralfasi marked this conversation as resolved.
Show resolved Hide resolved

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
85 changes: 84 additions & 1 deletion test/tftp/tftp_server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def test_create_default
end
@subject.create_default @content
end

def test_dashed_mac
assert "aa-bb-cc-dd-ee-ff", @subject.dashed_mac(@mac)
end
end

class HelperServerTest < Test::Unit::TestCase
Expand Down Expand Up @@ -111,11 +115,90 @@ def setup_paths
class TftpPxegrub2ServerTest < Test::Unit::TestCase
include TftpGenericServerSuite

def setup
@arch = "x86_64"
@bootfile_suffix = "x64"
@os = "redhat"
@release = "9.4"
super
end

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

def test_pxeconfig_dir
assert_equal File.join(@subject.path, "host-config", @subject.dashed_mac(@mac).downcase, "grub2"), @subject.pxeconfig_dir(@mac)
assert_equal File.join(@subject.path, "grub2"), @subject.pxeconfig_dir()
end

def test_release_specific_bootloader_path
release_specific_bootloader_path = File.join(@subject.path, "bootloader-universe/pxegrub2", @os, @release, @arch)
Dir.stubs(:exist?).with(release_specific_bootloader_path).returns(true).once
assert_equal release_specific_bootloader_path, @subject.bootloader_path(@os, @release, @arch)
end

def test_default_bootloader_path
release_specific_bootloader_path = File.join(@subject.path, "bootloader-universe/pxegrub2", @os, @release, @arch)
default_bootloader_path = File.join(@subject.path, "bootloader-universe/pxegrub2", @os, "default", @arch)
Dir.stubs(:exist?).with(release_specific_bootloader_path).returns(false).once
Dir.stubs(:exist?).with(default_bootloader_path).returns(true).once
assert_equal default_bootloader_path, @subject.bootloader_path(@os, @release, @arch)
end

def test_bootloader_universe_symlinks
temp_dir = Dir.mktmpdir()
pxeconfig_dir_mac = @subject.pxeconfig_dir(@mac)
symlinks = [
{ source: File.join(temp_dir, "dummy1.efi"), symlink: File.join(pxeconfig_dir_mac, "dummy1.efi") },
{ source: File.join(temp_dir, "dummy2.efi"), symlink: File.join(pxeconfig_dir_mac, "dummy2.efi") },
]
symlinks.each do |entry|
FileUtils.touch(entry[:source])
end
assert_equal(
symlinks.sort_by { |entry| entry[:source] },
@subject.bootloader_universe_symlinks(temp_dir, pxeconfig_dir_mac).sort_by { |entry| entry[:source] }
)
end

def test_default_symlinks
pxeconfig_dir = @subject.pxeconfig_dir
pxeconfig_dir_mac = @subject.pxeconfig_dir(@mac)
symlinks = [
{ source: File.join(pxeconfig_dir, "grub#{@bootfile_suffix}.efi"), symlink: File.join(pxeconfig_dir_mac, "boot.efi") },
{ source: File.join(pxeconfig_dir, "grub#{@bootfile_suffix}.efi"), symlink: File.join(pxeconfig_dir_mac, "grub#{@bootfile_suffix}.efi") },
{ source: File.join(pxeconfig_dir, "shim#{@bootfile_suffix}.efi"), symlink: File.join(pxeconfig_dir_mac, "boot-sb.efi") },
{ source: File.join(pxeconfig_dir, "shim#{@bootfile_suffix}.efi"), symlink: File.join(pxeconfig_dir_mac, "shim#{@bootfile_suffix}.efi") },
]
assert_equal symlinks, @subject.default_symlinks(@bootfile_suffix, @subject.pxeconfig_dir(@mac))
end

def test_create_symlinks
symlinks = [
{ source: "/path/to/source1", symlink: "/path/to/symlink1" },
{ source: "/path/to/source2", symlink: "/another/path/to/symlink2" },
]
FileUtils.expects(:ln_s).with("source1", "/path/to/symlink1", {:force => true}).once
FileUtils.expects(:ln_s).with("../../../path/to/source2", "/another/path/to/symlink2", {:force => true}).once
@subject.create_symlinks(symlinks)
end

def test_del
pxe_config_files.each do |file|
@subject.expects(:delete_file).with(file).once
end
@subject.expects(:delete_host_dir).with(@mac).once
@subject.del @mac
end
end

class TftpPoapServerTest < Test::Unit::TestCase
Expand Down