-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add inhibitor for custom libraries registered by ld.so.conf
The in-place upgrade does not support custom libraries linked using the ld.so configuration. The new actor introduced in this commit detects if the configuration was tempered with and inhibits the upgrade in such case.
- Loading branch information
1 parent
d6498b8
commit f873640
Showing
3 changed files
with
246 additions
and
0 deletions.
There are no files selected for viewing
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,23 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor.checkldsoconfiguration import check_ld_so_configuration | ||
from leapp.models import InstalledRedHatSignedRPM, Report | ||
from leapp.tags import ChecksPhaseTag, IPUWorkflowTag | ||
|
||
|
||
class CheckLdSoConfiguration(Actor): | ||
""" | ||
Check for customization of ld.so configuration | ||
The ld.so configuration files are used to overwrite standard library links | ||
in order to use different custom libraries. The in-place upgrade does not | ||
support customization of this configuration by user. This actor inhibits the | ||
upgrade upon detecting such customization. | ||
""" | ||
|
||
name = 'check_ld_so_configuration' | ||
consumes = (InstalledRedHatSignedRPM,) | ||
produces = (Report,) | ||
tags = (ChecksPhaseTag, IPUWorkflowTag) | ||
|
||
def process(self): | ||
check_ld_so_configuration() |
98 changes: 98 additions & 0 deletions
98
repos/system_upgrade/common/actors/checkldconf/libraries/checkldsoconfiguration.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,98 @@ | ||
import glob | ||
import os | ||
|
||
from leapp import reporting | ||
from leapp.libraries.common.rpms import has_package | ||
from leapp.libraries.stdlib import api, CalledProcessError, run | ||
from leapp.models import InstalledRedHatSignedRPM | ||
|
||
LD_SO_CONF_DIR = '/etc/ld.so.conf.d' | ||
LD_SO_CONF_MAIN = '/etc/ld.so.conf' | ||
LD_SO_CONF_DEFAULT_INCLUDE = 'ld.so.conf.d/*.conf' | ||
LIST_FORMAT_PREFIX = '\n - ' | ||
|
||
|
||
def _read_file(file_path): | ||
with open(file_path, 'r') as fd: | ||
return fd.readlines() | ||
|
||
|
||
def _report_custom_ld_so_configuration(summary): | ||
reporting.create_report([ | ||
reporting.Title( | ||
'Third party libraries linked with ld.so.conf are not supported for the in-place upgrade.' | ||
), | ||
reporting.Summary(summary), | ||
reporting.Remediation('Remove the custom ld.so configuration.'), | ||
reporting.RelatedResource('file', '/etc/ld.so.conf'), | ||
reporting.RelatedResource('directory', '/etc/ld.so.conf.d'), | ||
reporting.Severity(reporting.Severity.HIGH), | ||
reporting.Groups([reporting.Groups.OS_FACTS, reporting.Groups.INHIBITOR]), | ||
]) | ||
|
||
|
||
def _is_included_ld_so_config_custom(config_path): | ||
if not os.path.isfile(config_path): | ||
return False | ||
|
||
is_custom = False | ||
try: | ||
package_name = run(['rpm', '-qf', '--queryformat', '%{NAME}', config_path])['stdout'] | ||
is_custom = not has_package(InstalledRedHatSignedRPM, package_name) | ||
except CalledProcessError: | ||
is_custom = True | ||
api.current_logger().debug('The following config file is{}considered a custom ' | ||
'ld.so configuration: {}'.format(' ' if is_custom else ' NOT ', config_path)) | ||
return is_custom | ||
|
||
|
||
def _parse_main_ld_so_config(): | ||
config = _read_file(LD_SO_CONF_MAIN) | ||
|
||
included_configs = [] | ||
other_lines = [] | ||
for line in config: | ||
line = line.strip() | ||
if line.startswith('include'): | ||
cfg_glob = line.split(' ', 1)[1].strip() | ||
cfg_glob = os.path.join('/etc', cfg_glob) if not os.path.isabs(cfg_glob) else cfg_glob | ||
included_configs.append(cfg_glob) | ||
elif line: | ||
other_lines.append(line) | ||
|
||
return included_configs, other_lines | ||
|
||
|
||
def check_ld_so_configuration(): | ||
included_configs, other_lines = _parse_main_ld_so_config() | ||
summary = '' | ||
|
||
if other_lines: | ||
# The main ld.so config file is expected to only include additional configs that are owned by a package | ||
summary += ('The /etc/ld.so.conf file has unexpected contents:' | ||
'{}{}'.format(LIST_FORMAT_PREFIX, | ||
LIST_FORMAT_PREFIX.join(other_lines))) | ||
|
||
is_default_include_present = '/etc/' + LD_SO_CONF_DEFAULT_INCLUDE in included_configs | ||
if not is_default_include_present: | ||
api.current_logger().debug('The default include "' + LD_SO_CONF_DEFAULT_INCLUDE + | ||
'" is not present in the ' + LD_SO_CONF_MAIN + ' file.') | ||
|
||
if is_default_include_present and len(included_configs) != 1: | ||
# The additional included configs will most likely be created manually by the user | ||
# and therefore will get flagged as custom in the next part of this function | ||
api.current_logger().debug('The default include "' + LD_SO_CONF_DEFAULT_INCLUDE + | ||
'" is not the only include in the ' + LD_SO_CONF_MAIN + ' file.') | ||
|
||
# Detect custom ld configs from the includes in the main ld.so.conf | ||
custom_ld_configs = [] | ||
for cfg_glob in included_configs: | ||
custom_ld_configs += [cfg for cfg in glob.glob(cfg_glob) if _is_included_ld_so_config_custom(cfg)] | ||
|
||
if custom_ld_configs: | ||
summary += ('\nThe following config files were marked as unsupported:' | ||
'{}{}'.format(LIST_FORMAT_PREFIX, | ||
LIST_FORMAT_PREFIX.join(custom_ld_configs))) | ||
|
||
if summary: | ||
_report_custom_ld_so_configuration(summary) |
125 changes: 125 additions & 0 deletions
125
repos/system_upgrade/common/actors/checkldconf/tests/test_checkldsoconfiguration.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,125 @@ | ||
import glob | ||
import os | ||
|
||
import pytest | ||
|
||
from leapp import reporting | ||
from leapp.libraries.actor import checkldsoconfiguration | ||
from leapp.libraries.common.testutils import create_report_mocked | ||
from leapp.libraries.stdlib import CalledProcessError | ||
from leapp.models import InstalledRedHatSignedRPM | ||
|
||
INCLUDED_CONFIGS_GLOB_DICT_1 = {'/etc/ld.so.conf.d/*.conf': ['/etc/ld.so.conf.d/dyninst-x86_64.conf', | ||
'/etc/ld.so.conf.d/mariadb-x86_64.conf', | ||
'/etc/ld.so.conf.d/bind-export-x86_64.conf']} | ||
|
||
INCLUDED_CONFIGS_GLOB_DICT_2 = {'/etc/ld.so.conf.d/*.conf': ['/etc/ld.so.conf.d/dyninst-x86_64.conf', | ||
'/etc/ld.so.conf.d/mariadb-x86_64.conf', | ||
'/etc/ld.so.conf.d/bind-export-x86_64.conf', | ||
'/etc/ld.so.conf.d/custom1.conf', | ||
'/etc/ld.so.conf.d/custom2.conf']} | ||
|
||
INCLUDED_CONFIGS_GLOB_DICT_3 = {'/etc/ld.so.conf.d/*.conf': ['/etc/ld.so.conf.d/dyninst-x86_64.conf', | ||
'/etc/ld.so.conf.d/custom1.conf', | ||
'/etc/ld.so.conf.d/mariadb-x86_64.conf', | ||
'/etc/ld.so.conf.d/bind-export-x86_64.conf', | ||
'/etc/ld.so.conf.d/custom2.conf'], | ||
'/custom/path/*.conf': ['/custom/path/custom1.conf', | ||
'/custom/path/custom2.conf']} | ||
|
||
|
||
@pytest.mark.parametrize(('included_configs_glob_dict', 'other_lines', 'custom_configs'), | ||
[ | ||
(INCLUDED_CONFIGS_GLOB_DICT_1, [], []), | ||
(INCLUDED_CONFIGS_GLOB_DICT_1, ['/custom/path.lib'], []), | ||
(INCLUDED_CONFIGS_GLOB_DICT_2, [], ['/etc/ld.so.conf.d/custom1.conf', | ||
'/etc/ld.so.conf.d/custom2.conf']), | ||
(INCLUDED_CONFIGS_GLOB_DICT_3, ['/custom/path.lib'], ['/etc/ld.so.conf.d/custom1.conf', | ||
'/etc/ld.so.conf.d/custom2.conf' | ||
'/custom/path/custom1.conf', | ||
'/custom/path/custom2.conf']), | ||
]) | ||
def test_check_ld_so_configuration(monkeypatch, included_configs_glob_dict, other_lines, custom_configs): | ||
monkeypatch.setattr(reporting, 'create_report', create_report_mocked()) | ||
monkeypatch.setattr(glob, 'glob', lambda glob: included_configs_glob_dict[glob]) | ||
monkeypatch.setattr(checkldsoconfiguration, '_is_included_ld_so_config_custom', | ||
lambda config: config in custom_configs) | ||
monkeypatch.setattr(checkldsoconfiguration, '_parse_main_ld_so_config', | ||
lambda: (included_configs_glob_dict.keys(), other_lines)) | ||
|
||
checkldsoconfiguration.check_ld_so_configuration() | ||
|
||
report_expected = custom_configs or other_lines | ||
if not report_expected: | ||
assert reporting.create_report.called == 0 | ||
return | ||
|
||
assert reporting.create_report.called == 1 | ||
assert 'ld.so.conf' in reporting.create_report.reports[0]['title'] | ||
summary = reporting.create_report.reports[0]['summary'] | ||
|
||
all_configs = [] | ||
for configs in included_configs_glob_dict.values(): | ||
all_configs += configs | ||
|
||
if custom_configs: | ||
assert 'The following config files were marked as unsupported:' in summary | ||
|
||
for config in all_configs: | ||
assert (config in custom_configs) == (config in summary) | ||
|
||
if other_lines: | ||
assert 'The /etc/ld.so.conf file has unexpected contents' in summary | ||
|
||
for other_line in other_lines: | ||
assert other_line in summary | ||
|
||
|
||
@pytest.mark.parametrize(('config_contents', 'included_config_paths', 'other_lines'), | ||
[ | ||
(['include ld.so.conf.d/*.conf\n'], | ||
['/etc/ld.so.conf.d/*.conf'], []), | ||
(['include ld.so.conf.d/*.conf\n', '\n', '/custom/path.lib\n'], | ||
['/etc/ld.so.conf.d/*.conf'], ['/custom/path.lib']), | ||
(['include ld.so.conf.d/*.conf\n', 'include /custom/path.conf\n'], | ||
['/etc/ld.so.conf.d/*.conf', '/custom/path.conf'], []), | ||
([' \n'], | ||
[], []) | ||
]) | ||
def test_parse_main_ld_so_config(monkeypatch, config_contents, included_config_paths, other_lines): | ||
def mocked_read_file(path): | ||
assert path == checkldsoconfiguration.LD_SO_CONF_MAIN | ||
return config_contents | ||
|
||
monkeypatch.setattr(checkldsoconfiguration, '_read_file', mocked_read_file) | ||
|
||
_included_config_paths, _other_lines = checkldsoconfiguration._parse_main_ld_so_config() | ||
|
||
assert _included_config_paths == included_config_paths | ||
assert _other_lines == other_lines | ||
|
||
|
||
@pytest.mark.parametrize(('config_path', 'run_result', 'package_exists', 'is_custom'), | ||
[ | ||
('/etc/ld.so.conf.d/dyninst-x86_64.conf', 'dyninst', True, False), | ||
('/etc/ld.so.conf.d/somelib.conf', CalledProcessError, False, True), | ||
('/etc/custom/custom.conf', 'custom', False, True) | ||
]) | ||
def test_is_included_ld_so_config_custom(monkeypatch, config_path, run_result, package_exists, is_custom): | ||
def mocked_run(command): | ||
assert config_path in command | ||
if run_result and not isinstance(run_result, str): | ||
raise CalledProcessError("message", ["command"], "result") | ||
return {'stdout': run_result} | ||
|
||
def mocked_has_package(model, package_name): | ||
assert model is InstalledRedHatSignedRPM | ||
assert package_name == run_result | ||
return package_exists | ||
|
||
monkeypatch.setattr(checkldsoconfiguration, 'run', mocked_run) | ||
monkeypatch.setattr(checkldsoconfiguration, 'has_package', mocked_has_package) | ||
monkeypatch.setattr(os.path, 'isfile', lambda _: True) | ||
|
||
result = checkldsoconfiguration._is_included_ld_so_config_custom(config_path) | ||
assert result == is_custom |