Skip to content

Commit

Permalink
OpenSslEnginesCheck: New actor for OpenSSL engines (9->10)
Browse files Browse the repository at this point in the history
Signed-off-by: Jakub Jelen <[email protected]>
  • Loading branch information
Jakuje committed Feb 4, 2025
1 parent 755358e commit ba4cf3a
Show file tree
Hide file tree
Showing 3 changed files with 393 additions and 0 deletions.
57 changes: 57 additions & 0 deletions repos/system_upgrade/el9toel10/actors/opensslenginescheck/actor.py
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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']

0 comments on commit ba4cf3a

Please sign in to comment.