-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OpenSslEnginesCheck: New actor to check OpenSSL engines (9->10)
The OpenSSL in EL 10 has deprecated engines in favor of providers. When the user upgrades to EL 10, the openssl configuration file will be changed to the EL 10 defaults and if the user depends on some engine for the system to boot or sshd to start, it might break and user needs to be aware of this. * The most common engine we shipped, pkcs11, has been removed from EL 10. The pkcs11-provider rpm will be installed automatically based on PES data if openssl-pkcs11 has been installed on the source system. However, user will be still informed they need to configure the system manually after the upgrade to use it. * The third-party engines could do anything so user needs to make sure neither of them is crucial for the minimal system to work. If they are still needed in the target system, they need to be configured again, but we recommend finding provider alternatives. JIRA: RHEL-78396 Signed-off-by: Jakub Jelen <[email protected]>
- Loading branch information
Showing
3 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
62 changes: 62 additions & 0 deletions
62
repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
130 changes: 130 additions & 0 deletions
130
repos/system_upgrade/el9toel10/actors/opensslenginescheck/libraries/opensslenginescheck.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
137 changes: 137 additions & 0 deletions
137
..._upgrade/el9toel10/actors/opensslenginescheck/tests/component_test_opensslenginescheck.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'] |