Skip to content

Commit

Permalink
OpenSslEnginesCheck: New actor to check OpenSSL engines (9->10)
Browse files Browse the repository at this point in the history
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
Jakuje authored and pirat89 committed Feb 10, 2025
1 parent f8cc12e commit 8076b11
Show file tree
Hide file tree
Showing 3 changed files with 329 additions and 0 deletions.
62 changes: 62 additions & 0 deletions repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py
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)
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)
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']

0 comments on commit 8076b11

Please sign in to comment.