leapp-repository/SOURCES/0005-IPU-8-9-Migrate-blackl...

210 lines
8.2 KiB
Diff

From eeb4f99f57c67937ea562fce11fd5607470ae0a6 Mon Sep 17 00:00:00 2001
From: Petr Stodulka <pstodulk@redhat.com>
Date: Fri, 22 Apr 2022 00:20:15 +0200
Subject: [PATCH] [IPU 8 -> 9] Migrate blacklisted CAs (hotfix)
Preserve blacklisted certificates during the IPU 8 -> 9
Path for the blacklisted certificates has been changed on RHEL 9.
The original paths on RHEL 8 and older systems have been:
/etc/pki/ca-trust/source/blacklist/
/usr/share/pki/ca-trust-source/blacklist/
However on RHEL 9 the blacklist directory has been renamed to 'blocklist'.
So the paths are:
/etc/pki/ca-trust/source/blocklist/
/usr/share/pki/ca-trust-source/blocklist/
This actor moves all blacklisted certificates into the expected directories
and fix symlinks if to point to the new dirs if they originally pointed
to one of obsoleted dirs.
Covered cases:
- covered situations with missing dirs
- covered both mentioned blacklist directories
- update symlinks in case they point to one of obsoleted directories
- remove obsoleted directories when all files migrated successfully
- execute /usr/bin/update-ca-trust in the end
- remove original a blacklist directory in case all discovered files
inside are migrated successfully
- print error logs in case of any issues so the upgrade does not
crash in case of troubles and users could deal with problems
manually after the upgrade
The actor is not covered by unit-tests as it's just a hotfix. Follow
up works are expected to extend the problem with reports during
preupgrade phases, improve the test coverage, ....
BZ: https://bugzilla.redhat.com/show_bug.cgi?id=2077432
Followup ticket: CRYPTO-7097
---
.../actors/migrateblacklistca/actor.py | 28 ++++++
.../libraries/migrateblacklistca.py | 89 +++++++++++++++++++
.../tests/unit_test_migrateblacklistca.py | 25 ++++++
3 files changed, 142 insertions(+)
create mode 100644 repos/system_upgrade/el8toel9/actors/migrateblacklistca/actor.py
create mode 100644 repos/system_upgrade/el8toel9/actors/migrateblacklistca/libraries/migrateblacklistca.py
create mode 100644 repos/system_upgrade/el8toel9/actors/migrateblacklistca/tests/unit_test_migrateblacklistca.py
diff --git a/repos/system_upgrade/el8toel9/actors/migrateblacklistca/actor.py b/repos/system_upgrade/el8toel9/actors/migrateblacklistca/actor.py
new file mode 100644
index 00000000..863a0063
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/migrateblacklistca/actor.py
@@ -0,0 +1,28 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import migrateblacklistca
+from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag
+
+
+class MigrateBlacklistCA(Actor):
+ """
+ Preserve blacklisted certificates during the upgrade
+
+ Path for the blacklisted certificates has been changed on RHEL 9.
+ The original paths on RHEL 8 and older systems have been:
+ /etc/pki/ca-trust/source/blacklist/
+ /usr/share/pki/ca-trust-source/blacklist/
+ However on RHEL 9 the blacklist directory has been renamed to 'blocklist'.
+ So the new paths are:
+ /etc/pki/ca-trust/source/blocklist/
+ /usr/share/pki/ca-trust-source/blocklist/
+ This actor moves all blacklisted certificates into the expected directories
+ and fix symlinks if needed.
+ """
+
+ name = 'migrate_blacklist_ca'
+ consumes = ()
+ produces = ()
+ tags = (ApplicationsPhaseTag, IPUWorkflowTag)
+
+ def process(self):
+ migrateblacklistca.process()
diff --git a/repos/system_upgrade/el8toel9/actors/migrateblacklistca/libraries/migrateblacklistca.py b/repos/system_upgrade/el8toel9/actors/migrateblacklistca/libraries/migrateblacklistca.py
new file mode 100644
index 00000000..73c9d565
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/migrateblacklistca/libraries/migrateblacklistca.py
@@ -0,0 +1,89 @@
+import os
+import shutil
+
+from leapp.libraries.stdlib import api, CalledProcessError, run
+
+# dict(orig_dir: new_dir)
+DIRS_CHANGE = {
+ '/etc/pki/ca-trust/source/blacklist/': '/etc/pki/ca-trust/source/blocklist/',
+ '/usr/share/pki/ca-trust-source/blacklist/': '/usr/share/pki/ca-trust-source/blocklist/'
+}
+
+
+def _link_src_path(filepath):
+ """
+ Return expected target path for the symlink.
+
+ In case the symlink points to one of dirs supposed to be migrated in this
+ actor, we need to point to the new directory instead.
+
+ In case the link points anywhere else, keep the target path as it is.
+ """
+ realpath = os.path.realpath(filepath)
+ for dirname in DIRS_CHANGE:
+ if realpath.startswith(dirname):
+ return realpath.replace(dirname, DIRS_CHANGE[dirname])
+
+ # it seems we can keep this path
+ return realpath
+
+
+def _migrate_file(filename, src_basedir):
+ dst_path = filename.replace(src_basedir, DIRS_CHANGE[src_basedir])
+ if os.path.exists(dst_path):
+ api.current_logger().info(
+ 'Skipping migration of the {} certificate. The target file already exists'
+ .format(filename)
+ )
+ return
+ os.makedirs(os.path.dirname(dst_path), mode=0o755, exist_ok=True)
+ if os.path.islink(filename):
+ # create the new symlink instead of the moving the file
+ # as the target path could be different as well
+ link_src_path = _link_src_path(filename)
+ # TODO: is the broken symlink ok?
+ os.symlink(link_src_path, dst_path)
+ os.unlink(filename)
+ else:
+ # normal file, just move it
+ shutil.move(filename, dst_path)
+
+
+def _get_files(dirname):
+ return run(['find', dirname, '-type', 'f,l'], split=True)['stdout']
+
+
+def process():
+ for dirname in DIRS_CHANGE:
+ if not os.path.exists(dirname):
+ # The directory does not exist; nothing to do here
+ continue
+ try:
+ blacklisted_certs = _get_files(dirname)
+ except (CalledProcessError, OSError) as e:
+ # TODO: create post-upgrade report
+ api.current_logger().error('Cannot get list of files in {}: {}.'.format(dirname, e))
+ api.current_logger().error('Certificates under {} must be migrated manually.'.format(dirname))
+ continue
+ failed_files = []
+ for filename in blacklisted_certs:
+ try:
+ _migrate_file(filename, dirname)
+ except OSError as e:
+ api.current_logger().error(
+ 'Failed migration of blacklisted certificate {}: {}'
+ .format(filename, e)
+ )
+ failed_files.append(filename)
+ if not failed_files:
+ # the failed removal is not such a big issue here
+ # clean the dir if all files have been migrated successfully
+ shutil.rmtree(dirname, ignore_errors=True)
+ try:
+ run(['/usr/bin/update-ca-trust'])
+ except (CalledProcessError, OSError) as e:
+ api.current_logger().error(
+ 'Cannot update CA trust on the system.'
+ ' It needs to be done manually after the in-place upgrade.'
+ ' Reason: {}'.format(e)
+ )
diff --git a/repos/system_upgrade/el8toel9/actors/migrateblacklistca/tests/unit_test_migrateblacklistca.py b/repos/system_upgrade/el8toel9/actors/migrateblacklistca/tests/unit_test_migrateblacklistca.py
new file mode 100644
index 00000000..970dcb97
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/migrateblacklistca/tests/unit_test_migrateblacklistca.py
@@ -0,0 +1,25 @@
+import os
+
+from leapp.libraries.actor import migrateblacklistca
+from leapp.libraries.common.testutils import CurrentActorMocked
+from leapp.libraries.stdlib import api
+
+
+class MockedGetFiles():
+ def __init__(self):
+ self.called = 0
+
+ def __call__(self):
+ self.called += 1
+ return []
+
+
+def test_no_dirs_exist(monkeypatch):
+ mocked_files = MockedGetFiles()
+ monkeypatch.setattr(os.path, 'exists', lambda dummy: False)
+ monkeypatch.setattr(migrateblacklistca, '_get_files', mocked_files)
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked())
+ # this is bad mock, but we want to be sure that update-ca-trust is not
+ # called on the testing machine
+ monkeypatch.setattr(migrateblacklistca, 'run', lambda dummy: dummy)
+ assert not mocked_files.called
--
2.35.1