diff --git a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py new file mode 100644 index 0000000000..29f3208807 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py @@ -0,0 +1,57 @@ +from leapp.actors import Actor +from leapp.exceptions import StopActorExecutionError +from leapp.libraries.actor.opensslenginescheck import check_openssl_engines +from leapp.libraries.stdlib import api +from leapp.models import OpenSslConfig, Report +from leapp.tags import ChecksPhaseTag, IPUWorkflowTag + + +class OpenSslEnginesCheck(Actor): + """ + The OpenSSL in RHEL 10 has deprecated engines in favor of providers. + + When they are kept in the default configuration file, they might + not work as expected. + + * The most common engine we shipped, pkcs11, has been removed from RHEL 10, + which might cause failures to load the OpenSSL if it is hardcoded in the + configuration file. It needs to be removed before update from the configuration. + * For third-party engines, we should at least warn the user about deprecation. + """ + + name = 'open_ssl_engines_check' + consumes = (OpenSslConfig,) + produces = (Report,) + tags = (IPUWorkflowTag, ChecksPhaseTag,) + + def process(self): + openssl_messages = self.consume(OpenSslConfig) + config = next(openssl_messages, None) + if list(openssl_messages): + api.current_logger().warning('Unexpectedly received more than one OpenSslConfig message.') + if not config: + raise StopActorExecutionError( + 'Could not check openssl configuration', details={'details': 'No OpenSslConfig facts found.'} + ) + + # If the configuration file was not modified, it can not contain user changes + if not config.modified: + return + + # The libp11 documentation has the following configuration snippet in the README: + # + # [openssl_init] + # engines=engine_section + # + # [engine_section] + # pkcs11 = pkcs11_section + # + # [pkcs11_section] + # engine_id = pkcs11 + # dynamic_path = /usr/lib/ssl/engines/libpkcs11.so + # MODULE_PATH = opensc-pkcs11.so + # init = 0 + # + # The `openssl_init` is required by OpenSSL 3.0 so we need to explore the section + # pointed out by the `engines` key in there + check_openssl_engines(config) diff --git a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py new file mode 100644 index 0000000000..72bf3fb4a8 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py @@ -0,0 +1,154 @@ +from leapp import reporting +from leapp.libraries.stdlib import api + + +# FIXME: This is taken from the el8toel9 library in +# repos/system_upgrade/el8toel9/actors/opensslconfigcheck/libraries/opensslconfigcheck.py +def _normalize_key(key): + """ + Strip the part of the key before the first dot + """ + s = key.split(".", 1) + if len(s) == 2: + return s[1] + return key + + +def _key_equal(pair, key): + """ + Check the keys are equal in OpenSSL configuration semantics + + The OpenSSL semantics ignores everything before the first dot to allow specifying + something like following, where the first line would be otherwise normally ignored + + TLS.MaxProtocol = TLSv1.3 + DTLS.MaxProtocol = DTLSv1.2 + """ + if pair.key == key: + return True + return _normalize_key(pair.key) == key + + +def _find_pair(block, key): + """ + Find key-value pair in the given configuration block + + In the given configuration block (OpenSslConfigBlock) find a key-value with a given key. + If multiple values match, only the last one is returned. + """ + res = None + for pair in block.pairs: + if _key_equal(pair, key): + res = pair + + return res + + +def _openssl_find_block(config, name): + """ + In the given configuration file (OpenSslConfig) find a block with a given name + """ + for block in config.blocks: + if block.name == name: + return block + + return None + + +resources = [ + reporting.RelatedResource('package', 'openssl'), + reporting.RelatedResource('file', '/etc/pki/tls/openssl.cnf') +] + + +def check_openssl_engines(config): + """ + Check there are no engines configured in openssl.cnf + + In case the of the pkcs11 engine, stop the upgrade as the package was removed from RHEL 10. + + In case of other (usually third-party) engines, just warn about them being deprecated, + and suggest removal. + """ + init_block = _openssl_find_block(config, "openssl_init") + if config.openssl_conf != "openssl_init" or not init_block: + reporting.create_report([ + reporting.Title('Non-standard configuration of openssl.cnf'), + reporting.Summary( + 'The OpenSSL configuration file `/etc/pki/tls/openssl.cnf` does not contain ' + 'expected initialization (openssl_conf = openssl_init key-value pair).' + ), + reporting.Remediation( + 'The openssl.cnf file needs to contain the following initialization: ' + '`openssl_conf = openssl_init` The `openssl_conf` now contains {} or ' + 'the `[ openssl_init ]` block is missing. '.format(config.openssl_conf) + ), + reporting.Groups([reporting.Groups.INHIBITOR]), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([ + reporting.Groups.SECURITY, + reporting.Groups.NETWORK, + reporting.Groups.SERVICES + ]), + ] + resources) + return + + engines_pair = _find_pair(init_block, "engines") + if not engines_pair: + # No engines no problem + return + + engines_block = _openssl_find_block(config, engines_pair.value) + if not engines_block: + # No engines no problem + return + + enabled_engines = [] + # Iterate over engines directives -- they point to another block + for engine in engines_block.pairs: + name = engine.key + engine_block = _openssl_find_block(config, engine.value) + + # the engine is defined by name, but does not have a corresponding block + if not engine_block: + api.current_logger().debug("The engine {} does not have corresponding configuration block." + .format(name)) + continue + + enabled_engines.append(name) + + if "pkcs11" in enabled_engines: + reporting.create_report([ + reporting.Title('There is pkcs11 engine configured in openssl.cnf'), + reporting.Summary( + 'The OpenSSL configuration file `/etc/pki/tls/openssl.cnf` contains the ' + 'initialization of pkcs11 engin. The pkcs11 engine was removed from RHEL 10 ' + 'and replaced with pkcs11-provider. Before continuing the update, please remove ' + 'the pkcs11 engine configuration.' + ), + reporting.Groups([reporting.Groups.INHIBITOR]), + reporting.Severity(reporting.Severity.HIGH), + reporting.Groups([ + reporting.Groups.SECURITY, + reporting.Groups.NETWORK, + reporting.Groups.SERVICES + ]), + ] + resources) + # do not report it again below if it is the only one + enabled_engines.remove("pkcs11") + + if enabled_engines: + reporting.create_report([ + reporting.Title('There are enabled engines in openssl.cnf'), + reporting.Summary( + 'The OpenSSL configuration file `/etc/pki/tls/openssl.cnf` contains the ' + 'following enabled engines: {}. They are deprecated in OpenSSL 3.0, will ' + 'not work in the following versions. '.format(', '.join(enabled_engines)) + ), + reporting.Severity(reporting.Severity.LOW), + reporting.Groups([ + reporting.Groups.SECURITY, + reporting.Groups.NETWORK, + reporting.Groups.SERVICES + ]), + ] + resources) diff --git a/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py new file mode 100644 index 0000000000..6cdefec564 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py @@ -0,0 +1,182 @@ +from leapp.models import OpenSslConfig, OpenSslConfigBlock, OpenSslConfigPair, Report + + +def test_actor_execution_empty(current_actor_context): + current_actor_context.feed( + OpenSslConfig( + blocks=[], + # modified=False, # default + ) + ) + current_actor_context.run() + assert not current_actor_context.consume(Report) + + +def test_actor_execution_empty_modified(current_actor_context): + current_actor_context.feed( + OpenSslConfig( + blocks=[], + modified=True, + ) + ) + current_actor_context.run() + r = current_actor_context.consume(Report) + assert r + assert 'Non-standard configuration of openssl.cnf' in r[0].report['title'] + + +def test_actor_execution_default_modified(current_actor_context): + current_actor_context.feed( + OpenSslConfig( + openssl_conf='openssl_init', + blocks=[ + OpenSslConfigBlock( + name="openssl_init", + pairs=[ + OpenSslConfigPair( + key="providers", + value="provider_sect" + ), + OpenSslConfigPair( + key="ssl_conf", + value="ssl_module" + ), + OpenSslConfigPair( + key="alg_section", + value="evp_properties" + ) + ] + ), + OpenSslConfigBlock( + name="evp_properties", + pairs=[] + ), + OpenSslConfigBlock( + name="provider_sect", + pairs=[ + OpenSslConfigPair( + key="default", + value="default_sect" + ) + ] + ), + OpenSslConfigBlock( + name="default_sect", + pairs=[ + OpenSslConfigPair( + key="activate", + value="1" + ) + ] + ), + OpenSslConfigBlock( + name="ssl_module", + pairs=[ + OpenSslConfigPair( + key="system_default", + value="crypto_policy" + ) + ] + ), + OpenSslConfigBlock( + name="crypto_policy", + pairs=[ + OpenSslConfigPair( + key=".include", + value="/etc/crypto-policies/back-ends/opensslcnf.config" + ) + ] + ), + ], + modified=True, + ) + ) + current_actor_context.run() + assert not current_actor_context.consume(Report) + + +def test_actor_execution_pkcs11_engine_modified(current_actor_context): + # default, but removing contents unrelated for the checks + current_actor_context.feed( + OpenSslConfig( + openssl_conf='openssl_init', + blocks=[ + OpenSslConfigBlock( + name="openssl_init", + pairs=[ + OpenSslConfigPair( + key="engines", + value="engines_sect" + ) + ] + ), + OpenSslConfigBlock( + name="engines_sect", + pairs=[ + OpenSslConfigPair( + key="pkcs11", + value="pkcs11_sect" + ) + ] + ), + OpenSslConfigBlock( + name="pkcs11_sect", + pairs=[ + OpenSslConfigPair( + key="dynamic_path", + value="/usr/lib64/engines-3/pkcs11.so" + ) + ] + ) + ], + modified=True, + ) + ) + current_actor_context.run() + r = current_actor_context.consume(Report) + assert r + assert 'There is pkcs11 engine configured in openssl.cnf' in r[0].report['title'] + + +def test_actor_execution_other_engine_modified(current_actor_context): + # default, but removing contents unrelated for the checks + current_actor_context.feed( + OpenSslConfig( + openssl_conf='openssl_init', + blocks=[ + OpenSslConfigBlock( + name="openssl_init", + pairs=[ + OpenSslConfigPair( + key="engines", + value="engines_sect" + ) + ] + ), + OpenSslConfigBlock( + name="engines_sect", + pairs=[ + OpenSslConfigPair( + key="acme", + value="acme_sect" + ) + ] + ), + OpenSslConfigBlock( + name="acme_sect", + pairs=[ + OpenSslConfigPair( + key="init", + value="0" + ) + ] + ) + ], + modified=True, + ) + ) + current_actor_context.run() + r = current_actor_context.consume(Report) + assert r + assert 'There are enabled engines in openssl.cnf' in r[0].report['title'] + assert 'acme' in r[0].report['summary']