leapp-repository/SOURCES/0014-Add-actor-for-updating...

176 lines
6.9 KiB
Diff

From ce1b83fafbbf3b323874fbb363e85a2e5abab4e2 Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Wed, 16 Mar 2022 21:48:04 +0100
Subject: [PATCH 14/39] Add actor for updating OpenSSH configuration to RHEL9
---
.../actors/opensshdropindirectory/actor.py | 29 ++++++++
.../libraries/opensshdropindirectory.py | 67 +++++++++++++++++++
.../test_opensshdropindirectory_prepend.py | 44 ++++++++++++
3 files changed, 140 insertions(+)
create mode 100644 repos/system_upgrade/el8toel9/actors/opensshdropindirectory/actor.py
create mode 100644 repos/system_upgrade/el8toel9/actors/opensshdropindirectory/libraries/opensshdropindirectory.py
create mode 100644 repos/system_upgrade/el8toel9/actors/opensshdropindirectory/tests/test_opensshdropindirectory_prepend.py
diff --git a/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/actor.py b/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/actor.py
new file mode 100644
index 00000000..17a0c01a
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/actor.py
@@ -0,0 +1,29 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import opensshdropindirectory
+from leapp.models import InstalledRedHatSignedRPM, OpenSshConfig
+from leapp.tags import ApplicationsPhaseTag, IPUWorkflowTag
+
+
+class OpenSshDropInDirectory(Actor):
+ """
+ The RHEL 9 provides default configuration file with an Include directive.
+
+ If the configuration file was modified, it will not be replaced by the update
+ and we need to do couple of tweaks:
+
+ * Insert Include directive as expected by the rest of the OS
+ * Verify the resulting configuration is valid
+ * The only potentially problematic option is "Subsystem", but it is kept in the
+ main sshd_config even in RHEL9 so there is no obvious upgrade path where it
+ could cause issues (unlike the Debian version).
+
+ [1] https://bugzilla.mindrot.org/show_bug.cgi?id=3236
+ """
+
+ name = 'open_ssh_drop_in_directory'
+ consumes = (OpenSshConfig, InstalledRedHatSignedRPM,)
+ produces = ()
+ tags = (IPUWorkflowTag, ApplicationsPhaseTag,)
+
+ def process(self):
+ opensshdropindirectory.process(self.consume(OpenSshConfig))
diff --git a/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/libraries/opensshdropindirectory.py b/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/libraries/opensshdropindirectory.py
new file mode 100644
index 00000000..d55eee1c
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/libraries/opensshdropindirectory.py
@@ -0,0 +1,67 @@
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.common.rpms import has_package
+from leapp.libraries.stdlib import api
+from leapp.models import InstalledRedHatSignedRPM
+
+# The main SSHD configuration file
+SSHD_CONFIG = '/etc/ssh/sshd_config'
+
+# The include directive needed, taken from RHEL9 sshd_config with leapp comment
+INCLUDE = 'Include /etc/ssh/sshd_config.d/*.conf'
+INCLUDE_BLOCK = ''.join(('# Added by leapp during upgrade from RHEL8 to RHEL9\n', INCLUDE, '\n'))
+
+
+def prepend_string_if_not_present(f, content, check_string):
+ """
+ This reads the open file descriptor and checks for presense of the `check_string`.
+ If not present, the `content` is prepended to the original content of the file and
+ result is written.
+ Note, that this requires opened file for both reading and writing, for example with:
+
+ with open(path, r+') as f:
+ """
+ lines = f.readlines()
+ for line in lines:
+ if line.lstrip().startswith(check_string):
+ # The directive is present
+ return
+
+ # prepend it otherwise, also with comment
+ f.seek(0)
+ f.write(''.join((content, ''.join(lines))))
+
+
+def process(openssh_messages):
+ """
+ The main logic of the actor:
+ * read the configuration file message
+ * skip if no action is needed
+ * package not installed
+ * the configuration file was not modified
+ * insert the include directive if it is not present yet
+ """
+ config = next(openssh_messages, None)
+ if list(openssh_messages):
+ api.current_logger().warning('Unexpectedly received more than one OpenSshConfig message.')
+ if not config:
+ raise StopActorExecutionError(
+ 'Could not check openssh configuration', details={'details': 'No OpenSshConfig facts found.'}
+ )
+
+ # If the package is not installed, there is no need to do anything
+ if not has_package(InstalledRedHatSignedRPM, 'openssh-server'):
+ return
+
+ # If the configuration file was not modified, the rpm update will bring the new
+ # changes by itself
+ if not config.modified:
+ return
+
+ # otherwise prepend the Include directive to the main sshd_config
+ api.current_logger().debug('Adding the Include directive to {}.'
+ .format(SSHD_CONFIG))
+ try:
+ with open(SSHD_CONFIG, 'r+') as f:
+ prepend_string_if_not_present(f, INCLUDE_BLOCK, INCLUDE)
+ except (OSError, IOError) as error:
+ api.current_logger().error('Failed to modify the file {}: {} '.format(SSHD_CONFIG, error))
diff --git a/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/tests/test_opensshdropindirectory_prepend.py b/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/tests/test_opensshdropindirectory_prepend.py
new file mode 100644
index 00000000..bccadf4b
--- /dev/null
+++ b/repos/system_upgrade/el8toel9/actors/opensshdropindirectory/tests/test_opensshdropindirectory_prepend.py
@@ -0,0 +1,44 @@
+import pytest
+
+from leapp.libraries.actor.opensshdropindirectory import prepend_string_if_not_present
+
+
+class MockFile(object):
+ def __init__(self, path, content=None):
+ self.path = path
+ self.content = content
+ self.error = False
+
+ def readlines(self):
+ return self.content.splitlines(True)
+
+ def seek(self, n):
+ self.content = ''
+
+ def write(self, content):
+ self.content = content
+
+
+testdata = (
+ ('', 'Prepend', 'Prepend',
+ 'Prepend'), # only prepend
+ ('Text', '', '',
+ 'Text'), # only text
+ ('Text', 'Prepend', 'Prepend',
+ 'PrependText'), # prepended text
+ ('Prepend\nText\n', 'Prepend', 'Prepend',
+ 'Prepend\nText\n'), # already present
+ ('Text\n', '# Comment\nPrepend\n', 'Prepend',
+ '# Comment\nPrepend\nText\n'), # different prepend than check string
+ ('Prepend\nText\n', '# Comment\nPrepend\n', 'Prepend',
+ 'Prepend\nText\n'), # different prepend than check string, already present
+)
+
+
+@pytest.mark.parametrize('file_content,prepend,check_string,expected', testdata)
+def test_prepend_string_if_not_present(file_content, prepend, check_string, expected):
+ f = MockFile('/etc/ssh/sshd_config', file_content)
+
+ prepend_string_if_not_present(f, prepend, check_string)
+
+ assert f.content == expected
--
2.35.3