leapp-repository/SOURCES/0037-Enable-gpgcheck-during...

1815 lines
76 KiB
Diff

From 9ed71946b763e1b1e3049ebd55a0d61eba42015e Mon Sep 17 00:00:00 2001
From: Jakub Jelen <jjelen@redhat.com>
Date: Wed, 15 Jun 2022 21:49:22 +0200
Subject: [PATCH 37/37] Enable gpgcheck during IPU (+ add --nogpgcheck CLI
option)
Previously the gpgcheck=0 has been enforced during the IPU as we have
not have any reasonable solution doing upgrade with allowed gpgcheck.
Previously such upgrades automatically imported any gpgkeys without
any possible check whether the actual keys are valid or not, which
could lead to the automatical import of compromised/spurious gpg keys
and user would not know about that. So using the original solution,
user was asked for the import of new keys when installing additional
content from new repositories (if keys have been different from those
used in the original system).
To do the upgrade in the original way (without gpgcheck), execute
leapp with the `--nogpgcheck` option, or specify `LEAPP_NOGPGCHECK=1`
(envar). In such a case, all actions described below are skipped.
The current solution enables the GPG check by default but also could
require additional actions from user to be able to upgrade. The goal
is to ensure that no additional GPG keys are imported by DNF
automatically during any action (partially resolved, read bellow).
To be able to achive this, we are importing gpg keys automatically
from a *trusted directory* before any dnf transaction is executed, so:
a) into the rpmdb of the target userspace container, before the
actual installation of the target userspace container
b) into overlayed rpmdb when calculating/testing the upgrade
transaction
c) into the system rpmdb right before the execution of the DNF
upgrade transaction
The case a) is handled directly in the target_userspace_creator actor.
The other cases are handled via DNFWorkaround msg, using the
`importrpmgpgkeys` tool script.
The *trusted directory* is in this case located under
`files/rpm-gpg/`, where the directory name is major release of the
target system in case of production releases, in other cases it has
the *beta* suffix. So e.g.:
files/rpm-gpg/8
files/rpm-gpg/8beta
That's because production and beta repositories have different gpg
keys and it is not wanted to mix production and beta keys. Beta
repositories are used only when the target production type is beta:
LEAPP_DEVEL_TARGET_PRODUCT_TYPE=beta
Introducing the missinggpgkeysinhibitor actor that checks gpg keys
based on `gpgkey` specified in repofiles per each used target
repository which does not explicitly specify `gpgcheck=0`.
Such a key is compared with the currently installed gpg keys in the
host rpmdb and keys inside the *trusted directory*. If the key
is not stored in any of those places, the upgrade is inhibited with
the corresponding report. User can resolve the problem installing the
missing gpg keys or storing them to the trusted directory.
Currently supported protocols for the gpg keys are
file:///
http://
https://
If a key cannot be obtained (including use of an unsupported protocol,
e.g. ftp://) the actor prompt a log, but does not generate a report
about that (so the upgrade can continue, which could later lead into
a failure during the download of packages - one of TODOs).
This is not the final commit for this feature and additional work
is expected before the new release is introduced. Regarding that,
see the code for new TODO / FIXME notes that are put into the code.
Summary of some TODOs planned to address in followup PR:
- add checks that DNF does not import additional GPG keys during
any action
- report GPG keys that could not be checked, informing user about
possible consequences - the report should not inhibit the upgrade
- possibly introduce fallback for getting file:///... gpg keys
as currently they are obtained from the target userspace container
but if not present, the host system should be possibly checked:
- Note that if the file has been created manually (custom repo file)
most likely the gpgkey will be stored only on the host system
- and in such a case the file would need to be copied from the
host system into the container.
Signed-off-by: Jakub Jelen <jjelen@redhat.com>
---
commands/preupgrade/__init__.py | 1 +
commands/rerun/__init__.py | 1 +
commands/upgrade/__init__.py | 1 +
commands/upgrade/util.py | 3 +
.../actors/missinggpgkeysinhibitor/actor.py | 40 ++
.../libraries/missinggpgkey.py | 368 ++++++++++++
.../tests/component_test_missinggpgkey.py | 522 ++++++++++++++++++
.../tests/unit_test_missinggpgkey.py | 209 +++++++
.../libraries/userspacegen.py | 62 ++-
.../rpm-gpg/8/RPM-GPG-KEY-redhat-release | 89 +++
.../rpm-gpg/8beta/RPM-GPG-KEY-redhat-beta | 29 +
.../rpm-gpg/9/RPM-GPG-KEY-redhat-release | 66 +++
.../rpm-gpg/9beta/RPM-GPG-KEY-redhat-beta | 29 +
.../common/libraries/dnfplugin.py | 13 +-
.../common/libraries/tests/test_dnfplugin.py | 21 +-
.../common/models/targetrepositories.py | 34 ++
16 files changed, 1455 insertions(+), 33 deletions(-)
create mode 100644 repos/system_upgrade/common/actors/missinggpgkeysinhibitor/actor.py
create mode 100644 repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py
create mode 100644 repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/component_test_missinggpgkey.py
create mode 100644 repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/unit_test_missinggpgkey.py
create mode 100644 repos/system_upgrade/common/files/rpm-gpg/8/RPM-GPG-KEY-redhat-release
create mode 100644 repos/system_upgrade/common/files/rpm-gpg/8beta/RPM-GPG-KEY-redhat-beta
create mode 100644 repos/system_upgrade/common/files/rpm-gpg/9/RPM-GPG-KEY-redhat-release
create mode 100644 repos/system_upgrade/common/files/rpm-gpg/9beta/RPM-GPG-KEY-redhat-beta
diff --git a/commands/preupgrade/__init__.py b/commands/preupgrade/__init__.py
index d612fbb1..a1577a63 100644
--- a/commands/preupgrade/__init__.py
+++ b/commands/preupgrade/__init__.py
@@ -30,6 +30,7 @@ from leapp.utils.output import beautify_actor_exception, report_errors, report_i
command_utils.get_upgrade_flavour()))
@command_opt('report-schema', help='Specify report schema version for leapp-report.json',
choices=['1.0.0', '1.1.0', '1.2.0'], default=get_config().get('report', 'schema'))
+@command_opt('nogpgcheck', is_flag=True, help='Disable RPM GPG checks. Same as yum/dnf --nogpgcheck option.')
@breadcrumbs.produces_breadcrumbs
def preupgrade(args, breadcrumbs):
util.disable_database_sync()
diff --git a/commands/rerun/__init__.py b/commands/rerun/__init__.py
index 57149571..a06dd266 100644
--- a/commands/rerun/__init__.py
+++ b/commands/rerun/__init__.py
@@ -68,6 +68,7 @@ def rerun(args):
verbose=args.verbose,
reboot=False,
no_rhsm=False,
+ nogpgcheck=False,
channel=None,
report_schema='1.1.0',
whitelist_experimental=[],
diff --git a/commands/upgrade/__init__.py b/commands/upgrade/__init__.py
index 005538ed..8b257fa9 100644
--- a/commands/upgrade/__init__.py
+++ b/commands/upgrade/__init__.py
@@ -36,6 +36,7 @@ from leapp.utils.output import beautify_actor_exception, report_errors, report_i
command_utils.get_upgrade_flavour()))
@command_opt('report-schema', help='Specify report schema version for leapp-report.json',
choices=['1.0.0', '1.1.0', '1.2.0'], default=get_config().get('report', 'schema'))
+@command_opt('nogpgcheck', is_flag=True, help='Disable RPM GPG checks. Same as yum/dnf --nogpgcheck option.')
@breadcrumbs.produces_breadcrumbs
def upgrade(args, breadcrumbs):
skip_phases_until = None
diff --git a/commands/upgrade/util.py b/commands/upgrade/util.py
index aa433786..6055c65b 100644
--- a/commands/upgrade/util.py
+++ b/commands/upgrade/util.py
@@ -206,6 +206,9 @@ def prepare_configuration(args):
# Make sure we convert rel paths into abs ones while we know what CWD is
os.environ['LEAPP_TARGET_ISO'] = os.path.abspath(target_iso_path)
+ if args.nogpgcheck:
+ os.environ['LEAPP_NOGPGCHECK'] = '1'
+
# Check upgrade path and fail early if it's unsupported
target_version, flavor = command_utils.vet_upgrade_path(args)
os.environ['LEAPP_UPGRADE_PATH_TARGET_RELEASE'] = target_version
diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/actor.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/actor.py
new file mode 100644
index 00000000..6f836a5b
--- /dev/null
+++ b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/actor.py
@@ -0,0 +1,40 @@
+from leapp.actors import Actor
+from leapp.libraries.actor import missinggpgkey
+from leapp.models import (
+ DNFWorkaround,
+ InstalledRPM,
+ TargetUserSpaceInfo,
+ TMPTargetRepositoriesFacts,
+ UsedTargetRepositories
+)
+from leapp.reporting import Report
+from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag
+
+
+class MissingGpgKeysInhibitor(Actor):
+ """
+ Check if all used target repositories have signing gpg keys
+ imported in the existing RPM DB or they are planned to be imported
+
+ Right now, we can not check the package signatures yet, but we can do some
+ best effort estimation based on the gpgkey option in the repofile
+ and content of the existing rpm db.
+
+ Also register the DNFWorkaround to import trusted gpg keys - files provided
+ inside the GPG_CERTS_FOLDER directory.
+
+ In case that leapp is executed with --nogpgcheck, all actions are skipped.
+ """
+
+ name = 'missing_gpg_keys_inhibitor'
+ consumes = (
+ InstalledRPM,
+ TMPTargetRepositoriesFacts,
+ TargetUserSpaceInfo,
+ UsedTargetRepositories,
+ )
+ produces = (DNFWorkaround, Report,)
+ tags = (IPUWorkflowTag, TargetTransactionChecksPhaseTag,)
+
+ def process(self):
+ missinggpgkey.process()
diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py
new file mode 100644
index 00000000..b8b28df2
--- /dev/null
+++ b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/libraries/missinggpgkey.py
@@ -0,0 +1,368 @@
+import json
+import os
+import re
+import shutil
+import tempfile
+
+from six.moves import urllib
+
+from leapp import reporting
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.common import config
+from leapp.libraries.common.config.version import get_source_major_version, get_target_major_version
+from leapp.libraries.stdlib import api, run
+from leapp.models import (
+ DNFWorkaround,
+ InstalledRPM,
+ TargetUserSpaceInfo,
+ TMPTargetRepositoriesFacts,
+ UsedTargetRepositories
+)
+from leapp.utils.deprecation import suppress_deprecation
+
+GPG_CERTS_FOLDER = 'rpm-gpg'
+
+
+def _gpg_show_keys(key_path):
+ """
+ Show keys in given file in version-agnostic manner
+
+ This runs gpg --show-keys (EL8) or gpg --with-fingerprints (EL7)
+ to verify the given file exists, is readable and contains valid
+ OpenPGP key data, which is printed in parsable format (--with-colons).
+ """
+ try:
+ cmd = ['gpg2']
+ # RHEL7 gnupg requires different switches to get the same output
+ if get_source_major_version() == '7':
+ cmd.append('--with-fingerprint')
+ else:
+ cmd.append('--show-keys')
+ cmd += ['--with-colons', key_path]
+ # TODO: discussed, most likely the checked=False will be dropped
+ # and error will be handled in other functions
+ return run(cmd, split=True, checked=False)
+ except OSError as err:
+ # NOTE: this is hypothetic; gnupg2 has to be installed on RHEL 7+
+ error = 'Failed to read fingerprint from GPG key {}: {}'.format(key_path, str(err))
+ api.current_logger().error(error)
+ return {}
+
+
+def _parse_fp_from_gpg(output):
+ """
+ Parse the output of gpg --show-keys --with-colons.
+
+ Return list of 8 characters fingerprints per each gpgkey for the given
+ output from stdlib.run() or None if some error occurred. Either the
+ command return non-zero exit code, the file does not exists, its not
+ readable or does not contain any openpgp data.
+ """
+ if not output or output['exit_code']:
+ return []
+
+ # we are interested in the lines of the output starting with "pub:"
+ # the colons are used for separating the fields in output like this
+ # pub:-:4096:1:999F7CBF38AB71F4:1612983048:::-:::escESC::::::23::0:
+ # ^--------------^ this is the fingerprint we need
+ # ^------^ but RPM version is just the last 8 chars lowercase
+ # Also multiple gpg keys can be stored in the file, so go through all "pub"
+ # lines
+ gpg_fps = []
+ for line in output['stdout']:
+ if not line or not line.startswith('pub:'):
+ continue
+ parts = line.split(':')
+ if len(parts) >= 4 and len(parts[4]) == 16:
+ gpg_fps.append(parts[4][8:].lower())
+ else:
+ api.current_logger().warning(
+ 'Cannot parse the gpg2 output. Line: "{}"'
+ .format(line)
+ )
+
+ return gpg_fps
+
+
+def _read_gpg_fp_from_file(key_path):
+ """
+ Returns the list of public key fingerprints from the given file
+
+ Logs warning in case no OpenPGP data found in the given file or it is not
+ readable for some reason.
+ """
+ res = _gpg_show_keys(key_path)
+ fp = _parse_fp_from_gpg(res)
+ if not fp:
+ error = 'Unable to read OpenPGP keys from {}: {}'.format(key_path, res['stderr'])
+ api.current_logger().error(error)
+ return fp
+
+
+def _get_path_to_gpg_certs():
+ """
+ Get path to the directory with trusted target gpg keys in leapp tree
+ """
+ # XXX This is copy&paste from TargetUserspaceCreator actor.
+ # Potential changes need to happen in both places to keep them in sync.
+ target_major_version = get_target_major_version()
+ target_product_type = config.get_product_type('target')
+ certs_dir = target_major_version
+ # only beta is special in regards to the GPG signing keys
+ if target_product_type == 'beta':
+ certs_dir = '{}beta'.format(target_major_version)
+ return os.path.join(api.get_common_folder_path(GPG_CERTS_FOLDER), certs_dir)
+
+
+def _expand_vars(path):
+ """
+ Expand variables like $releasever and $basearch to the target system version
+ """
+ r = path.replace('$releasever', get_target_major_version())
+ r = r.replace('$basearch', api.current_actor().configuration.architecture)
+ return r
+
+
+def _get_abs_file_path(target_userspace, file_url):
+ """
+ Return the absolute path for file_url if starts with file:///
+
+ If the file_url starts with 'file:///', return its absolute path to
+ the target userspace container, as such a file is supposed to be located
+ on the target system.
+
+ For all other cases, return the originally obtained value.
+ """
+ # TODO(pstodulk): @Jakuje: are we sure the file will be inside the
+ # target userspace container? What if it's a file locally stored by user
+ # and the repository is defined like that as well? Possibly it's just
+ # a corner corner case. I guess it does not have a high prio tbh, but want
+ # to be sure.
+ if not isinstance(target_userspace, TargetUserSpaceInfo):
+ # not need to cover this by tests, it's seatbelt
+ raise ValueError('target_userspace must by TargetUserSpaceInfo object')
+
+ prefix = 'file:///'
+ if not file_url.startswith(prefix):
+ return file_url
+ return os.path.join(target_userspace.path, file_url[len(prefix):])
+
+
+def _pubkeys_from_rpms(installed_rpms):
+ """
+ Return the list of fingerprints of GPG keys in RPM DB
+
+ This function returns short 8 characters fingerprints of trusted GPG keys
+ "installed" in the source OS RPM database. These look like normal packages
+ named "gpg-pubkey" and the fingerprint is present in the version field.
+ """
+ return [pkg.version for pkg in installed_rpms.items if pkg.name == 'gpg-pubkey']
+
+
+def _get_pubkeys(installed_rpms):
+ """
+ Get pubkeys from installed rpms and the trusted directory
+ """
+ pubkeys = _pubkeys_from_rpms(installed_rpms)
+ certs_path = _get_path_to_gpg_certs()
+ for certname in os.listdir(certs_path):
+ key_file = os.path.join(certs_path, certname)
+ fps = _read_gpg_fp_from_file(key_file)
+ if fps:
+ pubkeys += fps
+ # TODO: what about else: ?
+ # The warning is now logged in _read_gpg_fp_from_file. We can raise
+ # the priority of the message or convert it to report though.
+ return pubkeys
+
+
+def _the_nogpgcheck_option_used():
+ return config.get_env('LEAPP_NOGPGCHECK', False) == '1'
+
+
+def _consume_data():
+ try:
+ used_target_repos = next(api.consume(UsedTargetRepositories)).repos
+ except StopIteration:
+ raise StopActorExecutionError(
+ 'Could not check for valid GPG keys', details={'details': 'No UsedTargetRepositories facts'}
+ )
+
+ try:
+ target_repos = next(api.consume(TMPTargetRepositoriesFacts)).repositories
+ except StopIteration:
+ raise StopActorExecutionError(
+ 'Could not check for valid GPG keys', details={'details': 'No TMPTargetRepositoriesFacts facts'}
+ )
+ try:
+ installed_rpms = next(api.consume(InstalledRPM))
+ except StopIteration:
+ raise StopActorExecutionError(
+ 'Could not check for valid GPG keys', details={'details': 'No InstalledRPM facts'}
+ )
+ try:
+ target_userspace = next(api.consume(TargetUserSpaceInfo))
+ except StopIteration:
+ raise StopActorExecutionError(
+ 'Could not check for valid GPG keys', details={'details': 'No TargetUserSpaceInfo facts'}
+ )
+
+ return used_target_repos, target_repos, installed_rpms, target_userspace
+
+
+def _get_repo_gpgkey_urls(repo):
+ """
+ Return the list or repository gpgkeys that should be checked
+
+ If the gpgcheck is disabled for the repo or gpgkey is not specified,
+ return an empty list.
+
+ Returned gpgkeys are URLs with already expanded variables
+ (e.g. $releasever) as gpgkey can contain list of URLs separated by comma
+ or whitespaces.
+ """
+
+ repo_additional = json.loads(repo.additional_fields)
+
+ # TODO does the case matter here?
+ if 'gpgcheck' in repo_additional and repo_additional['gpgcheck'] in ('0', 'False', 'no'):
+ # NOTE: https://dnf.readthedocs.io/en/latest/conf_ref.html#boolean-label
+ # nothing to do with repos with enforced gpgcheck=0
+ return []
+
+ if 'gpgkey' not in repo_additional:
+ # This means rpm will bail out at some time if the key is not present
+ # but we will not know if the needed key is present or not before we will have
+ # the packages at least downloaded
+ # TODO(pstodulk): possibly we should return None if gpgcheck is disabled
+ # and empty list when gpgkey is missing? So we could evaluate that better
+ # outside.
+ api.current_logger().warning(
+ 'The gpgcheck for the {} repository is enabled'
+ ' but gpgkey is not specified. Cannot be checked.'
+ .format(repo.repoid)
+ )
+ return []
+
+ return re.findall(r'[^,\s]+', _expand_vars(repo_additional['gpgkey']))
+
+
+def _report_missing_keys(missing_keys):
+ # TODO(pstodulk): polish the report, use FMT_LIST_SEPARATOR
+ # the list of keys should be mentioned in the summary
+ summary = (
+ "Some of the target repositories require GPG keys that are missing from the current"
+ " RPM DB. Leapp will not be able to verify packages from these repositories during the upgrade process."
+ )
+ hint = (
+ "Please, review the following list and import the GPG keys before "
+ "continuing the upgrade:\n * {}".format('\n * '.join(missing_keys))
+ )
+ reporting.create_report(
+ [
+ reporting.Title("Missing GPG key from target system repository"),
+ reporting.Summary(summary),
+ reporting.Severity(reporting.Severity.HIGH),
+ reporting.Groups([reporting.Groups.REPOSITORY, reporting.Groups.INHIBITOR]),
+ reporting.Remediation(hint=hint),
+ # TODO(pstodulk): @Jakuje: let's sync about it
+ # TODO update external documentation ?
+ # reporting.ExternalLink(
+ # title=(
+ # "Customizing your Red Hat Enterprise Linux "
+ # "in-place upgrade"
+ # ),
+ # url=(
+ # "https://access.redhat.com/articles/4977891/"
+ # "#repos-known-issues"
+ # ),
+ # ),
+ ]
+ )
+
+
+def register_dnfworkaround():
+ api.produce(DNFWorkaround(
+ display_name='import trusted gpg keys to RPM DB',
+ script_path=api.current_actor().get_common_tool_path('importrpmgpgkeys'),
+ script_args=[_get_path_to_gpg_certs()],
+ ))
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def process():
+ """
+ Process the repositories and find missing signing keys
+
+ UsedTargetRepositories doesn't contain baseurl attribute. So gathering
+ them from model TMPTargetRepositoriesFacts.
+ """
+ # when the user decided to ignore gpg signatures on the packages, we can ignore these checks altogether
+ if _the_nogpgcheck_option_used():
+ api.current_logger().warning('The --nogpgcheck option is used: skipping all related checks.')
+ return
+
+ used_target_repos, target_repos, installed_rpms, target_userspace = _consume_data()
+
+ target_repo_id_to_repositories_facts_map = {
+ repo.repoid: repo
+ for repofile in target_repos
+ for repo in repofile.data
+ }
+
+ # These are used only for getting the installed gpg-pubkey "packages"
+ pubkeys = _get_pubkeys(installed_rpms)
+ missing_keys = list()
+ processed_gpgkey_urls = set()
+ tmpdir = None
+ for repoid in used_target_repos:
+ if repoid.repoid not in target_repo_id_to_repositories_facts_map:
+ api.current_logger().warning('The target repository {} metadata not available'.format(repoid.repoid))
+ continue
+
+ repo = target_repo_id_to_repositories_facts_map[repoid.repoid]
+ for gpgkey_url in _get_repo_gpgkey_urls(repo):
+ if gpgkey_url in processed_gpgkey_urls:
+ continue
+ processed_gpgkey_urls.add(gpgkey_url)
+
+ if gpgkey_url.startswith('file:///'):
+ key_file = _get_abs_file_path(target_userspace, gpgkey_url)
+ elif gpgkey_url.startswith('http://') or gpgkey_url.startswith('https://'):
+ # delay creating temporary directory until we need it
+ tmpdir = tempfile.mkdtemp() if tmpdir is None else tmpdir
+ # FIXME: what to do with dummy? it's fd, that should be closed also
+ dummy, tmp_file = tempfile.mkstemp(dir=tmpdir)
+ try:
+ urllib.request.urlretrieve(gpgkey_url, tmp_file)
+ key_file = tmp_file
+ except urllib.error.URLError as err:
+ # TODO(pstodulk): create report for the repoids which cannot be checked?
+ # (no inhibitor)
+ api.current_logger().warning(
+ 'Failed to download the gpgkey {}: {}'.format(gpgkey_url, str(err)))
+ continue
+ else:
+ # TODO: report?
+ api.current_logger().error(
+ 'Skipping unknown protocol for gpgkey {}'.format(gpgkey_url))
+ continue
+ fps = _read_gpg_fp_from_file(key_file)
+ if not fps:
+ # TODO: for now. I think it should be treated better
+ api.current_logger().warning(
+ "Cannot get any gpg key from the file: {}".format(gpgkey_url)
+ )
+ continue
+ for fp in fps:
+ if fp not in pubkeys and gpgkey_url not in missing_keys:
+ missing_keys.append(_get_abs_file_path(target_userspace, gpgkey_url))
+
+ if tmpdir:
+ # clean up temporary directory with downloaded gpg keys
+ shutil.rmtree(tmpdir)
+
+ if missing_keys:
+ _report_missing_keys(missing_keys)
+
+ register_dnfworkaround()
diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/component_test_missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/component_test_missinggpgkey.py
new file mode 100644
index 00000000..5af5f026
--- /dev/null
+++ b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/component_test_missinggpgkey.py
@@ -0,0 +1,522 @@
+import pytest
+from six.moves.urllib.error import URLError
+
+from leapp import reporting
+from leapp.exceptions import StopActorExecutionError
+from leapp.libraries.actor.missinggpgkey import _pubkeys_from_rpms, process
+from leapp.libraries.common.testutils import create_report_mocked, CurrentActorMocked, logger_mocked, produce_mocked
+from leapp.libraries.stdlib import api
+from leapp.models import (
+ InstalledRPM,
+ Report,
+ RepositoriesFacts,
+ RepositoryData,
+ RepositoryFile,
+ RPM,
+ TargetUserSpaceInfo,
+ TMPTargetRepositoriesFacts,
+ UsedTargetRepositories,
+ UsedTargetRepository
+)
+from leapp.utils.deprecation import suppress_deprecation
+
+# Note, that this is not a real component test as described in the documentation,
+# but basically unit test calling the "main" function process() to simulate the
+# whole process as I was initially advised not to use these component tests.
+
+
+def _get_test_installedrpm_no_my_key():
+ return [
+ RPM(
+ name='rpm',
+ version='4.16.1.3',
+ release='17.el9',
+ epoch='0',
+ packager='Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>',
+ arch='x86_64',
+ pgpsig='RSA/SHA256, Mon 08 Aug 2022 09:10:15 AM UTC, Key ID 199e2f91fd431d51',
+ repository='BaseOS',
+ ),
+ RPM(
+ name='gpg-pubkey',
+ version='fd431d51',
+ release='4ae0493b',
+ epoch='0',
+ packager='Red Hat, Inc. (release key 2) <security@redhat.com>',
+ arch='noarch',
+ pgpsig=''
+ ),
+ RPM(
+ name='gpg-pubkey',
+ version='5a6340b3',
+ release='6229229e',
+ epoch='0',
+ packager='Red Hat, Inc. (auxiliary key 3) <security@redhat.com>',
+ arch='noarch',
+ pgpsig=''
+ ),
+ ]
+
+
+def _get_test_installedrpm():
+ return InstalledRPM(
+ items=[
+ RPM(
+ name='gpg-pubkey',
+ version='3228467c',
+ release='613798eb',
+ epoch='0',
+ packager='edora (epel9) <epel@fedoraproject.org>',
+ arch='noarch',
+ pgpsig=''
+ ),
+ ] + _get_test_installedrpm_no_my_key(),
+ )
+
+
+def _get_test_targuserspaceinfo(path='nopath'):
+ return TargetUserSpaceInfo(
+ path=path,
+ scratch='',
+ mounts='',
+ )
+
+
+def _get_test_usedtargetrepositories_list():
+ return [
+ UsedTargetRepository(
+ repoid='BaseOS',
+ ),
+ UsedTargetRepository(
+ repoid='AppStream',
+ ),
+ UsedTargetRepository(
+ repoid='MyAnotherRepo',
+ ),
+ ]
+
+
+def _get_test_usedtargetrepositories():
+ return UsedTargetRepositories(
+ repos=_get_test_usedtargetrepositories_list()
+ )
+
+
+def _get_test_target_repofile():
+ return RepositoryFile(
+ file='/etc/yum.repos.d/target_rhel.repo',
+ data=[
+ RepositoryData(
+ repoid='BaseOS',
+ name="RHEL BaseOS repository",
+ baseurl="/whatever/",
+ enabled=True,
+ additional_fields='{"gpgkey":"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"}'
+ ),
+ RepositoryData(
+ repoid='AppStream',
+ name="RHEL AppStream repository",
+ baseurl="/whatever/",
+ enabled=True,
+ additional_fields='{"gpgkey":"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release"}'
+ ),
+ ],
+ )
+
+
+def _get_test_target_repofile_additional():
+ return RepositoryFile(
+ file='/etc/yum.repos.d/my_target_rhel.repo',
+ data=[
+ RepositoryData(
+ repoid='MyRepo',
+ name="My repository",
+ baseurl="/whatever/",
+ enabled=False,
+ ),
+ RepositoryData(
+ repoid='MyAnotherRepo',
+ name="My another repository",
+ baseurl="/whatever/",
+ enabled=True,
+ additional_fields='{"gpgkey":"file:///etc/pki/rpm-gpg/RPM-GPG-KEY-my-release"}'
+ ),
+ ],
+ )
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def _get_test_tmptargetrepositoriesfacts():
+ return TMPTargetRepositoriesFacts(
+ repositories=[
+ _get_test_target_repofile(),
+ _get_test_target_repofile_additional(),
+ ],
+ )
+
+
+def test_perform_nogpgcheck(monkeypatch):
+ """
+ Executes the "main" function with the --nogpgcheck commandline switch
+
+ This test should skip any checks and just log a message that no checks were executed
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ envars={'LEAPP_NOGPGCHECK': '1'},
+ msgs=[
+ _get_test_installedrpm(),
+ _get_test_usedtargetrepositories(),
+ _get_test_tmptargetrepositoriesfacts(),
+ ],
+ ))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+
+ process()
+
+ assert api.produce.called == 0
+ assert len(api.current_logger.warnmsg) == 1
+ assert '--nogpgcheck option is used' in api.current_logger.warnmsg[0]
+
+
+@pytest.mark.parametrize('msgs', [
+ [],
+ [_get_test_installedrpm],
+ [_get_test_usedtargetrepositories],
+ [_get_test_tmptargetrepositoriesfacts],
+ # These are just incomplete lists of required facts
+ [_get_test_installedrpm(), _get_test_usedtargetrepositories()],
+ [_get_test_usedtargetrepositories(), _get_test_tmptargetrepositoriesfacts()],
+ [_get_test_installedrpm(), _get_test_tmptargetrepositoriesfacts()],
+])
+def test_perform_missing_facts(monkeypatch, msgs):
+ """
+ Executes the "main" function with missing required facts
+
+ The missing facts (either RPM information, Target Repositories or their facts) cause
+ the StopActorExecutionError excepction. But this should be rare as the required facts
+ are clearly defined in the actor interface.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(msgs=msgs))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ # TODO: the gpg call should be mocked
+
+ with pytest.raises(StopActorExecutionError):
+ process()
+ # nothing produced
+ assert api.produce.called == 0
+ # not skipped by --nogpgcheck
+ assert not api.current_logger.warnmsg
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def _get_test_tmptargetrepositoriesfacts_partial():
+ return [
+ _get_test_installedrpm(),
+ _get_test_usedtargetrepositories(),
+ TMPTargetRepositoriesFacts(
+ repositories=[
+ _get_test_target_repofile(),
+ # missing MyAnotherRepo
+ ]
+ )
+ ]
+
+
+def _gpg_show_keys_mocked(key_path):
+ """
+ Get faked output from gpg reading keys.
+
+ This is needed to get away from dependency on the filesystem
+ """
+ if key_path == '/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release':
+ return {
+ 'stdout': [
+ 'pub:-:4096:1:199E2F91FD431D51:1256212795:::-:::scSC::::::23::0:',
+ 'fpr:::::::::567E347AD0044ADE55BA8A5F199E2F91FD431D51:',
+ ('uid:-::::1256212795::DC1CAEC7997B3575101BB0FCAAC6191792660D8F::'
+ 'Red Hat, Inc. (release key 2) <security@redhat.com>::::::::::0:'),
+ 'pub:-:4096:1:5054E4A45A6340B3:1646863006:::-:::scSC::::::23::0:',
+ 'fpr:::::::::7E4624258C406535D56D6F135054E4A45A6340B3:',
+ ('uid:-::::1646863006::DA7F68E3872D6E7BDCE05225E7EB5F3ACDD9699F::'
+ 'Red Hat, Inc. (auxiliary key 3) <security@redhat.com>::::::::::0:'),
+ ],
+ 'stderr': (),
+ 'exit_code': 0,
+ }
+ if key_path == '/etc/pki/rpm-gpg/RPM-GPG-KEY-my-release': # actually epel9 key
+ return {
+ 'stdout': [
+ 'pub:-:4096:1:8A3872BF3228467C:1631033579:::-:::escESC::::::23::0:',
+ 'fpr:::::::::FF8AD1344597106ECE813B918A3872BF3228467C:',
+ ('uid:-::::1631033579::3EED52B2BDE50880047DB883C87B0FCAE458D111::'
+ 'Fedora (epel9) <epel@fedoraproject.org>::::::::::0:'),
+ ],
+ 'stderr': (),
+ 'exit_code': 0,
+ }
+
+ return {
+ 'stdout': [
+ 'pub:-:4096:1:F55AD3FB5323552A:1628617948:::-:::escESC::::::23::0:',
+ 'fpr:::::::::ACB5EE4E831C74BB7C168D27F55AD3FB5323552A:',
+ ('uid:-::::1628617948::4830BB019772421B89ABD0BBE245B89C73BF053F::'
+ 'Fedora (37) <fedora-37-primary@fedoraproject.org>::::::::::0:'),
+ ],
+ 'stderr': (),
+ 'exit_code': 0,
+ }
+
+
+def _get_pubkeys_mocked(installed_rpms):
+ """
+ This skips getting fps from files in container for simplification
+ """
+ return _pubkeys_from_rpms(installed_rpms)
+
+
+def test_perform_missing_some_repo_facts(monkeypatch):
+ """
+ Executes the "main" function with missing repositories facts
+
+ This is misalignment in the provided facts UsedTargetRepositories and TMPTargetRepositoriesFacts,
+ where we miss some metadata that are required by the first message.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ msgs=_get_test_tmptargetrepositoriesfacts_partial())
+ )
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._gpg_show_keys', _gpg_show_keys_mocked)
+
+ with pytest.raises(StopActorExecutionError):
+ process()
+ assert api.produce.called == 0
+ assert reporting.create_report.called == 0
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def _get_test_tmptargetrepositoriesfacts_https_unused():
+ return [
+ _get_test_targuserspaceinfo(),
+ _get_test_installedrpm(),
+ _get_test_usedtargetrepositories(),
+ TMPTargetRepositoriesFacts(
+ repositories=[
+ _get_test_target_repofile(),
+ _get_test_target_repofile_additional(),
+ RepositoryFile(
+ file='/etc/yum.repos.d/internet.repo',
+ data=[
+ RepositoryData(
+ repoid='ExternalRepo',
+ name="External repository",
+ baseurl="/whatever/path",
+ enabled=True,
+ additional_fields='{"gpgkey":"https://example.com/rpm-gpg/key.gpg"}',
+ ),
+ ],
+ )
+ ],
+ ),
+ ]
+
+
+def test_perform_https_gpgkey_unused(monkeypatch):
+ """
+ Executes the "main" function with repositories providing keys over internet
+
+ The external repository is not listed in UsedTargetRepositories so the repository
+ is not checked and we should not get any error here.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ msgs=_get_test_tmptargetrepositoriesfacts_https_unused()
+ ))
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._gpg_show_keys', _gpg_show_keys_mocked)
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._get_pubkeys', _get_pubkeys_mocked)
+
+ process()
+ assert not api.current_logger.warnmsg
+ # This is the DNFWorkaround
+ assert api.produce.called == 1
+ assert reporting.create_report.called == 1
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def get_test_tmptargetrepositoriesfacts_https():
+ return (
+ _get_test_targuserspaceinfo(),
+ _get_test_installedrpm(),
+ UsedTargetRepositories(
+ repos=_get_test_usedtargetrepositories_list() + [
+ UsedTargetRepository(
+ repoid='ExternalRepo',
+ ),
+ ]
+ ),
+ TMPTargetRepositoriesFacts(
+ repositories=[
+ _get_test_target_repofile(),
+ _get_test_target_repofile_additional(),
+ RepositoryFile(
+ file='/etc/yum.repos.d/internet.repo',
+ data=[
+ RepositoryData(
+ repoid='ExternalRepo',
+ name="External repository",
+ baseurl="/whatever/path",
+ enabled=True,
+ additional_fields='{"gpgkey":"https://example.com/rpm-gpg/key.gpg"}',
+ ),
+ ],
+ )
+ ],
+ ),
+ )
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def get_test_tmptargetrepositoriesfacts_ftp():
+ return (
+ _get_test_targuserspaceinfo(),
+ _get_test_installedrpm(),
+ UsedTargetRepositories(
+ repos=_get_test_usedtargetrepositories_list() + [
+ UsedTargetRepository(
+ repoid='ExternalRepo',
+ ),
+ ]
+ ),
+ TMPTargetRepositoriesFacts(
+ repositories=[
+ _get_test_target_repofile(),
+ _get_test_target_repofile_additional(),
+ RepositoryFile(
+ file='/etc/yum.repos.d/internet.repo',
+ data=[
+ RepositoryData(
+ repoid='ExternalRepo',
+ name="External repository",
+ baseurl="/whatever/path",
+ enabled=True,
+ additional_fields='{"gpgkey":"ftp://example.com/rpm-gpg/key.gpg"}',
+ ),
+ ],
+ )
+ ],
+ ),
+ )
+
+
+def _urlretrive_mocked(url, filename=None, reporthook=None, data=None):
+ return filename
+
+
+def test_perform_https_gpgkey(monkeypatch):
+ """
+ Executes the "main" function with repositories providing keys over internet
+
+ This produces an report.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ msgs=get_test_tmptargetrepositoriesfacts_https())
+ )
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._gpg_show_keys', _gpg_show_keys_mocked)
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._get_pubkeys', _get_pubkeys_mocked)
+ monkeypatch.setattr('six.moves.urllib.request.urlretrieve', _urlretrive_mocked)
+
+ process()
+ # This is the DNFWorkaround
+ assert api.produce.called == 1
+ assert reporting.create_report.called == 1
+
+
+def _urlretrive_mocked_urlerror(url, filename=None, reporthook=None, data=None):
+ raise URLError('error')
+
+
+def test_perform_https_gpgkey_urlerror(monkeypatch):
+ """
+ Executes the "main" function with repositories providing keys over internet
+
+ This results in warning message printed. Other than that, no report is still produced.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ msgs=get_test_tmptargetrepositoriesfacts_https())
+ )
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._gpg_show_keys', _gpg_show_keys_mocked)
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._get_pubkeys', _get_pubkeys_mocked)
+ monkeypatch.setattr('six.moves.urllib.request.urlretrieve', _urlretrive_mocked_urlerror)
+
+ process()
+ assert len(api.current_logger.warnmsg) == 1
+ assert 'Failed to download the gpgkey https://example.com/rpm-gpg/key.gpg:' in api.current_logger.warnmsg[0]
+ # This is the DNFWorkaround
+ assert api.produce.called == 1
+ assert reporting.create_report.called == 1
+
+
+def test_perform_ftp_gpgkey(monkeypatch):
+ """
+ Executes the "main" function with repositories providing keys over internet
+
+ This results in error message printed. Other than that, no report is still produced.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ msgs=get_test_tmptargetrepositoriesfacts_ftp())
+ )
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._gpg_show_keys', _gpg_show_keys_mocked)
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._get_pubkeys', _get_pubkeys_mocked)
+
+ process()
+ assert len(api.current_logger.errmsg) == 1
+ assert 'Skipping unknown protocol for gpgkey ftp://example.com/rpm-gpg/key.gpg' in api.current_logger.errmsg[0]
+ # This is the DNFWorkaround
+ assert api.produce.called == 1
+ assert reporting.create_report.called == 1
+
+
+@suppress_deprecation(TMPTargetRepositoriesFacts)
+def get_test_data_missing_key():
+ return [
+ _get_test_targuserspaceinfo(),
+ InstalledRPM(items=_get_test_installedrpm_no_my_key()),
+ _get_test_usedtargetrepositories(),
+ _get_test_tmptargetrepositoriesfacts(),
+ ]
+
+
+def test_perform_report(monkeypatch):
+ """
+ Executes the "main" function with missing keys
+
+ This should result in report outlining what key mentioned in target repositories is missing.
+ """
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(
+ msgs=get_test_data_missing_key())
+ )
+ monkeypatch.setattr(api, 'produce', produce_mocked())
+ monkeypatch.setattr(api, 'current_logger', logger_mocked())
+ monkeypatch.setattr(reporting, 'create_report', create_report_mocked())
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._gpg_show_keys', _gpg_show_keys_mocked)
+ monkeypatch.setattr('leapp.libraries.actor.missinggpgkey._get_pubkeys', _get_pubkeys_mocked)
+
+ process()
+ assert not api.current_logger.warnmsg
+ # This is the DNFWorkaround
+ assert api.produce.called == 1
+ assert reporting.create_report.called == 1
diff --git a/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/unit_test_missinggpgkey.py b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/unit_test_missinggpgkey.py
new file mode 100644
index 00000000..8a46f97b
--- /dev/null
+++ b/repos/system_upgrade/common/actors/missinggpgkeysinhibitor/tests/unit_test_missinggpgkey.py
@@ -0,0 +1,209 @@
+import os
+import shutil
+import sys
+import tempfile
+
+import distro
+import pytest
+
+from leapp.libraries.actor.missinggpgkey import (
+ _expand_vars,
+ _get_path_to_gpg_certs,
+ _get_pubkeys,
+ _get_repo_gpgkey_urls,
+ _gpg_show_keys,
+ _parse_fp_from_gpg,
+ _pubkeys_from_rpms
+)
+from leapp.libraries.common.testutils import CurrentActorMocked
+from leapp.libraries.stdlib import api
+from leapp.models import InstalledRPM, RepositoryData, RPM
+
+
+def is_rhel7():
+ return int(distro.major_version()) < 8
+
+
+def test_gpg_show_keys(current_actor_context, monkeypatch):
+ src = '7.9' if is_rhel7() else '8.6'
+ current_actor = CurrentActorMocked(src_ver=src)
+ monkeypatch.setattr(api, 'current_actor', current_actor)
+
+ # python2 compatibility :/
+ dirpath = tempfile.mkdtemp()
+
+ # using GNUPGHOME env should avoid gnupg modifying the system
+ os.environ['GNUPGHOME'] = dirpath
+
+ try:
+ # non-existing file
+ non_existent_path = os.path.join(dirpath, 'nonexistent')
+ res = _gpg_show_keys(non_existent_path)
+ if is_rhel7():
+ err_msg = "gpg: can't open `{}'".format(non_existent_path)
+ else:
+ err_msg = "gpg: can't open '{}': No such file or directory\n".format(non_existent_path)
+ assert not res['stdout']
+ assert err_msg in res['stderr']
+ assert res['exit_code'] == 2
+
+ fp = _parse_fp_from_gpg(res)
+ assert fp == []
+
+ # no gpg data found
+ no_key_path = os.path.join(dirpath, "no_key")
+ with open(no_key_path, "w") as f:
+ f.write('test')
+
+ res = _gpg_show_keys(no_key_path)
+ if is_rhel7():
+ err_msg = ('gpg: no valid OpenPGP data found.\n'
+ 'gpg: processing message failed: Unknown system error\n')
+ else:
+ err_msg = 'gpg: no valid OpenPGP data found.\n'
+ assert not res['stdout']
+ assert res['stderr'] == err_msg
+ assert res['exit_code'] == 2
+
+ fp = _parse_fp_from_gpg(res)
+ assert fp == []
+
+ # with some test data now -- rhel9 release key
+ # rhel9_key_path = os.path.join(api.get_common_folder_path('rpm-gpg'), '9')
+ cur_dir = os.path.dirname(os.path.abspath(__file__))
+ rhel9_key_path = os.path.join(cur_dir, '..', '..', '..', 'files', 'rpm-gpg', '9',
+ 'RPM-GPG-KEY-redhat-release')
+ res = _gpg_show_keys(rhel9_key_path)
+ finally:
+ shutil.rmtree(dirpath)
+
+ if is_rhel7():
+ assert len(res['stdout']) == 4
+ assert res['stdout'][0] == ('pub:-:4096:1:199E2F91FD431D51:1256212795:::-:'
+ 'Red Hat, Inc. (release key 2) <security@redhat.com>:')
+ assert res['stdout'][1] == 'fpr:::::::::567E347AD0044ADE55BA8A5F199E2F91FD431D51:'
+ assert res['stdout'][2] == ('pub:-:4096:1:5054E4A45A6340B3:1646863006:::-:'
+ 'Red Hat, Inc. (auxiliary key 3) <security@redhat.com>:')
+ assert res['stdout'][3] == 'fpr:::::::::7E4624258C406535D56D6F135054E4A45A6340B3:'
+ else:
+ assert len(res['stdout']) == 6
+ assert res['stdout'][0] == 'pub:-:4096:1:199E2F91FD431D51:1256212795:::-:::scSC::::::23::0:'
+ assert res['stdout'][1] == 'fpr:::::::::567E347AD0044ADE55BA8A5F199E2F91FD431D51:'
+ assert res['stdout'][2] == ('uid:-::::1256212795::DC1CAEC7997B3575101BB0FCAAC6191792660D8F::'
+ 'Red Hat, Inc. (release key 2) <security@redhat.com>::::::::::0:')
+ assert res['stdout'][3] == 'pub:-:4096:1:5054E4A45A6340B3:1646863006:::-:::scSC::::::23::0:'
+ assert res['stdout'][4] == 'fpr:::::::::7E4624258C406535D56D6F135054E4A45A6340B3:'
+ assert res['stdout'][5] == ('uid:-::::1646863006::DA7F68E3872D6E7BDCE05225E7EB5F3ACDD9699F::'
+ 'Red Hat, Inc. (auxiliary key 3) <security@redhat.com>::::::::::0:')
+
+ err = '{}/trustdb.gpg: trustdb created'.format(dirpath)
+ assert err in res['stderr']
+ assert res['exit_code'] == 0
+
+ # now, parse the output too
+ fp = _parse_fp_from_gpg(res)
+ assert fp == ['fd431d51', '5a6340b3']
+
+
+@pytest.mark.parametrize('res, exp', [
+ ({'exit_code': 2, 'stdout': '', 'stderr': ''}, []),
+ ({'exit_code': 2, 'stdout': '', 'stderr': 'bash: gpg2: command not found...'}, []),
+ ({'exit_code': 0, 'stdout': 'Some other output', 'stderr': ''}, []),
+ ({'exit_code': 0, 'stdout': ['Some other output', 'other line'], 'stderr': ''}, []),
+ ({'exit_code': 0, 'stdout': ['pub:-:4096:1:199E2F91FD431D:'], 'stderr': ''}, []),
+ ({'exit_code': 0, 'stdout': ['pub:-:4096:1:5054E4A45A6340B3:1..'], 'stderr': ''}, ['5a6340b3']),
+])
+def test_parse_fp_from_gpg(res, exp):
+ fp = _parse_fp_from_gpg(res)
+ assert fp == exp
+
+
+@pytest.mark.parametrize('target, product_type, exp', [
+ ('8.6', 'beta', '../../files/rpm-gpg/8beta'),
+ ('8.8', 'htb', '../../files/rpm-gpg/8'),
+ ('9.0', 'beta', '../../files/rpm-gpg/9beta'),
+ ('9.2', 'ga', '../../files/rpm-gpg/9'),
+])
+def test_get_path_to_gpg_certs(current_actor_context, monkeypatch, target, product_type, exp):
+ current_actor = CurrentActorMocked(dst_ver=target,
+ envars={'LEAPP_DEVEL_TARGET_PRODUCT_TYPE': product_type})
+ monkeypatch.setattr(api, 'current_actor', current_actor)
+
+ p = _get_path_to_gpg_certs()
+ assert p == exp
+
+
+@pytest.mark.parametrize('data, exp', [
+ ('bare string', 'bare string'),
+ ('with dollar$$$', 'with dollar$$$'),
+ ('path/with/$basearch/something', 'path/with/x86_64/something'),
+ ('path/with/$releasever/something', 'path/with/9/something'),
+ ('path/with/$releasever/$basearch', 'path/with/9/x86_64'),
+ ('path/with/$releasever/$basearch', 'path/with/9/x86_64'),
+])
+def test_expand_vars(monkeypatch, data, exp):
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='9.1')) # x86_64 arch is default
+ res = _expand_vars(data)
+ assert res == exp
+
+
+def _get_test_installed_rmps():
+ return InstalledRPM(
+ items=[
+ RPM(name='gpg-pubkey',
+ version='9570ff31',
+ release='5e3006fb',
+ epoch='0',
+ packager='Fedora (33) <fedora-33-primary@fedoraproject.org>',
+ arch='noarch',
+ pgpsig=''),
+ RPM(name='rpm',
+ version='4.17.1',
+ release='3.fc35',
+ epoch='0',
+ packager='Fedora Project',
+ arch='x86_64',
+ pgpsig='RSA/SHA256, Tue 02 Aug 2022 03:12:43 PM CEST, Key ID db4639719867c58f'),
+ ],
+ )
+
+
+def test_pubkeys_from_rpms():
+ installed_rpm = _get_test_installed_rmps()
+ assert _pubkeys_from_rpms(installed_rpm) == ['9570ff31']
+
+
+# @pytest.mark.parametrize('target, product_type, exp', [
+# ('8.6', 'beta', ['F21541EB']),
+# ('8.8', 'htb', ['FD431D51', 'D4082792']), # ga
+# ('9.0', 'beta', ['F21541EB']),
+# ('9.2', 'ga', ['FD431D51', '5A6340B3']),
+# ])
+# Def test_get_pubkeys(current_actor_context, monkeypatch, target, product_type, exp):
+# current_actor = CurrentActorMocked(dst_ver=target,
+# envars={'LEAPP_DEVEL_TARGET_PRODUCT_TYPE': product_type})
+# monkeypatch.setattr(api, 'current_actor', current_actor)
+# installed_rpm = _get_test_installed_rmps()
+#
+# p = _get_pubkeys(installed_rpm)
+# assert '9570ff31' in p
+# for x in exp:
+# assert x in p
+
+
+@pytest.mark.parametrize('repo, exp', [
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgcheck":0}'), []),
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgcheck":"no"}'), []),
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgcheck":"False"}'), []),
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgkey":"dummy"}'), ["dummy"]),
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgkey":"dummy, another"}'),
+ ["dummy", "another"]),
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgkey":"dummy\\nanother"}'),
+ ["dummy", "another"]),
+ (RepositoryData(repoid='dummy', name='name', additional_fields='{"gpgkey":"$releasever"}'),
+ ["9"]),
+])
+def test_get_repo_gpgkey_urls(monkeypatch, repo, exp):
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='9.1'))
+ keys = _get_repo_gpgkey_urls(repo)
+ assert keys == exp
diff --git a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py
index 0415f0fe..f2391ee8 100644
--- a/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py
+++ b/repos/system_upgrade/common/actors/targetuserspacecreator/libraries/userspacegen.py
@@ -52,6 +52,7 @@ from leapp.utils.deprecation import suppress_deprecation
# Issue: #486
PROD_CERTS_FOLDER = 'prod-certs'
+GPG_CERTS_FOLDER = 'rpm-gpg'
PERSISTENT_PACKAGE_CACHE_DIR = '/var/lib/leapp/persistent_package_cache'
@@ -136,32 +137,65 @@ def _backup_to_persistent_package_cache(userspace_dir):
target_context.copytree_from('/var/cache/dnf', PERSISTENT_PACKAGE_CACHE_DIR)
+def _the_nogpgcheck_option_used():
+ return get_env('LEAPP_NOGPGCHECK', False) == '1'
+
+
+def _get_path_to_gpg_certs(target_major_version):
+ target_product_type = get_product_type('target')
+ certs_dir = target_major_version
+ # only beta is special in regards to the GPG signing keys
+ if target_product_type == 'beta':
+ certs_dir = '{}beta'.format(target_major_version)
+ return os.path.join(api.get_common_folder_path(GPG_CERTS_FOLDER), certs_dir)
+
+
+def _import_gpg_keys(context, install_root_dir, target_major_version):
+ certs_path = _get_path_to_gpg_certs(target_major_version)
+ # Import the RHEL X+1 GPG key to be able to verify the installation of initial packages
+ try:
+ # Import also any other keys provided by the customer in the same directory
+ for certname in os.listdir(certs_path):
+ cmd = ['rpm', '--root', install_root_dir, '--import', os.path.join(certs_path, certname)]
+ context.call(cmd, callback_raw=utils.logging_handler)
+ except CalledProcessError as exc:
+ raise StopActorExecutionError(
+ message=(
+ 'Unable to import GPG certificates to install RHEL {} userspace packages.'
+ .format(target_major_version)
+ ),
+ details={'details': str(exc), 'stderr': exc.stderr}
+ )
+
+
def prepare_target_userspace(context, userspace_dir, enabled_repos, packages):
"""
Implement the creation of the target userspace.
"""
_backup_to_persistent_package_cache(userspace_dir)
- target_major_version = get_target_major_version()
run(['rm', '-rf', userspace_dir])
_create_target_userspace_directories(userspace_dir)
- with mounting.BindMount(
- source=userspace_dir, target=os.path.join(context.base_dir, 'el{}target'.format(target_major_version))
- ):
+
+ target_major_version = get_target_major_version()
+ install_root_dir = '/el{}target'.format(target_major_version)
+ with mounting.BindMount(source=userspace_dir, target=os.path.join(context.base_dir, install_root_dir.lstrip('/'))):
_restore_persistent_package_cache(userspace_dir)
+ if not _the_nogpgcheck_option_used():
+ _import_gpg_keys(context, install_root_dir, target_major_version)
repos_opt = [['--enablerepo', repo] for repo in enabled_repos]
repos_opt = list(itertools.chain(*repos_opt))
- cmd = ['dnf',
- 'install',
- '-y',
- '--nogpgcheck',
- '--setopt=module_platform_id=platform:el{}'.format(target_major_version),
- '--setopt=keepcache=1',
- '--releasever', api.current_actor().configuration.version.target,
- '--installroot', '/el{}target'.format(target_major_version),
- '--disablerepo', '*'
- ] + repos_opt + packages
+ cmd = ['dnf', 'install', '-y']
+ if _the_nogpgcheck_option_used():
+ cmd.append('--nogpgcheck')
+ cmd += [
+ '--setopt=module_platform_id=platform:el{}'.format(target_major_version),
+ '--setopt=keepcache=1',
+ '--releasever', api.current_actor().configuration.version.target,
+ '--installroot', install_root_dir,
+ '--disablerepo', '*'
+ ] + repos_opt + packages
if config.is_verbose():
cmd.append('-v')
if rhsm.skip_rhsm():
diff --git a/repos/system_upgrade/common/files/rpm-gpg/8/RPM-GPG-KEY-redhat-release b/repos/system_upgrade/common/files/rpm-gpg/8/RPM-GPG-KEY-redhat-release
new file mode 100644
index 00000000..6744de9e
--- /dev/null
+++ b/repos/system_upgrade/common/files/rpm-gpg/8/RPM-GPG-KEY-redhat-release
@@ -0,0 +1,89 @@
+The following public key can be used to verify RPM packages built and
+signed by Red Hat, Inc. This key is used for packages in Red Hat
+products shipped after November 2009, and for all updates to those
+products.
+
+Questions about this key should be sent to security@redhat.com.
+
+pub 4096R/FD431D51 2009-10-22 Red Hat, Inc. (release key 2) <security@redhat.com>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.2.6 (GNU/Linux)
+
+mQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF
+0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF
+0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c
+u7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh
+XGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H
+5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW
+9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj
+/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1
+PcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY
+HVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF
+buhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB
+tDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0
+LmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK
+CRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC
+2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf
+C/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5
+un3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E
+0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE
+IGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh
+8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL
+Ght5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki
+JUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25
+OFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq
+dzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==
+=zbHE
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+vt61kpkXoRGxjTlc6h+69aidSg==
+=ls8J
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/repos/system_upgrade/common/files/rpm-gpg/8beta/RPM-GPG-KEY-redhat-beta b/repos/system_upgrade/common/files/rpm-gpg/8beta/RPM-GPG-KEY-redhat-beta
new file mode 100644
index 00000000..1efd1509
--- /dev/null
+++ b/repos/system_upgrade/common/files/rpm-gpg/8beta/RPM-GPG-KEY-redhat-beta
@@ -0,0 +1,29 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.2.6 (GNU/Linux)
+
+mQINBEmkAzABEAC2/c7bP1lHQ3XScxbIk0LQWe1YOiibQBRLwf8Si5PktgtuPibT
+kKpZjw8p4D+fM7jD1WUzUE0X7tXg2l/eUlMM4dw6XJAQ1AmEOtlwSg7rrMtTvM0A
+BEtI7Km6fC6sU6RtBMdcqD1cH/6dbsfh8muznVA7UlX+PRBHVzdWzj6y8h84dBjo
+gzcbYu9Hezqgj/lLzicqsSZPz9UdXiRTRAIhp8V30BD8uRaaa0KDDnD6IzJv3D9P
+xQWbFM4Z12GN9LyeZqmD7bpKzZmXG/3drvfXVisXaXp3M07t3NlBa3Dt8NFIKZ0D
+FRXBz5bvzxRVmdH6DtkDWXDPOt+Wdm1rZrCOrySFpBZQRpHw12eo1M1lirANIov7
+Z+V1Qh/aBxj5EUu32u9ZpjAPPNtQF6F/KjaoHHHmEQAuj4DLex4LY646Hv1rcv2i
+QFuCdvLKQGSiFBrfZH0j/IX3/0JXQlZzb3MuMFPxLXGAoAV9UP/Sw/WTmAuTzFVm
+G13UYFeMwrToOiqcX2VcK0aC1FCcTP2z4JW3PsWvU8rUDRUYfoXovc7eg4Vn5wHt
+0NBYsNhYiAAf320AUIHzQZYi38JgVwuJfFu43tJZE4Vig++RQq6tsEx9Ftz3EwRR
+fJ9z9mEvEiieZm+vbOvMvIuimFVPSCmLH+bI649K8eZlVRWsx3EXCVb0nQARAQAB
+tDBSZWQgSGF0LCBJbmMuIChiZXRhIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0LmNv
+bT6JAjYEEwECACAFAkpSM+cCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCT
+ioDK8hVB6/9tEAC0+KmzeKceXQ/GTUoU6jy9vtkFCFrmv+c7ol4XpdTt0QhqBOwy
+6m2mKWwmm8KfYfy0cADQ4y/EcoXl7FtFBwYmkCuEQGXhTDn9DvVjhooIq59LEMBQ
+OW879RwwzRIZ8ebbjMUjDPF5MfPQqP2LBu9N4KvXlZp4voykwuuaJ+cbsKZR6pZ6
+0RQKPHKP+NgUFC0fff7XY9cuOZZWFAeKRhLN2K7bnRHKxp+kELWb6R9ZfrYwZjWc
+MIPbTd1khE53L4NTfpWfAnJRtkPSDOKEGVlVLtLq4HEAxQt07kbslqISRWyXER3u
+QOJj64D1ZiIMz6t6uZ424VE4ry9rBR0Jz55cMMx5O/ni9x3xzFUgH8Su2yM0r3jE
+Rf24+tbOaPf7tebyx4OKe+JW95hNVstWUDyGbs6K9qGfI/pICuO1nMMFTo6GqzQ6
+DwLZvJ9QdXo7ujEtySZnfu42aycaQ9ZLC2DOCQCUBY350Hx6FLW3O546TAvpTfk0
+B6x+DV7mJQH7MGmRXQsE7TLBJKjq28Cn4tVp04PmybQyTxZdGA/8zY6pPl6xyVMH
+V68hSBKEVT/rlouOHuxfdmZva1DhVvUC6Xj7+iTMTVJUAq/4Uyn31P1OJmA2a0PT
+CAqWkbJSgKFccsjPoTbLyxhuMSNkEZFHvlZrSK9vnPzmfiRH0Orx3wYpMQ==
+=21pb
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/repos/system_upgrade/common/files/rpm-gpg/9/RPM-GPG-KEY-redhat-release b/repos/system_upgrade/common/files/rpm-gpg/9/RPM-GPG-KEY-redhat-release
new file mode 100644
index 00000000..afd9e05a
--- /dev/null
+++ b/repos/system_upgrade/common/files/rpm-gpg/9/RPM-GPG-KEY-redhat-release
@@ -0,0 +1,66 @@
+The following public key can be used to verify RPM packages built and
+signed by Red Hat, Inc. This key is used for packages in Red Hat
+products shipped after November 2009, and for all updates to those
+products.
+
+Questions about this key should be sent to security@redhat.com.
+
+pub 4096R/FD431D51 2009-10-22 Red Hat, Inc. (release key 2) <security@redhat.com>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBErgSTsBEACh2A4b0O9t+vzC9VrVtL1AKvUWi9OPCjkvR7Xd8DtJxeeMZ5eF
+0HtzIG58qDRybwUe89FZprB1ffuUKzdE+HcL3FbNWSSOXVjZIersdXyH3NvnLLLF
+0DNRB2ix3bXG9Rh/RXpFsNxDp2CEMdUvbYCzE79K1EnUTVh1L0Of023FtPSZXX0c
+u7Pb5DI5lX5YeoXO6RoodrIGYJsVBQWnrWw4xNTconUfNPk0EGZtEnzvH2zyPoJh
+XGF+Ncu9XwbalnYde10OCvSWAZ5zTCpoLMTvQjWpbCdWXJzCm6G+/hx9upke546H
+5IjtYm4dTIVTnc3wvDiODgBKRzOl9rEOCIgOuGtDxRxcQkjrC+xvg5Vkqn7vBUyW
+9pHedOU+PoF3DGOM+dqv+eNKBvh9YF9ugFAQBkcG7viZgvGEMGGUpzNgN7XnS1gj
+/DPo9mZESOYnKceve2tIC87p2hqjrxOHuI7fkZYeNIcAoa83rBltFXaBDYhWAKS1
+PcXS1/7JzP0ky7d0L6Xbu/If5kqWQpKwUInXtySRkuraVfuK3Bpa+X1XecWi24JY
+HVtlNX025xx1ewVzGNCTlWn1skQN2OOoQTV4C8/qFpTW6DTWYurd4+fE0OJFJZQF
+buhfXYwmRlVOgN5i77NTIJZJQfYFj38c/Iv5vZBPokO6mffrOTv3MHWVgQARAQAB
+tDNSZWQgSGF0LCBJbmMuIChyZWxlYXNlIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0
+LmNvbT6JAjYEEwECACAFAkrgSTsCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAK
+CRAZni+R/UMdUWzpD/9s5SFR/ZF3yjY5VLUFLMXIKUztNN3oc45fyLdTI3+UClKC
+2tEruzYjqNHhqAEXa2sN1fMrsuKec61Ll2NfvJjkLKDvgVIh7kM7aslNYVOP6BTf
+C/JJ7/ufz3UZmyViH/WDl+AYdgk3JqCIO5w5ryrC9IyBzYv2m0HqYbWfphY3uHw5
+un3ndLJcu8+BGP5F+ONQEGl+DRH58Il9Jp3HwbRa7dvkPgEhfFR+1hI+Btta2C7E
+0/2NKzCxZw7Lx3PBRcU92YKyaEihfy/aQKZCAuyfKiMvsmzs+4poIX7I9NQCJpyE
+IGfINoZ7VxqHwRn/d5mw2MZTJjbzSf+Um9YJyA0iEEyD6qjriWQRbuxpQXmlAJbh
+8okZ4gbVFv1F8MzK+4R8VvWJ0XxgtikSo72fHjwha7MAjqFnOq6eo6fEC/75g3NL
+Ght5VdpGuHk0vbdENHMC8wS99e5qXGNDued3hlTavDMlEAHl34q2H9nakTGRF5Ki
+JUfNh3DVRGhg8cMIti21njiRh7gyFI2OccATY7bBSr79JhuNwelHuxLrCFpY7V25
+OFktl15jZJaMxuQBqYdBgSay2G0U6D1+7VsWufpzd/Abx1/c3oi9ZaJvW22kAggq
+dzdA27UUYjWvx42w9menJwh/0jeQcTecIUd0d0rFcw/c1pvgMMl/Q73yzKgKYw==
+=zbHE
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGIpIp4BEAC/o5e1WzLIsS6/JOQCs4XYATYTcf6B6ALzcP05G0W3uRpUQSrL
+FRKNrU8ZCelm/B+XSh2ljJNeklp2WLxYENDOsftDXGoyLr2hEkI5OyK267IHhFNJ
+g+BN+T5Cjh4ZiiWij6o9F7x2ZpxISE9M4iI80rwSv1KOnGSw5j2zD2EwoMjTVyVE
+/t3s5XJxnDclB7ZqL+cgjv0mWUY/4+b/OoRTkhq7b8QILuZp75Y64pkrndgakm1T
+8mAGXV02mEzpNj9DyAJdUqa11PIhMJMxxHOGHJ8CcHZ2NJL2e7yJf4orTj+cMhP5
+LzJcVlaXnQYu8Zkqa0V6J1Qdj8ZXL72QsmyicRYXAtK9Jm5pvBHuYU2m6Ja7dBEB
+Vkhe7lTKhAjkZC5ErPmANNS9kPdtXCOpwN1lOnmD2m04hks3kpH9OTX7RkTFUSws
+eARAfRID6RLfi59B9lmAbekecnsMIFMx7qR7ZKyQb3GOuZwNYOaYFevuxusSwCHv
+4FtLDIhk+Fge+EbPdEva+VLJeMOb02gC4V/cX/oFoPkxM1A5LHjkuAM+aFLAiIRd
+Np/tAPWk1k6yc+FqkcDqOttbP4ciiXb9JPtmzTCbJD8lgH0rGp8ufyMXC9x7/dqX
+TjsiGzyvlMnrkKB4GL4DqRFl8LAR02A3846DD8CAcaxoXggL2bJCU2rgUQARAQAB
+tDVSZWQgSGF0LCBJbmMuIChhdXhpbGlhcnkga2V5IDMpIDxzZWN1cml0eUByZWRo
+YXQuY29tPokCUgQTAQgAPBYhBH5GJCWMQGU11W1vE1BU5KRaY0CzBQJiKSKeAhsD
+BQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRBQVOSkWmNAsyBfEACuTN/X
+YR+QyzeRw0pXcTvMqzNE4DKKr97hSQEwZH1/v1PEPs5O3psuVUm2iam7bqYwG+ry
+EskAgMHi8AJmY0lioQD5/LTSLTrM8UyQnU3g17DHau1NHIFTGyaW4a7xviU4C2+k
+c6X0u1CPHI1U4Q8prpNcfLsldaNYlsVZtUtYSHKPAUcswXWliW7QYjZ5tMSbu8jR
+OMOc3mZuf0fcVFNu8+XSpN7qLhRNcPv+FCNmk/wkaQfH4Pv+jVsOgHqkV3aLqJeN
+kNUnpyEKYkNqo7mNfNVWOcl+Z1KKKwSkIi3vg8maC7rODsy6IX+Y96M93sqYDQom
+aaWue2gvw6thEoH4SaCrCL78mj2YFpeg1Oew4QwVcBnt68KOPfL9YyoOicNs4Vuu
+fb/vjU2ONPZAeepIKA8QxCETiryCcP43daqThvIgdbUIiWne3gae6eSj0EuUPoYe
+H5g2Lw0qdwbHIOxqp2kvN96Ii7s1DK3VyhMt/GSPCxRnDRJ8oQKJ2W/I1IT5VtiU
+zMjjq5JcYzRPzHDxfVzT9CLeU/0XQ+2OOUAiZKZ0dzSyyVn8xbpviT7iadvjlQX3
+CINaPB+d2Kxa6uFWh+ZYOLLAgZ9B8NKutUHpXN66YSfe79xFBSFWKkJ8cSIMk13/
+Ifs7ApKlKCCRDpwoDqx/sjIaj1cpOfLHYjnefg==
+=UZd/
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/repos/system_upgrade/common/files/rpm-gpg/9beta/RPM-GPG-KEY-redhat-beta b/repos/system_upgrade/common/files/rpm-gpg/9beta/RPM-GPG-KEY-redhat-beta
new file mode 100644
index 00000000..1efd1509
--- /dev/null
+++ b/repos/system_upgrade/common/files/rpm-gpg/9beta/RPM-GPG-KEY-redhat-beta
@@ -0,0 +1,29 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.2.6 (GNU/Linux)
+
+mQINBEmkAzABEAC2/c7bP1lHQ3XScxbIk0LQWe1YOiibQBRLwf8Si5PktgtuPibT
+kKpZjw8p4D+fM7jD1WUzUE0X7tXg2l/eUlMM4dw6XJAQ1AmEOtlwSg7rrMtTvM0A
+BEtI7Km6fC6sU6RtBMdcqD1cH/6dbsfh8muznVA7UlX+PRBHVzdWzj6y8h84dBjo
+gzcbYu9Hezqgj/lLzicqsSZPz9UdXiRTRAIhp8V30BD8uRaaa0KDDnD6IzJv3D9P
+xQWbFM4Z12GN9LyeZqmD7bpKzZmXG/3drvfXVisXaXp3M07t3NlBa3Dt8NFIKZ0D
+FRXBz5bvzxRVmdH6DtkDWXDPOt+Wdm1rZrCOrySFpBZQRpHw12eo1M1lirANIov7
+Z+V1Qh/aBxj5EUu32u9ZpjAPPNtQF6F/KjaoHHHmEQAuj4DLex4LY646Hv1rcv2i
+QFuCdvLKQGSiFBrfZH0j/IX3/0JXQlZzb3MuMFPxLXGAoAV9UP/Sw/WTmAuTzFVm
+G13UYFeMwrToOiqcX2VcK0aC1FCcTP2z4JW3PsWvU8rUDRUYfoXovc7eg4Vn5wHt
+0NBYsNhYiAAf320AUIHzQZYi38JgVwuJfFu43tJZE4Vig++RQq6tsEx9Ftz3EwRR
+fJ9z9mEvEiieZm+vbOvMvIuimFVPSCmLH+bI649K8eZlVRWsx3EXCVb0nQARAQAB
+tDBSZWQgSGF0LCBJbmMuIChiZXRhIGtleSAyKSA8c2VjdXJpdHlAcmVkaGF0LmNv
+bT6JAjYEEwECACAFAkpSM+cCGwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRCT
+ioDK8hVB6/9tEAC0+KmzeKceXQ/GTUoU6jy9vtkFCFrmv+c7ol4XpdTt0QhqBOwy
+6m2mKWwmm8KfYfy0cADQ4y/EcoXl7FtFBwYmkCuEQGXhTDn9DvVjhooIq59LEMBQ
+OW879RwwzRIZ8ebbjMUjDPF5MfPQqP2LBu9N4KvXlZp4voykwuuaJ+cbsKZR6pZ6
+0RQKPHKP+NgUFC0fff7XY9cuOZZWFAeKRhLN2K7bnRHKxp+kELWb6R9ZfrYwZjWc
+MIPbTd1khE53L4NTfpWfAnJRtkPSDOKEGVlVLtLq4HEAxQt07kbslqISRWyXER3u
+QOJj64D1ZiIMz6t6uZ424VE4ry9rBR0Jz55cMMx5O/ni9x3xzFUgH8Su2yM0r3jE
+Rf24+tbOaPf7tebyx4OKe+JW95hNVstWUDyGbs6K9qGfI/pICuO1nMMFTo6GqzQ6
+DwLZvJ9QdXo7ujEtySZnfu42aycaQ9ZLC2DOCQCUBY350Hx6FLW3O546TAvpTfk0
+B6x+DV7mJQH7MGmRXQsE7TLBJKjq28Cn4tVp04PmybQyTxZdGA/8zY6pPl6xyVMH
+V68hSBKEVT/rlouOHuxfdmZva1DhVvUC6Xj7+iTMTVJUAq/4Uyn31P1OJmA2a0PT
+CAqWkbJSgKFccsjPoTbLyxhuMSNkEZFHvlZrSK9vnPzmfiRH0Orx3wYpMQ==
+=21pb
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/repos/system_upgrade/common/libraries/dnfplugin.py b/repos/system_upgrade/common/libraries/dnfplugin.py
index 0ef9ea9b..7a15abc4 100644
--- a/repos/system_upgrade/common/libraries/dnfplugin.py
+++ b/repos/system_upgrade/common/libraries/dnfplugin.py
@@ -6,6 +6,7 @@ import shutil
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.common import dnfconfig, guards, mounting, overlaygen, rhsm, utils
+from leapp.libraries.common.config import get_env
from leapp.libraries.common.config.version import get_target_major_version, get_target_version
from leapp.libraries.stdlib import api, CalledProcessError, config
from leapp.models import DNFWorkaround
@@ -74,6 +75,10 @@ def _rebuild_rpm_db(context, root=None):
context.call(cmd)
+def _the_nogpgcheck_option_used():
+ return get_env('LEAPP_NOGPGCHECK', '0') == '1'
+
+
def build_plugin_data(target_repoids, debug, test, tasks, on_aws):
"""
Generates a dictionary with the DNF plugin data.
@@ -93,7 +98,7 @@ def build_plugin_data(target_repoids, debug, test, tasks, on_aws):
'debugsolver': debug,
'disable_repos': True,
'enable_repos': target_repoids,
- 'gpgcheck': False,
+ 'gpgcheck': not _the_nogpgcheck_option_used(),
'platform_id': 'platform:el{}'.format(get_target_major_version()),
'releasever': get_target_version(),
'installroot': '/installroot',
@@ -269,8 +274,10 @@ def install_initramdisk_requirements(packages, target_userspace_info, used_repos
cmd = [
'dnf',
'install',
- '-y',
- '--nogpgcheck',
+ '-y']
+ if _the_nogpgcheck_option_used():
+ cmd.append('--nogpgcheck')
+ cmd += [
'--setopt=module_platform_id=platform:el{}'.format(get_target_major_version()),
'--setopt=keepcache=1',
'--releasever', api.current_actor().configuration.version.target,
diff --git a/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py b/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py
index 3d0b908f..1ca95945 100644
--- a/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py
+++ b/repos/system_upgrade/common/libraries/tests/test_dnfplugin.py
@@ -5,6 +5,8 @@ import pytest
import leapp.models
from leapp.libraries.common import dnfplugin
from leapp.libraries.common.config.version import get_major_version
+from leapp.libraries.common.testutils import CurrentActorMocked
+from leapp.libraries.stdlib import api
from leapp.models.fields import Boolean
from leapp.topics import Topic
@@ -61,7 +63,7 @@ class DATADnfPluginDataDnfConf(leapp.models.Model):
debugsolver = fields.Boolean()
disable_repos = BooleanEnum(choices=[True])
enable_repos = fields.List(fields.StringEnum(choices=TEST_ENABLE_REPOS_CHOICES))
- gpgcheck = BooleanEnum(choices=[False])
+ gpgcheck = fields.Boolean()
platform_id = fields.StringEnum(choices=['platform:el8', 'platform:el9'])
releasever = fields.String()
installroot = fields.StringEnum(choices=['/installroot'])
@@ -94,16 +96,6 @@ del leapp.models.DATADnfPluginDataRHUIAWS
del leapp.models.DATADnfPluginData
-def _mocked_get_target_major_version(version):
- def impl():
- return version
- return impl
-
-
-def _mocked_api_get_file_path(name):
- return 'some/random/file/path/{}'.format(name)
-
-
_CONFIG_BUILD_TEST_DEFINITION = (
# Parameter, Input Data, Expected Fields with data
('debug', False, ('dnf_conf', 'debugsolver'), False),
@@ -131,9 +123,7 @@ def test_build_plugin_data_variations(
expected_value,
):
used_target_major_version = get_major_version(used_target_version)
- monkeypatch.setattr(dnfplugin, 'get_target_version', _mocked_get_target_major_version(used_target_version))
- monkeypatch.setattr(dnfplugin, 'get_target_major_version',
- _mocked_get_target_major_version(used_target_major_version))
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver=used_target_version))
inputs = {
'target_repoids': ['BASEOS', 'APPSTREAM'],
'debug': True,
@@ -161,8 +151,7 @@ def test_build_plugin_data_variations(
def test_build_plugin_data(monkeypatch):
- monkeypatch.setattr(dnfplugin, 'get_target_version', _mocked_get_target_major_version('8.4'))
- monkeypatch.setattr(dnfplugin, 'get_target_major_version', _mocked_get_target_major_version('8'))
+ monkeypatch.setattr(api, 'current_actor', CurrentActorMocked(dst_ver='8.4'))
# Use leapp to validate format and data
created = DATADnfPluginData.create(
dnfplugin.build_plugin_data(
diff --git a/repos/system_upgrade/common/models/targetrepositories.py b/repos/system_upgrade/common/models/targetrepositories.py
index a5a245f1..02c6c5e5 100644
--- a/repos/system_upgrade/common/models/targetrepositories.py
+++ b/repos/system_upgrade/common/models/targetrepositories.py
@@ -22,14 +22,48 @@ class CustomTargetRepository(TargetRepositoryBase):
class TargetRepositories(Model):
+ """
+ Repositories supposed to be used during the IPU process
+
+ The list of the actually used repositories could be just subset
+ of these repositoies. In case of `custom_repositories`, all such repositories
+ must be available otherwise the upgrade is inhibited. But in case of
+ `rhel_repos`, only BaseOS and Appstream repos are required now. If others
+ are missing, upgrade can still continue.
+ """
topic = TransactionTopic
rhel_repos = fields.List(fields.Model(RHELTargetRepository))
+ """
+ Expected target YUM RHEL repositories provided via RHSM
+
+ These repositories are stored inside /etc/yum.repos.d/redhat.repo and
+ are expected to be used based on the provided repositories mapping.
+ """
+
custom_repos = fields.List(fields.Model(CustomTargetRepository), default=[])
+ """
+ Custom YUM repositories required to be used for the IPU
+
+ Usually contains third-party or custom repositories specified by user
+ to be used for the IPU. But can contain also RHEL repositories. Difference
+ is that these repositories are not mapped automatically but are explicitly
+ required by user or by an additional product via actors.
+ """
class UsedTargetRepositories(Model):
+ """
+ Repositories that are used for the IPU process
+
+ This is the source of truth about the repositories used during the upgrade.
+ Once specified, it is used for all actions related to the upgrade rpm
+ transaction itself.
+ """
topic = TransactionTopic
repos = fields.List(fields.Model(UsedTargetRepository))
+ """
+ The list of the used target repositories.
+ """
class CustomTargetRepositoryFile(Model):
--
2.38.1