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..7317165748 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py @@ -0,0 +1,62 @@ +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. However, as far as the /etc/pki/tls/openssl.cnf + configuration file is replaced during the upgrade by the target default + configuration, it should be ok to just inform user about that (see + related actors in the system_upgrade_common repository). + * Similarly user should be warned in case of third-party engines + """ + + 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: + # NOTE: unexpected situation - putting the check just as a seatbelt + # - not covered by unit-tests. + 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..00edc1daa0 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py @@ -0,0 +1,130 @@ +from leapp import reporting +from leapp.libraries.stdlib import api + +FMT_LIST_SEPARATOR = '\n - ' +RESOURCES = [ + reporting.RelatedResource('package', 'openssl'), + reporting.RelatedResource('file', '/etc/pki/tls/openssl.cnf') +] + + +def _formatted_list_output(input_list, sep=FMT_LIST_SEPARATOR): + return ['{}{}'.format(sep, item) for item in input_list] + + +# NOTE: 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 + + +def check_openssl_engines(config): + """ + Check there are no engines configured in openssl.cnf + + Report any detected openssl engines defined in /etc/pki/tls/openssl.cnf. + """ + init_block = _openssl_find_block(config, config.openssl_conf) + if config.openssl_conf != 'openssl_init' or not init_block: + api.current_logger().warning( + 'Non standard configuration in /etc/pki/tls/openssl.cnf: missing "openssl_init" section.' + ) + 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 enabled_engines: + reporting.create_report([ + reporting.Title('Detected enabled deprecated engines in openssl.cnf'), + reporting.Summary( + 'OpenSSL engines are deprecated since OpenSSL version 3.0' + ' and they are no longer supported nor available on the target' + ' RHEL 10 system. Any applications depending on OpenSSL engines' + ' might not work correctly on the target system and should be configured' + ' to use OpenSSL providers instead.' + ' The following OpenSSL engines are configured inside the /etc/pki/tls/openssl.cnf file:{}' + .format(''.join(_formatted_list_output(enabled_engines))) + ), + reporting.Remediation(hint=( + 'After the upgrade configure your system and applications' + ' to use OpenSSL providers instead of OpenSSL engines if needed.' + )), + reporting.Severity(reporting.Severity.MEDIUM), + reporting.Groups([ + reporting.Groups.NETWORK, + reporting.Groups.POST, + reporting.Groups.SECURITY, + 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..e5ed7b2537 --- /dev/null +++ b/repos/system_upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py @@ -0,0 +1,137 @@ +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() + assert not current_actor_context.consume(Report) + + +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_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() + report = current_actor_context.consume(Report) + assert report + assert 'Detected enabled deprecated engines in openssl.cnf' in report[0].report['title'] + assert 'acme' in report[0].report['summary']