diff --git a/doc/boot-requirements.md b/doc/boot-requirements.md index dd3382ee6e..bbbdfe188c 100644 --- a/doc/boot-requirements.md +++ b/doc/boot-requirements.md @@ -66,6 +66,14 @@ - **does not require any partition (PReP will be reused and Grub2 can handle this setup)** - and it is not on the boot disk - **requires only a new PReP partition (to allocate Grub2)** + - with an encrypted proposal using LUKS2 + - if there are no suitable PReP partitions in the target disk + - **requires a new PReP and a new /boot partition** + - if there is already a suitable PReP partition in the disk + - and it is on the boot disk + - **requires a /boot partition** + - and it is not on the boot disk + - **requires a new PReP and a new /boot partition** - in bare metal (PowerNV) - with a partitions-based proposal - **does not require any booting partition (no Grub stage1, PPC firmware parses grub2.cfg)** @@ -274,6 +282,8 @@ - **does not require any particular volume** - in an encrypted proposal - **does not require any particular volume** + - in an encrypted proposal using LUKS2 + - **requires a new /boot partition to install Grub into it** - with a MBR gap too small to accommodate Grub - in a partitions-based proposal - if the file-system selected for / can embed grub (ext2/3/4 or btrfs) diff --git a/package/yast2-storage-ng.changes b/package/yast2-storage-ng.changes index 7efe6ae66a..b01669fb2b 100644 --- a/package/yast2-storage-ng.changes +++ b/package/yast2-storage-ng.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Sep 17 14:41:01 UTC 2019 - Steffen Winterfeldt + +- add warning if /boot is on a LUKS2 encrypted partition +- 4.2.39 + ------------------------------------------------------------------- Thu Sep 5 15:03:50 UTC 2019 - Ancor Gonzalez Sosa diff --git a/package/yast2-storage-ng.spec b/package/yast2-storage-ng.spec index f8a3362caa..3dd320ca20 100644 --- a/package/yast2-storage-ng.spec +++ b/package/yast2-storage-ng.spec @@ -16,7 +16,7 @@ # Name: yast2-storage-ng -Version: 4.2.38 +Version: 4.2.39 Release: 0 Summary: YaST2 - Storage Configuration License: GPL-2.0-only OR GPL-3.0-only diff --git a/src/lib/y2storage/boot_requirements_strategies/analyzer.rb b/src/lib/y2storage/boot_requirements_strategies/analyzer.rb index 04ec439146..128b485d3c 100644 --- a/src/lib/y2storage/boot_requirements_strategies/analyzer.rb +++ b/src/lib/y2storage/boot_requirements_strategies/analyzer.rb @@ -20,6 +20,7 @@ require "yast" require "pathname" require "y2storage/planned" +require "y2storage/encryption_type" module Y2Storage module BootRequirementsStrategies @@ -250,6 +251,15 @@ def boot_filesystem_type filesystem_type(device_for_boot) end + # Encryption type of boot device + # + # The device can be a planned one or filesystem from the devicegraph. + # + # @return [Y2Storage::EncryptionType] Encryption type + def boot_encryption_type + encryption_type(device_for_boot) + end + # Whether the partition table of the disk used for booting matches the # given type. # @@ -533,15 +543,25 @@ def in_bcache?(device) # The device can be a planned one or filesystem from the devicegraph. # # @param device [Filesystems::Base, Planned::Device, nil] - # @return [Boolean] false if device is nil + # @return [Boolean] device encryption state; return false if device is nil def encrypted?(device) - return false if device.nil? + !encryption_type(device).is?(:none) + end + # Encryption type of device + # + # The device can be a planned one or filesystem from the devicegraph. + # + # Note: returns EncryptionType::LUKS1 for Planned::Device as there's no encryption type. + # + # @param device [Filesystems::Base, Planned::Device, nil] + # @return [Y2Storage::EncryptionType] Encryption type + def encryption_type(device) if device.is_a?(Planned::Device) - device.respond_to?(:encrypt?) && device.encrypt? - else - device.plain_blk_devices.any? { |d| d.respond_to?(:encrypted?) && d.encrypted? } - end + (device.respond_to?(:encrypt?) && device.encrypt?) ? Y2Storage::EncryptionType::LUKS1 : nil + elsif device.respond_to?(:plain_blk_devices) + device.plain_blk_devices.map { |d| d.encryption&.type }.compact.first + end || Y2Storage::EncryptionType::NONE end # Whether the device is in a software RAID diff --git a/src/lib/y2storage/boot_requirements_strategies/base.rb b/src/lib/y2storage/boot_requirements_strategies/base.rb index bcd12624d6..187578cb14 100644 --- a/src/lib/y2storage/boot_requirements_strategies/base.rb +++ b/src/lib/y2storage/boot_requirements_strategies/base.rb @@ -45,7 +45,7 @@ class Base :root_in_lvm?, :root_in_software_raid?, :encrypted_root?, :btrfs_root?, :root_fs_can_embed_grub?, :boot_in_lvm?, :boot_in_thin_lvm?, :boot_in_bcache?, :boot_in_software_raid?, :encrypted_boot?, - :boot_fs_can_embed_grub?, :boot_filesystem_type, :boot_can_embed_grub?, + :boot_fs_can_embed_grub?, :boot_filesystem_type, :boot_encryption_type, :esp_in_lvm?, :esp_in_software_raid?, :esp_in_software_raid1?, :encrypted_esp? # Constructor @@ -81,7 +81,18 @@ def needed_partitions(target) # # @return [Array] def warnings - [] + res = [] + + if !encrypted_for_grub? + error_message = + _( + "The boot loader cannot access the file system mounted at /boot. " \ + "Only LUKS1 encryption is supported." + ) + res << SetupError.new(message: error_message) + end + + res end # All fatal boot errors detected in the setup, for example, when a / partition @@ -136,7 +147,7 @@ def root_filesystem_missing? end def boot_partition_needed? - false + !encrypted_for_grub? end def too_small_boot? @@ -201,6 +212,14 @@ def unknown_boot_disk_error error_message = _("Boot requirements cannot be determined because there is no '/' mount point") SetupError.new(message: error_message) end + + # Whether the boot device is encrypted and grub can decrypt it + # + # @return [Boolean] true if grub can decrypt boot device (or it is unencrypted) + def encrypted_for_grub? + t = boot_encryption_type + t.is?(:none) || t.is?(:luks1) + end end end end diff --git a/src/lib/y2storage/boot_requirements_strategies/legacy.rb b/src/lib/y2storage/boot_requirements_strategies/legacy.rb index 1299c1de73..6167678bb2 100644 --- a/src/lib/y2storage/boot_requirements_strategies/legacy.rb +++ b/src/lib/y2storage/boot_requirements_strategies/legacy.rb @@ -154,7 +154,7 @@ def mbr_gap_for_grub? # # @return [Boolean] true if a separate boot partition is needed, else false def boot_partition_needed? - boot_ptable_type?(:msdos) && !mbr_gap_for_grub? && !root_can_embed_grub? + super || (boot_ptable_type?(:msdos) && !mbr_gap_for_grub? && !root_can_embed_grub?) end # @return [VolumeSpecification] diff --git a/src/lib/y2storage/boot_requirements_strategies/prep.rb b/src/lib/y2storage/boot_requirements_strategies/prep.rb index 902181677d..dfe88f7621 100644 --- a/src/lib/y2storage/boot_requirements_strategies/prep.rb +++ b/src/lib/y2storage/boot_requirements_strategies/prep.rb @@ -98,7 +98,7 @@ def boot_partition_needed? # We cannot ensure the mentioned firmware can handle technologies like # LVM, MD or LUKS, so propose a separate /boot partition for those cases - root_in_lvm? || root_in_software_raid? || encrypted_root? + super || (root_in_lvm? || root_in_software_raid? || encrypted_root?) end def prep_partition_needed? diff --git a/test/support/boot_requirements_context.rb b/test/support/boot_requirements_context.rb index fed6a2269c..956f243b2c 100755 --- a/test/support/boot_requirements_context.rb +++ b/test/support/boot_requirements_context.rb @@ -63,7 +63,8 @@ def find_vol(mount_point, volumes) esp_in_lvm?: false, esp_in_software_raid?: false, esp_in_software_raid1?: false, - encrypted_esp?: false + encrypted_esp?: false, + boot_encryption_type: boot_enc_type ) end @@ -78,6 +79,7 @@ def find_vol(mount_point, volumes) use_btrfs ? Y2Storage::Filesystems::Type::BTRFS : Y2Storage::Filesystems::Type::EXT4 end let(:boot_ptable_type) { :msdos } + let(:boot_enc_type) { Y2Storage::EncryptionType::NONE } # Mocks for Raspberry Pi detection let(:raspi_system) { false } diff --git a/test/y2storage/boot_requirements_checker_ppc_test.rb b/test/y2storage/boot_requirements_checker_ppc_test.rb index d087540956..ae966eab37 100755 --- a/test/y2storage/boot_requirements_checker_ppc_test.rb +++ b/test/y2storage/boot_requirements_checker_ppc_test.rb @@ -83,6 +83,44 @@ end end + RSpec.shared_examples "PReP + /boot partition" do + context "if there are no suitable PReP partitions in the target disk" do + let(:prep_partitions) { [] } + + it "requires a new PReP and a new /boot partition" do + expect(checker.needed_partitions).to contain_exactly( + an_object_having_attributes(mount_point: nil, partition_id: prep_id), + an_object_having_attributes(mount_point: "/boot") + ) + end + end + + context "if there is already a suitable PReP partition in the disk" do + let(:prep_partitions) { [prep_partition] } + + context "and it is on the boot disk" do + let(:boot_disk) { dev_sda } + + it "requires a /boot partition" do + expect(checker.needed_partitions).to contain_exactly( + an_object_having_attributes(mount_point: "/boot") + ) + end + end + + context "and it is not on the boot disk" do + let(:boot_disk) { dev_sdb } + + it "requires a new PReP and a new /boot partition" do + expect(checker.needed_partitions).to contain_exactly( + an_object_having_attributes(mount_point: nil, partition_id: prep_id), + an_object_having_attributes(mount_point: "/boot") + ) + end + end + end + end + context "in a non-PowerNV system (KVM/LPAR)" do let(:power_nv) { false } @@ -101,6 +139,14 @@ let(:use_encryption) { true } include_examples "PReP partition" end + + context "with an encrypted proposal using LUKS2" do + let(:use_lvm) { false } + let(:use_encryption) { true } + let(:boot_enc_type) { Y2Storage::EncryptionType::LUKS2 } + + include_examples "PReP + /boot partition" + end end context "in bare metal (PowerNV)" do diff --git a/test/y2storage/boot_requirements_checker_x86_test.rb b/test/y2storage/boot_requirements_checker_x86_test.rb index 325887b759..5a2f1a371a 100755 --- a/test/y2storage/boot_requirements_checker_x86_test.rb +++ b/test/y2storage/boot_requirements_checker_x86_test.rb @@ -159,6 +159,14 @@ include_examples("needs no volume") end + + context "in an encrypted proposal using LUKS2" do + let(:use_lvm) { false } + let(:use_encryption) { true } + let(:boot_enc_type) { Y2Storage::EncryptionType::LUKS2 } + + include_examples("needs /boot partition") + end end context "with a MBR gap too small to accommodate Grub" do diff --git a/test/y2storage/boot_requirements_errors_test.rb b/test/y2storage/boot_requirements_errors_test.rb index 142c77683f..86b92770c2 100755 --- a/test/y2storage/boot_requirements_errors_test.rb +++ b/test/y2storage/boot_requirements_errors_test.rb @@ -36,6 +36,9 @@ .and_return(use_thin_lvm) allow_any_instance_of(Y2Storage::BootRequirementsStrategies::Analyzer).to receive(:boot_in_bcache?) .and_return(use_bcache) + allow_any_instance_of(Y2Storage::BootRequirementsStrategies::Analyzer) + .to receive(:boot_encryption_type) + .and_return(enc_type) end let(:storage_arch) { instance_double(Storage::Arch) } @@ -44,6 +47,7 @@ let(:efiboot) { false } let(:use_thin_lvm) { false } let(:use_bcache) { false } + let(:enc_type) { Y2Storage::EncryptionType::NONE } let(:scenario) { "trivial" } @@ -302,6 +306,23 @@ def format_zipl(name) end end + context "when /boot is encrypted" do + context "and grub can decrypt it" do + let(:enc_type) { Y2Storage::EncryptionType::LUKS1 } + it "does not contain any warning" do + expect(checker.warnings).to be_empty + end + end + + context "and grub cannot decrypt it" do + let(:enc_type) { Y2Storage::EncryptionType::LUKS2 } + it "shows a warning that grub cannot access /boot" do + expect(checker.warnings.size).to eq(1) + expect(checker.warnings.first.message).to match(/cannot access/) + end + end + end + context "in a x86 system" do let(:architecture) { :x86 } diff --git a/test/y2storage/boot_requirements_strategies/analyzer_test.rb b/test/y2storage/boot_requirements_strategies/analyzer_test.rb index d277621629..54df421f3c 100755 --- a/test/y2storage/boot_requirements_strategies/analyzer_test.rb +++ b/test/y2storage/boot_requirements_strategies/analyzer_test.rb @@ -767,4 +767,62 @@ def create_filesystem(device_name, mount_point) end end end + + describe "#boot_encryption_type" do + subject(:analyzer) { described_class.new(devicegraph, planned_devs, boot_name) } + + context "if '/boot' is a planned plain partition" do + let(:planned_boot) { planned_partition(mount_point: "/boot") } + + it "returns type none" do + expect(analyzer.boot_encryption_type).to eq Y2Storage::EncryptionType::NONE + end + end + + context "if '/boot' is a planned encrypted partition" do + let(:planned_boot) { planned_partition(mount_point: "/boot", encryption_password: "12345678") } + + it "returns type luks1" do + expect(analyzer.boot_encryption_type).to eq Y2Storage::EncryptionType::LUKS1 + end + end + + context "if '/boot' is a planned encrypted logical volume" do + let(:planned_boot) { planned_lv(mount_point: "/boot", encryption_password: "12345678") } + + it "returns type luks1" do + expect(analyzer.boot_encryption_type).to eq Y2Storage::EncryptionType::LUKS1 + end + end + + context "if '/boot' is a plain partition from the devicegraph" do + let(:planned_devs) { [] } + let(:scenario) { "mixed_disks" } + + it "returns type none" do + expect(analyzer.boot_encryption_type).to eq Y2Storage::EncryptionType::NONE + end + end + + context "if '/boot' is an encrypted partition from the devicegraph with default encryption" do + let(:planned_devs) { [] } + let(:scenario) { "output/empty_hard_disk_gpt_50GiB-enc" } + + it "returns type luks1" do + expect(analyzer.boot_encryption_type).to eq Y2Storage::EncryptionType::LUKS1 + end + end + + context "if '/boot' is an encrypted partition with encryption type luks2" do + let(:planned_devs) { [] } + let(:scenario) { "output/empty_hard_disk_gpt_50GiB-enc" } + before do + fake_devicegraph.find_by_name("/dev/sda2").encryption.type = Y2Storage::EncryptionType::LUKS2 + end + + it "returns type luks2" do + expect(analyzer.boot_encryption_type).to eq Y2Storage::EncryptionType::LUKS2 + end + end + end end