From 3edb1c97f4519e163bd25d3f9856198f2a8c4e31 Mon Sep 17 00:00:00 2001 From: Ancor Gonzalez Sosa Date: Fri, 6 Oct 2023 16:56:46 +0200 Subject: [PATCH] Encryption processes can request packages to install --- src/lib/y2partitioner/widgets/overview.rb | 6 +- src/lib/y2storage/devicegraph.rb | 32 ++++- src/lib/y2storage/encryption.rb | 8 ++ .../y2storage/encryption_method/tpm_fde.rb | 6 +- .../y2storage/encryption_processes/base.rb | 12 +- .../encryption_processes/pervasive.rb | 4 +- .../encryption_processes/tpm_fde_tools.rb | 7 + src/lib/y2storage/feature.rb | 123 ++++++++++++++++++ src/lib/y2storage/storage_feature.rb | 86 +----------- src/lib/y2storage/storage_features_list.rb | 10 +- src/lib/y2storage/yast_feature.rb | 61 +++++++++ test/y2storage/devicegraph_test.rb | 18 ++- test/y2storage/storage_features_list_test.rb | 17 ++- test/y2storage/yast_feature_test.rb | 34 +++++ 14 files changed, 329 insertions(+), 95 deletions(-) create mode 100644 src/lib/y2storage/feature.rb create mode 100644 src/lib/y2storage/yast_feature.rb create mode 100755 test/y2storage/yast_feature_test.rb diff --git a/src/lib/y2partitioner/widgets/overview.rb b/src/lib/y2partitioner/widgets/overview.rb index a3493e6b19..307d7b2341 100644 --- a/src/lib/y2partitioner/widgets/overview.rb +++ b/src/lib/y2partitioner/widgets/overview.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2017-2022] SUSE LLC +# Copyright (c) [2017-2023] SUSE LLC # # All Rights Reserved. # @@ -215,7 +215,9 @@ def valid_setup? def packages_installed? return true if Yast::Mode.installation - pkgs = device_graph.actiongraph.used_features.pkg_list + features = device_graph.actiongraph.used_features + features.concat(device_graph.yast_commit_features) + pkgs = features.pkg_list Y2Storage::PackageHandler.new(pkgs).install end diff --git a/src/lib/y2storage/devicegraph.rb b/src/lib/y2storage/devicegraph.rb index aca8f3d727..d163a8de89 100644 --- a/src/lib/y2storage/devicegraph.rb +++ b/src/lib/y2storage/devicegraph.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2017-2021] SUSE LLC +# Copyright (c) [2017-2023] SUSE LLC # # All Rights Reserved. # @@ -594,12 +594,19 @@ def finish_installation # List of storage features used by the devicegraph # + # Note this is used during system installation. In the installed system, the + # combination of Actiongraph#used_features and Devicegraph#yast_commit_features + # is used instead. + # # By default, it returns the features associated to all devices and filesystems # in the devicegraph. The required_only argument can be used to limit the result - # by excluding features associated to those filesystems that have no mount point. + # by excluding features that are not mandatory to produce a functional system. For + # example, it excludes features associated to those filesystems that have no mount + # point. # # @param required_only [Boolean] whether the result should only include those - # features that are mandatory (ie. associated to devices with a mount point) + # features that are mandatory (ie. associated to devices with a mount point or + # to devices that will be configured during the first boot of the new system) # @return [StorageFeaturesList] def used_features(required_only: false) type = @@ -609,7 +616,9 @@ def used_features(required_only: false) Storage::UsedFeaturesDependencyType_SUGGESTED end - StorageFeaturesList.from_bitfield(storage_used_features(type)) + list = StorageFeaturesList.from_bitfield(storage_used_features(type)) + list.concat(yast_commit_features) + list end # List of required (mandatory) storage features used by the devicegraph @@ -626,7 +635,20 @@ def optional_used_features all = storage_used_features(Storage::UsedFeaturesDependencyType_SUGGESTED) required = storage_used_features(Storage::UsedFeaturesDependencyType_REQUIRED) # Using binary XOR in those bit fields to calculate the difference - StorageFeaturesList.from_bitfield(all ^ required) + list = StorageFeaturesList.from_bitfield(all ^ required) + list.concat(yast_commit_features) + list + end + + # List of features that correspond to aspects handled by Y2Storage (not coming + # from libstorage-ng) and that need to be present in the target system either during + # the storage commit phase or at a later stage. Ie. features needed in the target + # system to access the device or to finish its configuration. + # + # @return [StorageFeaturesList] + def yast_commit_features + features = encryptions.flat_map(&:commit_features).uniq + StorageFeaturesList.new(features) end private diff --git a/src/lib/y2storage/encryption.rb b/src/lib/y2storage/encryption.rb index ba88de1ec4..ada6766370 100644 --- a/src/lib/y2storage/encryption.rb +++ b/src/lib/y2storage/encryption.rb @@ -362,6 +362,14 @@ def finish_installation encryption_process&.finish_installation end + # Features that must be supported in the target system to finish the encryption + # process + # + # @return [Array] + def commit_features + encryption_process&.commit_features || [] + end + # If the current mount_by is suitable, it does nothing. # # Otherwise, it assigns the best option from all the suitable ones diff --git a/src/lib/y2storage/encryption_method/tpm_fde.rb b/src/lib/y2storage/encryption_method/tpm_fde.rb index 0ecd7b1913..f37b02b763 100644 --- a/src/lib/y2storage/encryption_method/tpm_fde.rb +++ b/src/lib/y2storage/encryption_method/tpm_fde.rb @@ -20,6 +20,7 @@ require "yast" require "y2storage/encryption_method/base" +require "y2storage/yast_feature" require "y2storage/encryption_processes/tpm_fde_tools" Yast.import "Mode" @@ -97,16 +98,13 @@ def tpm_present? @tpm_present = EncryptionProcesses::FdeTools.new.tpm_present? end - NEEDED_PACKAGES = ["fde-tools"].freeze - private_constant :NEEDED_PACKAGES - def tpm_product? # FIXME: Can we detect when to invalidate this memoization (new packages available due # to some change in selected product or to new repositories)? return @tpm_product unless @tpm_product.nil? # Beware: apart from true and false, AvailableAll can return nil if things go wrong - @tpm_product = Yast::Package.AvailableAll(NEEDED_PACKAGES.pkg_list) + @tpm_product = Yast::Package.AvailableAll(YastFeature::ENCRYPTION_TPM_FDE.pkg_list) end end end diff --git a/src/lib/y2storage/encryption_processes/base.rb b/src/lib/y2storage/encryption_processes/base.rb index affe9de4c0..0a9c2c8a4f 100644 --- a/src/lib/y2storage/encryption_processes/base.rb +++ b/src/lib/y2storage/encryption_processes/base.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2019] SUSE LLC +# Copyright (c) [2019-2023] SUSE LLC # # All Rights Reserved. # @@ -18,6 +18,7 @@ # find current contact information at www.suse.com. require "yast" +require "y2storage/yast_feature" require "abstract_method" @@ -93,6 +94,15 @@ def crypt_options(_blk_device) [] end + # Features objects to describe the requirements to perform the commit phase + # and any subsequent operation (eg., initialization during the first boot) of + # the encryption procedure + # + # @return [Array] + def commit_features + [] + end + private # Open options with the format expected by the underlying tools (cryptsetup) diff --git a/src/lib/y2storage/encryption_processes/pervasive.rb b/src/lib/y2storage/encryption_processes/pervasive.rb index 69a5fc8636..791072f455 100644 --- a/src/lib/y2storage/encryption_processes/pervasive.rb +++ b/src/lib/y2storage/encryption_processes/pervasive.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2019-2020] SUSE LLC +# Copyright (c) [2019-2023] SUSE LLC # # All Rights Reserved. # @@ -23,6 +23,8 @@ require "yast2/execute" require "yast" +Yast.import "Mode" + module Y2Storage module EncryptionProcesses # Encryption process that allows to create and identify a volume encrypted diff --git a/src/lib/y2storage/encryption_processes/tpm_fde_tools.rb b/src/lib/y2storage/encryption_processes/tpm_fde_tools.rb index af7aea6333..86d656279f 100644 --- a/src/lib/y2storage/encryption_processes/tpm_fde_tools.rb +++ b/src/lib/y2storage/encryption_processes/tpm_fde_tools.rb @@ -95,6 +95,13 @@ def finish_installation self.class.devices = [] end + # @see Base#commit_features + def commit_features + # In installation mode is needed to ensure the enroll service is present in the new system. + # In an installed system is needed in order to be able to execute the fdectl commands. + [YastFeature::ENCRYPTION_TPM_FDE] + end + private def configure_fde_tools(devices) diff --git a/src/lib/y2storage/feature.rb b/src/lib/y2storage/feature.rb new file mode 100644 index 0000000000..6fd28aa95a --- /dev/null +++ b/src/lib/y2storage/feature.rb @@ -0,0 +1,123 @@ +# Copyright (c) [2023] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "yast" +require "storage" + +module Y2Storage + # Generalization of the concept of {StorageFeature}. + # + # In libstorage-ng the concept of "feature" is used to communicate the usage of some + # functionality that may require the presence in the system of some packages and tools. + # + # The sibling concept of {YastFeature} makes it possible for Y2Storage to add its own + # requirements. + # + # This is the abstract base class for both. + class Feature + include Yast::Logger + + # Constructor + # + # @param id [Symbol] see {#id} + # @param packages [Array] see {#all_packages} + def initialize(id, packages) + @id = id + @all_packages = packages + end + + # Symbol representation of the feature + # + # For StorageFeature objects, this has the same form than the corresponding constant + # name in libstorage-ng, eg. :UF_NTFS + # + # @return [Symbol] + attr_reader :id + + alias_method :to_sym, :id + + # Names of the packages that should be installed if the feature is going to be used + # + # @return [Array] + def pkg_list + packages.map(&:name) + end + + # Drop the cache about which packages related to the feature are available + def drop_cache + @packages = nil + end + + private + + # All packages that would be relevant for the feature, no matter if they are really available + # @return [Array] + attr_reader :all_packages + + # List of available packages associated to the feature + # + # @return [Array] + def packages + return @packages unless @packages.nil? + + unavailable, @packages = all_packages.partition(&:unavailable_optional?) + if unavailable.any? + log.warn("WARNING: Skipping unavailable support packages #{unavailable.map(&:name)}") + end + + @packages + end + + # Internal class to represent a package associated to a feature + class Package + Yast.import "Package" + + # Constructor + # + # @param name [String] see {#name} + # @param optional [Boolean] see {#optional?} + def initialize(name, optional: false) + @name = name + @optional = optional + end + + # @return [String] name of the package + attr_reader :name + + # Whether installation of the package can be skipped if the package is not + # available + # + # See the comment in {StorageFeature::OPTIONAL_PACKAGES} for more details + # + # @return [Boolean] + def optional? + !!@optional + end + + # Check if a package is an optional package that is unavailable. + # See also bsc#1039830 + # + # @return [Boolean] true if package is optional and unavailable, + # false if not optional or if available. + def unavailable_optional? + optional? && !Yast::Package.Available(name) + end + end + end +end diff --git a/src/lib/y2storage/storage_feature.rb b/src/lib/y2storage/storage_feature.rb index a7063d3e10..565671f22a 100644 --- a/src/lib/y2storage/storage_feature.rb +++ b/src/lib/y2storage/storage_feature.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2016-2020] SUSE LLC +# Copyright (c) [2016-2023] SUSE LLC # # All Rights Reserved. # @@ -19,6 +19,7 @@ require "yast" require "storage" +require "y2storage/feature" module Y2Storage # @@ -38,9 +39,7 @@ module Y2Storage # which add-on packages would be needed so support that feature, so they # can be installed or marked for installation as needed. # - class StorageFeature - include Yast::Logger - + class StorageFeature < Feature #====================================================================== # Configurable part starts here # @@ -114,7 +113,7 @@ class StorageFeature # configurable part ends here #====================================================================== - # All known features + # All known libstorage-ng features # # @return [Array] def self.all @@ -126,12 +125,12 @@ def self.all end end - # Drop the cache of storage packages that are available. + # Drop the cache of packages for all known storage features # # This is only ever needed if the available packages might have changed # since the last use of this class. def self.drop_cache - @all = nil + all.each(&:drop_cache) end # Constructor @@ -144,8 +143,7 @@ def self.drop_cache # @param id [Symbol] see {#id} # @param packages [Array] see {#all_packages} def initialize(id, packages) - @id = id - @all_packages = packages + super # Raising a NameError exception as soon as possible (i.e. in the constructor) # is a good way to make sure we are in sync with libstorage-ng regarding @@ -153,16 +151,6 @@ def initialize(id, packages) @bitmask = ::Storage.const_get(id) end - # Symbol representation of the feature - # - # It has the same form than the corresponding constant name in - # libstorage-ng, eg. :UF_NTFS - # - # @return [Symbol] - attr_reader :id - - alias_method :to_sym, :id - # Whether the feature is included in the given bit-field # # @param bitfield [Integer] @@ -171,18 +159,8 @@ def in_bitfield?(bitfield) (bitfield & bitmask) == bitmask end - # Names of the packages that should be installed if the feature is going to - # be used - # - # @return [Array] - def pkg_list - packages.map(&:name) - end - private - attr_reader :all_packages - # Bitmask for a storage feature # # This looks up a constant in the ::Storage (libstorage-ng) namespace with @@ -190,55 +168,5 @@ def pkg_list # # @return [Integer] attr_reader :bitmask - - # List of packages associated to the feature - # - # @return [Array] - def packages - return @packages unless @packages.nil? - - unavailable, @packages = all_packages.partition(&:unavailable_optional?) - if unavailable.any? - log.warn("WARNING: Skipping unavailable filesystem support packages #{unavailable.map(&:name)}") - end - - @packages - end - - # Internal class to represent a package associated to a storage feature - class Package - Yast.import "Package" - - # Constructor - # - # @param name [String] see {#name} - # @param optional [Boolean] see {#optional?} - def initialize(name, optional: false) - @name = name - @optional = optional - end - - # @return [String] name of the package - attr_reader :name - - # Whether installation of the package can be skipped if the package is not - # available - # - # See the comment in {StorageFeature::OPTIONAL_PACKAGES} for more details - # - # @return [Boolean] - def optional? - !!@optional - end - - # Check if a package is an optional package that is unavailable. - # See also bsc#1039830 - # - # @return [Boolean] true if package is optional and unavailable, - # false if not optional or if available. - def unavailable_optional? - optional? && !Yast::Package.Available(name) - end - end end end diff --git a/src/lib/y2storage/storage_features_list.rb b/src/lib/y2storage/storage_features_list.rb index 65220e21f8..5636783aba 100644 --- a/src/lib/y2storage/storage_features_list.rb +++ b/src/lib/y2storage/storage_features_list.rb @@ -1,4 +1,4 @@ -# Copyright (c) [2016-2017,2019-2020] SUSE LLC +# Copyright (c) [2016-2023] SUSE LLC # # All Rights Reserved. # @@ -20,6 +20,7 @@ require "yast" require "forwardable" require "y2storage/storage_feature" +require "y2storage/yast_feature" module Y2Storage # List of storage features @@ -70,5 +71,12 @@ def pkg_list @pkg_list end + + # Concatenate the give features into the current list + # + # @param other_list [#to_a] + def concat(other_list) + @features.concat(other_list.to_a) + end end end diff --git a/src/lib/y2storage/yast_feature.rb b/src/lib/y2storage/yast_feature.rb new file mode 100644 index 0000000000..904cfa27c2 --- /dev/null +++ b/src/lib/y2storage/yast_feature.rb @@ -0,0 +1,61 @@ +# Copyright (c) [2023] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require "yast" +require "y2storage/feature" + +module Y2Storage + # Analogous to {StorageFeature}, but for requirements originated by Y2Storage and not + # from libstorage-ng. + class YastFeature < Feature + # Constructor + def initialize(id, mandatory_pkgs, optional_pkgs) + pkgs = mandatory_pkgs.map { |pkg| Package.new(pkg, optional: false) } + pkgs.concat(optional_pkgs.map { |pkg| Package.new(pkg, optional: true) }) + super(id, pkgs) + end + + # Instance of the feature to be always returned by the class + ENCRYPTION_TPM_FDE = new(:encryption_tpm_fde, ["fde-tools"], []) + + # All possible instances + ALL = [ENCRYPTION_TPM_FDE].freeze + private_constant :ALL + + # Sorted list of all features defined by YaST + def self.all + ALL.dup + end + + # Drop the cache of packages for all known YaST features + # + # This is only ever needed if the available packages might have changed + # since the last use of this class. + def self.drop_cache + all.each(&:drop_cache) + end + + # @return [Boolean] + def ==(other) + other.class == self.class && other.id == id + end + + alias_method :eql?, :== + end +end diff --git a/test/y2storage/devicegraph_test.rb b/test/y2storage/devicegraph_test.rb index e31f815c2c..1efbe06107 100755 --- a/test/y2storage/devicegraph_test.rb +++ b/test/y2storage/devicegraph_test.rb @@ -1,6 +1,6 @@ #!/usr/bin/env rspec -# Copyright (c) [2017-2021] SUSE LLC +# Copyright (c) [2017-2023] SUSE LLC # # All Rights Reserved. # @@ -1307,6 +1307,22 @@ def with_sda2_deleted(initial_graph) expect(features.map(&:id)) .to contain_exactly(:UF_BTRFS, :UF_XFS, :UF_SWAP) end + + context "if YaST needs some extra feature to configure a device" do + before do + sda2 = fake_devicegraph.find_by_name("/dev/sda2") + sda2.encrypt(method: Y2Storage::EncryptionMethod::TPM_FDE) + end + + it "returns the expected set of features" do + features = fake_devicegraph.used_features + expect(features).to be_a Y2Storage::StorageFeaturesList + expect(features.map(&:id)) + .to contain_exactly( + :UF_BTRFS, :UF_EXT4, :UF_NTFS, :UF_XFS, :UF_SWAP, :UF_LUKS, :encryption_tpm_fde + ) + end + end end context "with unformatted DASD and FC devices" do diff --git a/test/y2storage/storage_features_list_test.rb b/test/y2storage/storage_features_list_test.rb index 761b652be7..069ff2db78 100755 --- a/test/y2storage/storage_features_list_test.rb +++ b/test/y2storage/storage_features_list_test.rb @@ -2,7 +2,7 @@ # # encoding: utf-8 -# Copyright (c) [2017,2019-2020] SUSE LLC +# Copyright (c) [2017-2023] SUSE LLC # # All Rights Reserved. # @@ -110,5 +110,20 @@ expect(list.pkg_list).to eq [] end end + + context "when the list includes both storage and YaST features" do + let(:bits) { Storage::UF_LUKS } + + before do + # Not really needed yet since we have no optional packages in any YaST feature + Y2Storage::YastFeature.drop_cache + + list.concat(Y2Storage::YastFeature.all) + end + + it "includes the packages related to both kind of features" do + expect(list.pkg_list).to include("device-mapper", "cryptsetup", "fde-tools") + end + end end end diff --git a/test/y2storage/yast_feature_test.rb b/test/y2storage/yast_feature_test.rb new file mode 100755 index 0000000000..a09a18787c --- /dev/null +++ b/test/y2storage/yast_feature_test.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env rspec +# +# encoding: utf-8 + +# Copyright (c) [2023] SUSE LLC +# +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of version 2 of the GNU General Public License as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, contact SUSE LLC. +# +# To contact SUSE LLC about this file by physical or electronic mail, you may +# find current contact information at www.suse.com. + +require_relative "spec_helper" +require "y2storage/yast_feature" + +describe Y2Storage::YastFeature do + describe "eql?" do + # Pretty trivial test now that we only have one feature, to be improved in the future + it "returns true when comparing the same feature" do + expect(Y2Storage::YastFeature.all.first).to eq Y2Storage::YastFeature::ENCRYPTION_TPM_FDE + end + end +end