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

Do not merge (depends on storage-ng) - Supporting LUKS2 encryption by TPM2 device (jsc#PED-10703) #713

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
9ab7637
enable TPM2
schubi2 Feb 21, 2025
7ecbaa7
enable TPM2
schubi2 Feb 21, 2025
eb5eddc
secure boot for grub2bls
schubi2 Feb 25, 2025
11d7f51
show summary secure boot
schubi2 Feb 25, 2025
8380843
show summary secure boot
schubi2 Feb 25, 2025
336967b
merged with master
schubi2 Mar 3, 2025
65835f2
handling TPM2 support
schubi2 Mar 5, 2025
d622f1a
handling TPM2 support
schubi2 Mar 5, 2025
6410a8e
test
schubi2 Mar 6, 2025
9a5f439
using target.bash
schubi2 Mar 6, 2025
cf46331
test
schubi2 Mar 6, 2025
abd2bd2
test
schubi2 Mar 6, 2025
7fe2568
test
schubi2 Mar 6, 2025
27300a1
test
schubi2 Mar 6, 2025
d9994b9
test
schubi2 Mar 6, 2025
c6baf40
test
schubi2 Mar 7, 2025
f3c44e9
test
schubi2 Mar 7, 2025
7d11f0b
test
schubi2 Mar 7, 2025
009e27f
test
schubi2 Mar 7, 2025
8d37eb5
test
schubi2 Mar 7, 2025
d3633c6
cleanup
schubi2 Mar 7, 2025
c617b85
cleanup
schubi2 Mar 7, 2025
d644b91
cleanup set-default
schubi2 Mar 11, 2025
358db95
cleanup set-default
schubi2 Mar 11, 2025
b8f389c
cleanup set-default
schubi2 Mar 11, 2025
a986ea0
cleanup set-default
schubi2 Mar 11, 2025
9a2aba9
cleanup set-default
schubi2 Mar 11, 2025
a5e9121
cleanup set-default
schubi2 Mar 11, 2025
b62aca7
removed logging
schubi2 Mar 11, 2025
04fa2fe
renaming
schubi2 Mar 11, 2025
f345647
rubocop
schubi2 Mar 12, 2025
7dae85e
rubocop
schubi2 Mar 12, 2025
7c85137
rubocop
schubi2 Mar 12, 2025
0bfa3d0
rubocop
schubi2 Mar 12, 2025
b4f206d
packaging
schubi2 Mar 14, 2025
f97ef94
fixed testcase
schubi2 Mar 14, 2025
e611cee
fixed testcase
schubi2 Mar 14, 2025
5bde8db
fixed testcase
schubi2 Mar 17, 2025
98ef82f
moved to prtected
schubi2 Mar 17, 2025
9252750
added testcase
schubi2 Mar 17, 2025
90b71b0
fixed testcase
schubi2 Mar 17, 2025
d35d5e7
fixed testcase
schubi2 Mar 17, 2025
867668c
changed password
schubi2 Mar 18, 2025
27dabb2
cleanup
schubi2 Mar 18, 2025
9f22d73
used private
schubi2 Mar 18, 2025
333f4c0
fixed test
schubi2 Mar 18, 2025
82598c1
fixed testcase
schubi2 Mar 18, 2025
89b5341
cleanup
schubi2 Mar 18, 2025
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
7 changes: 7 additions & 0 deletions package/yast2-bootloader.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
-------------------------------------------------------------------
Fri Mar 14 14:06:16 UTC 2025 - Stefan Schubert <[email protected]>

- Selecting grub2-bls bootloader if a TPM2 device is used for
checking encryptions (jsc#PED-10703).
- 5.0.16

-------------------------------------------------------------------
Thu Feb 27 08:44:41 UTC 2025 - Stefan Schubert <[email protected]>

Expand Down
10 changes: 5 additions & 5 deletions package/yast2-bootloader.spec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@


Name: yast2-bootloader
Version: 5.0.15
Version: 5.0.16
Release: 0
Summary: YaST2 - Bootloader Configuration
License: GPL-2.0-or-later
Expand All @@ -30,8 +30,8 @@ BuildRequires: yast2 >= 4.5.16
BuildRequires: yast2-devtools >= 4.2.2
# yast/rspec/helpers.rb
BuildRequires: yast2-ruby-bindings >= 4.4.7
# BlkDevice#preferred_name and Filesystems::BlkFilesystem#preferred_name
BuildRequires: yast2-storage-ng >= 4.3.36
# Supporting LUKS2 encryption by TPM2 device
BuildRequires: yast2-storage-ng >= 5.0.28
# lenses needed also for tests
BuildRequires: augeas-lenses
BuildRequires: rubygem(%rb_default_ruby_abi:cfa_grub2) >= 1.0.1
Expand All @@ -47,8 +47,8 @@ Requires: yast2 >= 4.5.16
Requires: yast2-core >= 2.18.7
Requires: yast2-packager >= 2.17.24
Requires: yast2-pkg-bindings >= 2.17.25
# Y2Storage::Arch#efibootmgr?
Requires: yast2-storage-ng >= 4.4.22
# Supporting LUKS2 encryption by TPM2 device
Requires: yast2-storage-ng >= 5.0.28
# Support for multiple values in GRUB_TERMINAL
Requires: rubygem(%rb_default_ruby_abi:cfa_grub2) >= 1.0.1
# lenses are needed as cfa_grub2 depends only on augeas bindings, but also
Expand Down
89 changes: 76 additions & 13 deletions src/lib/bootloader/bls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "fileutils"
require "yast"
require "y2storage"
require "bootloader/sysconfig"
require "bootloader/cpu_mitigations"
require "cfa/grub2/default"
Expand All @@ -22,7 +23,7 @@ def initialize
end

def self.create_menu_entries
Yast::Execute.on_target!(SDBOOTUTIL, "--verbose", "add-all-kernels")
Yast::Execute.on_target!(SDBOOTUTIL, "add-all-kernels")
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
Expand All @@ -34,8 +35,7 @@ def self.create_menu_entries
end

def self.install_bootloader
Yast::Execute.on_target!(SDBOOTUTIL, "--verbose",
"install")
Yast::Execute.on_target!(SDBOOTUTIL, "install")
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
Expand Down Expand Up @@ -75,15 +75,19 @@ def self.menu_timeout
end

def self.write_default_menu(default)
Yast::Execute.on_target!(SDBOOTUTIL, "set-default", default)
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
"Cannot write default boot menu entry:\n" \
"Command `%{command}`.\n" \
"Error output: %{stderr}"
), command: e.commands.inspect, stderr: e.stderr)
)
return if default.empty?

begin
Yast::Execute.on_target!(SDBOOTUTIL, "set-default", default)
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
"Cannot write default boot menu entry:\n" \
"Command `%{command}`.\n" \
"Error output: %{stderr}"
), command: e.commands.inspect, stderr: e.stderr)
)
end
end

def self.default_menu
Expand All @@ -99,7 +103,66 @@ def self.default_menu
)
output = ""
end
output
output.strip
end

# Enabe TPM2, if it is required
def self.enable_tpm2
return unless Y2Storage::StorageManager.instance.encryption_use_tpm2

export_password
generate_machine_id

begin
Yast::Execute.on_target!("/usr/bin/sdbootutil",
"enroll", "--method=tpm2")
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
"Cannot enroll TPM2 method:\n" \
"Command `%{command}`.\n" \
"Error output: %{stderr}"
), command: e.commands.inspect, stderr: e.stderr)
)
end
end

def self.generate_machine_id
Yast::SCR.Execute(Yast::Path.new(".target.remove"), "/etc/machine-id")
begin
Yast::Execute.on_target!("/usr/bin/dbus-uuidgen",
"--ensure=/etc/machine-id")
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
"Cannot Cannot create machine-id:\n" \
"Command `%{command}`.\n" \
"Error output: %{stderr}"
), command: e.commands.inspect, stderr: e.stderr)
)
end
end

def self.export_password
pwd = Y2Storage::StorageManager.instance.encryption_tpm2_password
if pwd.empty?
Yast::Report.Error(_("Cannot pass empty password via the keyring."))
return
end

begin
Yast::Execute.on_target!("keyctl", "padd", "user", "cryptenroll", "@u",
stdout: :capture,
stdin: pwd)
rescue Cheetah::ExecutionFailed => e
Yast::Report.Error(
format(_(
"Cannot pass the password via the keyring:\n" \
"Command `%{command}`.\n" \
"Error output: %{stderr}"
), command: e.commands.inspect, stderr: e.stderr)
)
end
end
end
end
30 changes: 19 additions & 11 deletions src/lib/bootloader/bls_sections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,35 @@ class BlsSections
# or an empty array
attr_reader :all

# @return [String] title of default boot section.
attr_reader :default

def initialize
@all = []
@default = ""
end

# @return [String] title of default boot section.
def default
return unless @data

entry = @data.select { |d| d["id"] == @default }
if entry.empty?
""
else
entry.first["title"]
end
end

# Sets default section internally.
# @param [String] value of new boot title to boot
# @note to write it to system use #write later
def default=(value)
log.info "set new default to '#{value.inspect}'"

# empty value mean no default specified
if !all.empty? && !all.include?(value) && !value.empty?
log.warn "Invalid value #{value} trying to set as default. Fallback to default"
value = ""
entry = @data.select { |d| d["title"] == value }
if entry.empty?
log.warn "Invalid value '#{value}'"
@default = ""
else
@default = entry.first["id"]
log.info "set new default to '#{value.inspect}' --> '#{@default}'"
end

@default = value
end

# writes default to system making it persistent
Expand Down
4 changes: 4 additions & 0 deletions src/lib/bootloader/bootloader_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ def proposed_name
return prefered_bootloader
end

if bls_installable? && Y2Storage::StorageManager.instance.encryption_use_tpm2
return "grub2-bls" # TPM2 chips should be used by grub2-bls bootloader
end

if ["systemd-boot", "grub2-bls"].include?(prefered_bootloader) && bls_installable?
return prefered_bootloader
end
Expand Down
10 changes: 6 additions & 4 deletions src/lib/bootloader/grub2bls.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ def proposed?

# writes configuration to target disk
def write(*)
Bls.install_bootloader if Yast::Stage.initial # while new installation only (currently)
Bls.create_menu_entries
Bls.install_bootloader
if Yast::Stage.initial # while new installation only
Bls.install_bootloader
Bls.create_menu_entries
Bls.enable_tpm2
end
@sections.write
Bls.write_menu_timeout(grub_default.timeout)

Expand Down Expand Up @@ -155,7 +157,7 @@ def packages
res = super
res << ("grub2-" + grub2bls_architecture + "-efi-bls")
res << "sdbootutil"
res << "shim" if secure_boot
res << "shim"
res
end

Expand Down
7 changes: 5 additions & 2 deletions src/lib/bootloader/systemdboot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ def read
def write(etc_only: false)
super
log.info("Writing settings...")
Bls.install_bootloader if Yast::Stage.initial # while new installation only (currently)
if Yast::Stage.initial # while new installation only (currently)
Bls.install_bootloader
Bls.enable_tpm2
end
write_kernel_parameter
Bls.create_menu_entries
Bls.write_menu_timeout(menu_timeout)
Expand Down Expand Up @@ -179,7 +182,7 @@ def packages

case Yast::Arch.architecture
when "x86_64"
res << "shim" if secure_boot
res << "shim"
else
log.warn "Unknown architecture #{Yast::Arch.architecture} for systemdboot"
end
Expand Down
17 changes: 16 additions & 1 deletion src/modules/BootSupportCheck.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ def SystemSupported
supported = GRUB2EFI() && supported
when "systemd-boot"
supported = SYSTEMDBOOT() && supported
when "grub2-bls"
supported = GRUB2BLS() && supported
end

log.info "Configuration supported: #{supported}"
Expand Down Expand Up @@ -174,26 +176,39 @@ def check_mbr
false
end

def check_tpm2
return true unless Y2Storage::StorageManager.instance.encryption_use_tpm2

add_new_problem(_("This bootloader cannot handle encryption supported by a TPM2 device. "))
false
end

# GRUB2-related check
def GRUB2
ret = []
ret << check_gpt_reserved_partition if Arch.x86_64
ret << check_activate_partition if Arch.x86_64 || Arch.ppc64
ret << check_mbr if Arch.x86_64
ret << check_tpm2

ret.all?
end

# GRUB2EFI-related check
def GRUB2EFI
true
check_tpm2
end

# systemd-boot-related check
def SYSTEMDBOOT
true
end

# grub2-bls-related check
def GRUB2BLS
true
end

def stage1
::Bootloader::BootloaderFactory.current.stage1
end
Expand Down
8 changes: 4 additions & 4 deletions test/bls_sections_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
.and_return("openSUSE")
allow(Yast::Execute).to receive(:on_target)
.with("/usr/bin/bootctl", "--json=short", "list", stdout: :capture)
.and_return("[{\"title\" : \"openSUSE Tumbleweed\", \"isDefault\" : true }," \
"{\"title\" : \"Snapper: *openSUSE Tumbleweed 20241107\", \"isDefault\" : false}]")
.and_return("[{\"title\" : \"openSUSE Tumbleweed\", \"id\" : \"openSUSE.conf\", \"isDefault\" : true }," \
"{\"title\" : \"Snapper: *openSUSE Tumbleweed 20241107\", \"id\" : \"Snapper.conf\", \"isDefault\" : false}]")
allow(Bootloader::Bls).to receive(:default_menu)
.and_return("openSUSE Tumbleweed")
.and_return("openSUSE.conf")

subject.read
end
Expand Down Expand Up @@ -49,7 +49,7 @@
it "writes default value if set" do
subject.default = "Snapper: *openSUSE Tumbleweed 20241107"
expect(Bootloader::Bls).to receive(:write_default_menu)
.with(subject.default)
.with("Snapper.conf")

subject.write
end
Expand Down
26 changes: 24 additions & 2 deletions test/bls_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
describe "#create_menu_entries" do
it "calls sdbootutil add-all-kernels" do
expect(Yast::Execute).to receive(:on_target!)
.with("/usr/bin/sdbootutil", "--verbose", "add-all-kernels")
.with("/usr/bin/sdbootutil", "add-all-kernels")
subject.create_menu_entries
end
end

describe "#install_bootloader" do
it "calls sdbootutil install" do
expect(Yast::Execute).to receive(:on_target!)
.with("/usr/bin/sdbootutil", "--verbose", "install")
.with("/usr/bin/sdbootutil", "install")
subject.install_bootloader
end
end
Expand Down Expand Up @@ -56,4 +56,26 @@
end
end

describe "#enable_tpm2" do
context "TPM2 is used for encryption" do
before do
allow(Y2Storage::StorageManager.instance).to receive(:encryption_use_tpm2).and_return(true)
allow(Y2Storage::StorageManager.instance).to receive(:encryption_tpm2_password).and_return("123456")
end

it "enrolls the TPM2" do
expect(Yast::Execute).to receive(:on_target!)
.with("keyctl", "padd", "user", "cryptenroll", "@u",
stdout: :capture,
stdin: "123456")
expect(Yast::Execute).to receive(:on_target!)
.with("/usr/bin/sdbootutil", "enroll", "--method=tpm2")
expect(Yast::Execute).to receive(:on_target!)
.with("/usr/bin/dbus-uuidgen",
"--ensure=/etc/machine-id")

subject.enable_tpm2
end
end
end
end
Loading
Loading