From 0a203330cee4fba6a28c65f1c6e0e450cc45771e Mon Sep 17 00:00:00 2001 From: Michal Hecko Date: Thu, 30 Oct 2025 11:52:39 +0100 Subject: [PATCH 48/55] mount_unit_gen: bind mount /sysroot/boot to /boot Our changes towards using systemd-fstab-generator in the upgrade initramfs caused that we are mounting almost all partitions, including /boot (the actual mount target is /sysroot/boot) early in the boot process. When upgrading with FIPS, the dracut fips module tries to mount the device where the boot partition resides to check the integrity of the kernel, however, it fails as the boot block device is already mounted by us. This patch therefore introduces a static unit that bind-mounts What=/sysroot/boot to Where=/boot, making the contents of /boot available to the fips module. The bind-mounting service is introduced only if the source system has /boot on a separate partition. This is determined by checking whether anything shuld be mounted at /boot according to fstab. Jira-ref: RHEL-123886 --- .../initramfs/mount_units_generator/actor.py | 3 +- .../files/bundled_units/boot.mount | 11 ++++ .../libraries/mount_unit_generator.py | 38 ++++++++++- .../tests/test_mount_unit_generation.py | 63 ++++++++++++++++++- 4 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py index dd667513..23c618b6 100644 --- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py +++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py @@ -1,6 +1,6 @@ from leapp.actors import Actor from leapp.libraries.actor import mount_unit_generator as mount_unit_generator_lib -from leapp.models import LiveModeConfig, TargetUserSpaceInfo, UpgradeInitramfsTasks +from leapp.models import LiveModeConfig, StorageInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks from leapp.tags import InterimPreparationPhaseTag, IPUWorkflowTag @@ -15,6 +15,7 @@ class MountUnitGenerator(Actor): consumes = ( LiveModeConfig, TargetUserSpaceInfo, + StorageInfo, ) produces = ( UpgradeInitramfsTasks, diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount new file mode 100644 index 00000000..869c5e4c --- /dev/null +++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount @@ -0,0 +1,11 @@ +[Unit] +DefaultDependencies=no +Before=local-fs.target +After=sysroot-boot.target +Requires=sysroot-boot.target + +[Mount] +What=/sysroot/boot +Where=/boot +Type=none +Options=bind diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py index 943bddd4..e3070986 100644 --- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py +++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py @@ -5,7 +5,9 @@ import tempfile from leapp.exceptions import StopActorExecutionError from leapp.libraries.common import mounting from leapp.libraries.stdlib import api, CalledProcessError, run -from leapp.models import LiveModeConfig, TargetUserSpaceInfo, UpgradeInitramfsTasks +from leapp.models import LiveModeConfig, StorageInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks + +BIND_MOUNT_SYSROOT_BOOT_UNIT = 'boot.mount' def run_systemd_fstab_generator(output_directory): @@ -294,6 +296,39 @@ def request_units_inclusion_in_initramfs(files_to_include): api.produce(tasks) +def does_system_have_separate_boot_partition(): + storage_info = next(api.consume(StorageInfo), None) + if not storage_info: + err_msg = 'Actor did not receive required information about system storage (StorageInfo)' + raise StopActorExecutionError(err_msg) + + for fstab_entry in storage_info.fstab: + if fstab_entry.fs_file == '/boot': + return True + + return False + + +def inject_bundled_units(workspace): + """ + Copy static units that are bundled within this actor into the workspace. + """ + bundled_units_dir = api.get_actor_folder_path('bundled_units') + for unit in os.listdir(bundled_units_dir): + if unit == BIND_MOUNT_SYSROOT_BOOT_UNIT: + has_separate_boot = does_system_have_separate_boot_partition() + if not has_separate_boot: + # We perform bind-mounting because of dracut's fips module. + # When /boot is not a separate partition, we don't need to bind mount it -- + # the fips module itself will create a symlink. + continue + + unit_path = os.path.join(bundled_units_dir, unit) + unit_dst = os.path.join(workspace, unit) + api.current_logger().debug('Copying static unit bundled within leapp {} to {}'.format(unit, unit_dst)) + shutil.copyfile(unit_path, unit_dst) + + def setup_storage_initialization(): livemode_config = next(api.consume(LiveModeConfig), None) if livemode_config and livemode_config.is_enabled: @@ -306,6 +341,7 @@ def setup_storage_initialization(): run_systemd_fstab_generator(workspace_path) remove_units_for_targets_that_are_already_mounted_by_dracut(workspace_path) prefix_all_mount_units_with_sysroot(workspace_path) + inject_bundled_units(workspace_path) fix_symlinks_in_targets(workspace_path) mount_unit_files = copy_units_into_system_location(upgrade_container_ctx, workspace_path) request_units_inclusion_in_initramfs(mount_unit_files) diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py index 8849ada9..eb90a75d 100644 --- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py +++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py @@ -5,9 +5,9 @@ import pytest from leapp.exceptions import StopActorExecutionError from leapp.libraries.actor import mount_unit_generator -from leapp.libraries.common.testutils import logger_mocked +from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked from leapp.libraries.stdlib import api, CalledProcessError -from leapp.models import TargetUserSpaceInfo, UpgradeInitramfsTasks +from leapp.models import FstabEntry, StorageInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks def test_run_systemd_fstab_generator_successful_generation(monkeypatch): @@ -267,3 +267,62 @@ def test_copy_units_mixed_content(monkeypatch): ] assert sorted(files) == sorted(expected_files) assert mount_unit_generator._delete_file.removal_called + + +class CurrentActorMockedWithActorFolder(CurrentActorMocked): + def __init__(self, actor_folder_path, *args, **kwargs): + self.actor_folder_path = actor_folder_path + super().__init__(*args, **kwargs) + + def get_actor_folder_path(self, subfolder): + return os.path.join(self.actor_folder_path, subfolder) + + +@pytest.mark.parametrize('has_separate_boot', (True, False)) +def test_injection_of_sysroot_boot_bindmount_unit(monkeypatch, has_separate_boot): + fstab_entries = [ + FstabEntry(fs_spec='UUID=123', fs_file='/root', fs_vfstype='xfs', + fs_mntops='defaults', fs_freq='0', fs_passno='0') + ] + + if has_separate_boot: + boot_fstab_entry = FstabEntry(fs_spec='UUID=123', fs_file='/root', fs_vfstype='xfs', + fs_mntops='defaults', fs_freq='0', fs_passno='0') + fstab_entries.append(boot_fstab_entry) + + storage_info = StorageInfo(fstab=fstab_entries) + + actor_mock = CurrentActorMockedWithActorFolder(actor_folder_path='/actor', msgs=[storage_info]) + monkeypatch.setattr(api, 'current_actor', actor_mock) + + workspace_path = '/workspace' + was_copyfile_for_sysroot_boot_called = False + + def copyfile_mocked(source, dest, *args, **kwargs): + if not os.path.basename(source) == mount_unit_generator.BIND_MOUNT_SYSROOT_BOOT_UNIT: + return + + assert has_separate_boot + assert dest == os.path.join(workspace_path, mount_unit_generator.BIND_MOUNT_SYSROOT_BOOT_UNIT) + + nonlocal was_copyfile_for_sysroot_boot_called + was_copyfile_for_sysroot_boot_called = True + + monkeypatch.setattr(shutil, 'copyfile', copyfile_mocked) + + def listdir_mocked(path): + assert path == actor_mock.get_actor_folder_path('bundled_units') + return [ + mount_unit_generator.BIND_MOUNT_SYSROOT_BOOT_UNIT, + 'other.mount' + ] + + monkeypatch.setattr(os, 'listdir', listdir_mocked) + monkeypatch.setattr(mount_unit_generator, + 'does_system_have_separate_boot_partition', + lambda: has_separate_boot) + + mount_unit_generator.inject_bundled_units(workspace_path) + + if has_separate_boot: + assert was_copyfile_for_sysroot_boot_called -- 2.51.1