import UBI dnf-plugins-core-4.3.0-24.el9_7
This commit is contained in:
parent
41932746dd
commit
911a483847
@ -0,0 +1,40 @@
|
||||
From 43d07e2b385b069b1f851e5a16f1b7d8bbed1195 Mon Sep 17 00:00:00 2001
|
||||
From: Marek Blaha <mblaha@redhat.com>
|
||||
Date: Mon, 4 Nov 2024 10:35:08 +0100
|
||||
Subject: [PATCH] reposync: Avoid multiple downloads of duplicate packages
|
||||
|
||||
Download each package only once if it would have been saved to the same
|
||||
location. This can occur if the repository metadata contains duplicate
|
||||
entries for the same package.
|
||||
|
||||
Resolves: https://issues.redhat.com/browse/RHEL-64320
|
||||
|
||||
Upstream commit: 17e36ed
|
||||
---
|
||||
plugins/reposync.py | 10 +++++++++-
|
||||
1 file changed, 9 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/plugins/reposync.py b/plugins/reposync.py
|
||||
index ab513e7..19f5440 100644
|
||||
--- a/plugins/reposync.py
|
||||
+++ b/plugins/reposync.py
|
||||
@@ -292,7 +292,15 @@ class RepoSyncCommand(dnf.cli.Command):
|
||||
query.filterm(arch='src')
|
||||
elif self.opts.arches:
|
||||
query.filterm(arch=self.opts.arches)
|
||||
- return query
|
||||
+ # skip packages that would have been downloaded to the same location
|
||||
+ pkglist = []
|
||||
+ seen_paths = set()
|
||||
+ for pkg in query:
|
||||
+ download_path = self.pkg_download_path(pkg)
|
||||
+ if download_path not in seen_paths:
|
||||
+ pkglist.append(pkg)
|
||||
+ seen_paths.add(download_path)
|
||||
+ return pkglist
|
||||
|
||||
def download_packages(self, pkglist):
|
||||
base = self.base
|
||||
--
|
||||
2.48.1
|
||||
|
||||
@ -0,0 +1,617 @@
|
||||
From 37c4cd7014f4b3c5db64a0a900761e53caf645be Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
|
||||
Date: Tue, 25 Mar 2025 12:34:34 +0100
|
||||
Subject: [PATCH] multisig: A new plugin for verifying extraordinary RPM
|
||||
signatures
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
This plugin executes a dedicated rpmkeys(8) tool of pqrpm for
|
||||
verifying RPM packages bound into a transaction.
|
||||
|
||||
The dedicated tool is supposed to understand signatures in RPM version
|
||||
6 format. One feature of that format is that you can have multiple
|
||||
signatures on a single RPM package.
|
||||
|
||||
This plugin takes public keys from a default key store of pqrpm, which
|
||||
is separate from the native RPM database. A reason is that the native
|
||||
database might reject storing OpenPGP keys with an unsupported key
|
||||
schema. A downside is that "dnf --installroot" places the keyring to
|
||||
a directory which is ununowned in the installroot.
|
||||
|
||||
XXX: This plugin does not report which keys required for the
|
||||
verification are missing (NOKEY case in _process_rpm_output()).
|
||||
Implementing that would require changing too many function interfaces.
|
||||
Classical single-signature verification does not do that either.
|
||||
|
||||
XXX: The plugin is packaged into a package which requires pqrpm which
|
||||
will only be available in RHEL 9. Next RHEL version will support the
|
||||
extraordinary signatures natively. Thus this patch only makes sense in
|
||||
RHEL 9.
|
||||
|
||||
Signed-off-by: Petr Písař <ppisar@redhat.com>
|
||||
---
|
||||
dnf-plugins-core.spec | 19 ++
|
||||
doc/CMakeLists.txt | 5 +
|
||||
doc/conf.py | 3 +
|
||||
doc/index.rst | 1 +
|
||||
doc/multisig.rst | 62 +++++++
|
||||
plugins/CMakeLists.txt | 3 +
|
||||
plugins/multisig.py | 402 +++++++++++++++++++++++++++++++++++++++++
|
||||
7 files changed, 495 insertions(+)
|
||||
create mode 100644 doc/multisig.rst
|
||||
create mode 100644 plugins/multisig.py
|
||||
|
||||
diff --git a/dnf-plugins-core.spec b/dnf-plugins-core.spec
|
||||
index f2d1bc4..cb3b1b8 100644
|
||||
--- a/dnf-plugins-core.spec
|
||||
+++ b/dnf-plugins-core.spec
|
||||
@@ -301,6 +301,18 @@ Obsoletes: python-dnf-plugins-extras-migrate < %{dnf_plugins_extra}
|
||||
Migrate Plugin for DNF, Python 2 version. Migrates history, group and yumdb data from yum to dnf.
|
||||
%endif
|
||||
|
||||
+%if %{with python3}
|
||||
+%package -n python3-dnf-plugin-multisig
|
||||
+Summary: Multisig Plugin for DNF
|
||||
+Requires: pqrpm
|
||||
+Requires: python3-%{name} = %{version}-%{release}
|
||||
+Provides: dnf-plugin-multisig = %{version}-%{release}
|
||||
+
|
||||
+%description -n python3-dnf-plugin-multisig
|
||||
+Multisig Plugin for DNF, Python 3 version. The plugin verifies multiple RPMv6
|
||||
+signatures on RPMv4 packages by using an external rpmkeys program.
|
||||
+%endif
|
||||
+
|
||||
%if %{with python2}
|
||||
%package -n python2-dnf-plugin-post-transaction-actions
|
||||
Summary: Post transaction actions Plugin for DNF
|
||||
@@ -716,6 +728,13 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%exclude %{_mandir}/man8/dnf-migrate.*
|
||||
%endif
|
||||
|
||||
+%if %{with python3}
|
||||
+%files -n python3-dnf-plugin-multisig
|
||||
+%{python3_sitelib}/dnf-plugins/multisig.*
|
||||
+%{python3_sitelib}/dnf-plugins/__pycache__/multisig.*
|
||||
+%{_mandir}/man8/dnf*-multisig.*
|
||||
+%endif
|
||||
+
|
||||
%if %{with python2}
|
||||
%files -n python2-dnf-plugin-post-transaction-actions
|
||||
%config(noreplace) %{_sysconfdir}/dnf/plugins/post-transaction-actions.conf
|
||||
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
|
||||
index 79472a5..297506a 100644
|
||||
--- a/doc/CMakeLists.txt
|
||||
+++ b/doc/CMakeLists.txt
|
||||
@@ -47,6 +47,11 @@ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-migrate.8
|
||||
DESTINATION share/man/man8)
|
||||
endif()
|
||||
|
||||
+if (${PYTHON_VERSION_MAJOR} STREQUAL "3")
|
||||
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf4-multisig.8
|
||||
+ DESTINATION share/man/man8)
|
||||
+endif()
|
||||
+
|
||||
if (${WITHOUT_LOCAL} STREQUAL "0")
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-local.8
|
||||
DESTINATION share/man/man8)
|
||||
diff --git a/doc/conf.py b/doc/conf.py
|
||||
index 327ac07..2845d18 100644
|
||||
--- a/doc/conf.py
|
||||
+++ b/doc/conf.py
|
||||
@@ -300,6 +300,9 @@ man_pages = [
|
||||
if sys.version_info[0] < 3:
|
||||
man_pages.append(('migrate', 'dnf-migrate', u'DNF migrate Plugin', AUTHORS, 8))
|
||||
|
||||
+if sys.version_info[0] == 3:
|
||||
+ man_pages.append(('multisig', 'dnf4-multisig', u'DNF multisig Plugin', AUTHORS, 8))
|
||||
+
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
diff --git a/doc/index.rst b/doc/index.rst
|
||||
index 251a24e..32984a5 100644
|
||||
--- a/doc/index.rst
|
||||
+++ b/doc/index.rst
|
||||
@@ -37,6 +37,7 @@ This documents core plugins of DNF:
|
||||
leaves
|
||||
local
|
||||
migrate
|
||||
+ multisig
|
||||
modulesync
|
||||
needs_restarting
|
||||
post-transaction-actions
|
||||
diff --git a/doc/multisig.rst b/doc/multisig.rst
|
||||
new file mode 100644
|
||||
index 0000000..21b9436
|
||||
--- /dev/null
|
||||
+++ b/doc/multisig.rst
|
||||
@@ -0,0 +1,62 @@
|
||||
+===================
|
||||
+DNF multisig Plugin
|
||||
+===================
|
||||
+
|
||||
+-----------
|
||||
+Description
|
||||
+-----------
|
||||
+
|
||||
+This plugin verifies extraordinary RPMv6 signatures when installing,
|
||||
+reinstalling, upgrading, or downgrading packages from a repository. If the
|
||||
+verification fails, the RPM operation will be aborted.
|
||||
+
|
||||
+The verification is achieved by executing a dedicated rpmkeys(8) tool which is
|
||||
+supposed to understand package signatures in RPM version 6 format. One feature
|
||||
+of that format is that you can have multiple signatures on a single RPM
|
||||
+package. If the package has no RPMv6 signature a signature in version 4 format
|
||||
+will be verified instead. If there is no signature, the verification will be
|
||||
+handled as failed.
|
||||
+
|
||||
+The dedicated rpmkeys(8) tool trusts public keys in a key store separate from
|
||||
+the native RPM database because the native database might reject storing
|
||||
+OpenPGP keys with an unsupported key schema which is foreseen to be used in
|
||||
+RPMv6 signatures.
|
||||
+
|
||||
+Public keys missing from the separate key store are attempted to be imported
|
||||
+from URLs listed in ``gpgkey`` configuration field of a repository the package
|
||||
+belongs to. Before importing, a user is asked for a confirmation with the
|
||||
+import, unless DNF was invoked with ``--assumeyes`` or ``--assumeno`` options.
|
||||
+
|
||||
+The key store can be inspected with ``/usr/lib/pqrpm/bin/rpmkeys -D --list``
|
||||
+command. A key can be deleted from the key store with
|
||||
+``/usr/lib/pqrpm/bin/rpmkeys -D --erase KEY_ID`` command. The ``KEY_ID`` is
|
||||
+the first word in an output of the ``--list`` command.
|
||||
+
|
||||
+Users who do not wish to verify the extraordinary RPMv6 signatures should
|
||||
+uninstall this plugin.
|
||||
+
|
||||
+-------------
|
||||
+Configuration
|
||||
+-------------
|
||||
+
|
||||
+Hard-coded path to the rpmkeys(8) tool is ``/usr/lib/pqrpm/bin/rpmkeys``.
|
||||
+
|
||||
+This plugin respects ``gpgkey`` and ``gpgcheck`` fields in a repository
|
||||
+configuration. See dnf.conf(5) for more details.
|
||||
+
|
||||
+-----
|
||||
+Files
|
||||
+-----
|
||||
+
|
||||
+``/usr/lib/pqrpm/lib/sysimage/rpm``
|
||||
+ A location of the key store defined by ``/usr/lib/pqrpm/bin/rpmkeys``
|
||||
+ tool.
|
||||
+
|
||||
+--------
|
||||
+See Also
|
||||
+--------
|
||||
+
|
||||
+* :manpage:`dnf.conf(5)`
|
||||
+* :manpage:`dnf(8)`
|
||||
+* :manpage:`rpmkeys(8)`
|
||||
+
|
||||
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
|
||||
index d004e5e..a84a786 100644
|
||||
--- a/plugins/CMakeLists.txt
|
||||
+++ b/plugins/CMakeLists.txt
|
||||
@@ -14,6 +14,9 @@ endif()
|
||||
if (${PYTHON_VERSION_MAJOR} STREQUAL "2")
|
||||
INSTALL (FILES migrate.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
endif()
|
||||
+if (${PYTHON_VERSION_MAJOR} STREQUAL "3")
|
||||
+INSTALL (FILES multisig.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
+endif()
|
||||
INSTALL (FILES needs_restarting.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
INSTALL (FILES post-transaction-actions.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
INSTALL (FILES repoclosure.py DESTINATION ${PYTHON_INSTALL_DIR}/dnf-plugins)
|
||||
diff --git a/plugins/multisig.py b/plugins/multisig.py
|
||||
new file mode 100644
|
||||
index 0000000..8735a26
|
||||
--- /dev/null
|
||||
+++ b/plugins/multisig.py
|
||||
@@ -0,0 +1,402 @@
|
||||
+from __future__ import print_function, absolute_import, unicode_literals
|
||||
+import dnf
|
||||
+import dnf.crypto
|
||||
+import dnf.dnssec
|
||||
+import dnf.exceptions
|
||||
+from dnf.i18n import ucd
|
||||
+import dnf.rpm.transaction
|
||||
+import dnf.transaction
|
||||
+from dnfpluginscore import _, logger
|
||||
+import os
|
||||
+import subprocess
|
||||
+import sys
|
||||
+
|
||||
+class MultiSig(dnf.Plugin):
|
||||
+ """
|
||||
+ This plugin verifies signatures of RPM packages by executing an
|
||||
+ extraordinary "rpmkeys" tool. That tool can, for example, support multiple
|
||||
+ RPM v6 signatures, or signature schemata uknown to the ordinary,
|
||||
+ system-wide rpmkeys tool.
|
||||
+
|
||||
+ This verification is perfmored in addition to the standard verification
|
||||
+ performed by DNF.
|
||||
+ """
|
||||
+
|
||||
+ name = "multisig"
|
||||
+
|
||||
+ def __init__(self, base, cli):
|
||||
+ super(MultiSig, self).__init__(base, cli)
|
||||
+ # Path to the rpmkeys executable
|
||||
+ self.rpmkeys_executable = "/usr/lib/pqrpm/bin/rpmkeys"
|
||||
+ # List of repositories whose keys we have tried importing so far
|
||||
+ # during a run of this plugin.
|
||||
+ self._repo_set_imported_gpg_keys = [];
|
||||
+
|
||||
+ def pre_transaction(self):
|
||||
+ inbound_packages = []
|
||||
+ for ts_item in self.base.transaction:
|
||||
+ if ts_item.action in dnf.transaction.FORWARD_ACTIONS:
|
||||
+ inbound_packages.append(ts_item.pkg);
|
||||
+ self.gpgsigcheck(inbound_packages)
|
||||
+
|
||||
+ def _process_rpm_output(self, data):
|
||||
+ # No signatures or digests = corrupt package.
|
||||
+ # There is at least one line for -: and another (empty) entry after the
|
||||
+ # last newline.
|
||||
+ if len(data) < 3 or data[0] != b'-:' or data[-1]:
|
||||
+ return 2
|
||||
+ seen_sig, missing_key, not_trusted, not_signed = False, False, False, False
|
||||
+ for i in data[1:-1]:
|
||||
+ if b': BAD' in i:
|
||||
+ return 2
|
||||
+ elif i.endswith(b': NOKEY'):
|
||||
+ missing_key = True
|
||||
+ elif i.endswith(b': NOTTRUSTED'):
|
||||
+ not_trusted = True
|
||||
+ elif i.endswith(b': NOTFOUND'):
|
||||
+ not_signed = True
|
||||
+ elif not i.endswith(b': OK'):
|
||||
+ return 2
|
||||
+ if not_trusted:
|
||||
+ return 3
|
||||
+ elif missing_key:
|
||||
+ return 1
|
||||
+ elif not_signed:
|
||||
+ return 4
|
||||
+ # we still check return code, so this is safe
|
||||
+ return 0
|
||||
+
|
||||
+ def _verifyPackageUsingRpmkeys(self, package, installroot):
|
||||
+ # "--define=_pkgverify_level signature" enforces signature checking;
|
||||
+ # "--define=_pkgverify_flags 0x0" ensures that all signatures are checked.
|
||||
+ args = (self.rpmkeys_executable,
|
||||
+ '--checksig', '--root', installroot, '--verbose',
|
||||
+ '--define=_pkgverify_level signature', '--define=_pkgverify_flags 0x0',
|
||||
+ '-')
|
||||
+ env = dict(os.environ)
|
||||
+ env['LC_ALL'] = 'C'
|
||||
+ with subprocess.Popen(
|
||||
+ args=args,
|
||||
+ executable=self.rpmkeys_executable,
|
||||
+ env=env,
|
||||
+ stdout=subprocess.PIPE,
|
||||
+ cwd='/',
|
||||
+ stdin=package) as p:
|
||||
+ data = p.communicate()[0]
|
||||
+ returncode = p.returncode
|
||||
+ if type(returncode) is not int:
|
||||
+ raise AssertionError('Popen set return code to non-int')
|
||||
+ # rpmkeys can return something other than 0 or 1 in the case of a
|
||||
+ # fatal error (OOM, abort() called, SIGSEGV, etc)
|
||||
+ if returncode >= 2 or returncode < 0:
|
||||
+ return 2
|
||||
+ ret = self._process_rpm_output(data.split(b'\n'))
|
||||
+ if ret:
|
||||
+ return ret
|
||||
+ return 2 if returncode else 0
|
||||
+
|
||||
+ def _checkSig(self, installroot, package):
|
||||
+ """Takes a transaction set and a package, check it's sigs,
|
||||
+ return 0 if they are all fine
|
||||
+ return 1 if the gpg key can't be found
|
||||
+ return 2 if the header is in someway damaged
|
||||
+ return 3 if the key is not trusted
|
||||
+ return 4 if the pkg is not gpg or pgp signed"""
|
||||
+
|
||||
+ fdno = os.open(package, os.O_RDONLY|os.O_NOCTTY|os.O_CLOEXEC)
|
||||
+ try:
|
||||
+ value = self._verifyPackageUsingRpmkeys(fdno, installroot)
|
||||
+ finally:
|
||||
+ os.close(fdno)
|
||||
+ return value
|
||||
+
|
||||
+ def _sig_check_pkg(self, po):
|
||||
+ """Verify the GPG signature of the given package object.
|
||||
+
|
||||
+ :param po: the package object to verify the signature of
|
||||
+ :return: (result, error_string)
|
||||
+ where result is::
|
||||
+
|
||||
+ 0 = GPG signature verifies ok or verification is not required.
|
||||
+ 1 = GPG verification failed but installation of the right GPG key
|
||||
+ might help.
|
||||
+ 2 = Fatal GPG verification error, give up.
|
||||
+ """
|
||||
+ if po._from_cmdline:
|
||||
+ check = self.base.conf.localpkg_gpgcheck
|
||||
+ hasgpgkey = 0
|
||||
+ else:
|
||||
+ repo = self.base.repos[po.repoid]
|
||||
+ check = repo.gpgcheck
|
||||
+ hasgpgkey = not not repo.gpgkey
|
||||
+
|
||||
+ localfn = os.path.basename(po.localPkg())
|
||||
+ if check:
|
||||
+ logger.debug(_("Multisig: verifying: {}").format(po.localPkg()))
|
||||
+ sigresult = self._checkSig(self.base.conf.installroot, po.localPkg())
|
||||
+ if sigresult == 0:
|
||||
+ result = 0
|
||||
+ msg = _('All signatures for %s successfully verified') % localfn
|
||||
+
|
||||
+ elif sigresult == 1:
|
||||
+ if hasgpgkey:
|
||||
+ result = 1
|
||||
+ else:
|
||||
+ result = 2
|
||||
+ msg = _('Public key for %s is not installed') % localfn
|
||||
+
|
||||
+ elif sigresult == 2:
|
||||
+ result = 2
|
||||
+ msg = _('Problem opening package %s') % localfn
|
||||
+
|
||||
+ elif sigresult == 3:
|
||||
+ if hasgpgkey:
|
||||
+ result = 1
|
||||
+ else:
|
||||
+ result = 2
|
||||
+ result = 1
|
||||
+ msg = _('Public key for %s is not trusted') % localfn
|
||||
+
|
||||
+ elif sigresult == 4:
|
||||
+ result = 2
|
||||
+ msg = _('Package %s is not signed') % localfn
|
||||
+
|
||||
+ else:
|
||||
+ result = 0
|
||||
+ msg = _('Signature verification for %s is disabled') % localfn
|
||||
+
|
||||
+ logger.debug(_("Multisig: verification result: {} (code={})").format(msg, result))
|
||||
+ return result, msg
|
||||
+
|
||||
+ def keyInstalled(self, fingerprint):
|
||||
+ '''
|
||||
+ Return if the GPG key described by the given fingerprint is installed
|
||||
+ in the multisig keyring.
|
||||
+
|
||||
+ Return values:
|
||||
+ - True key is installed
|
||||
+ - False otherwise
|
||||
+ Trows: If rpmkeys program could not been executed.
|
||||
+
|
||||
+ No effort is made to handle duplicates.
|
||||
+ '''
|
||||
+ # XXX: rpmkeys expects lowercase
|
||||
+ # <https://github.com/rpm-software-management/rpm/issues/3721>
|
||||
+ logger.debug(_("Multisig: Checking a presence of key={}").format(fingerprint))
|
||||
+ args = (self.rpmkeys_executable,
|
||||
+ '--root', self.base.conf.installroot,
|
||||
+ '--list', fingerprint.lower())
|
||||
+ p = subprocess.run(
|
||||
+ args=args,
|
||||
+ executable=self.rpmkeys_executable,
|
||||
+ cwd='/',
|
||||
+ stdin=subprocess.DEVNULL,
|
||||
+ stdout=subprocess.DEVNULL,
|
||||
+ stderr=subprocess.DEVNULL)
|
||||
+ return p.returncode == 0
|
||||
+
|
||||
+ def importKey(self, key):
|
||||
+ '''
|
||||
+ Import given Key object into the multisig keyring.
|
||||
+
|
||||
+ Return values:
|
||||
+ - True key imported successfully
|
||||
+ - False otherwise
|
||||
+ Trows: If rpmkeys program could not been executed.
|
||||
+
|
||||
+ What happens if a key's raw_string contains multiple public key
|
||||
+ packets, or if the key was already in the keyring is unspecified and
|
||||
+ it depends on rpmkeys behavior. Current rpmkeys implementation
|
||||
+ gracefully ignores (or updates?) existing keys.
|
||||
+ '''
|
||||
+ args = (self.rpmkeys_executable,
|
||||
+ '--root', self.base.conf.installroot,
|
||||
+ '--import', '-')
|
||||
+ env = dict(os.environ)
|
||||
+ env['LC_ALL'] = 'C'
|
||||
+ with subprocess.Popen(
|
||||
+ executable=self.rpmkeys_executable,
|
||||
+ args=args,
|
||||
+ env=env,
|
||||
+ cwd='/',
|
||||
+ # XXX: rpmkeys used to fail reading from a pipe. Fix at
|
||||
+ # <https://github.com/rpm-software-management/rpm/pull/3706>.
|
||||
+ stdin=subprocess.PIPE,
|
||||
+ stdout=subprocess.PIPE,
|
||||
+ stderr=subprocess.PIPE) as p:
|
||||
+ stdout, stderr = p.communicate(input=key.raw_key)
|
||||
+ returncode = p.returncode
|
||||
+ if type(returncode) is not int:
|
||||
+ raise AssertionError('Popen set return code to non-int')
|
||||
+ logger.debug(_("Multisig: Key import result: exitcode={}, stdout={}, stderr={}").format(
|
||||
+ returncode, stdout, stderr))
|
||||
+ return returncode == 0
|
||||
+
|
||||
+ def _get_key_for_package(self, po, askcb=None, fullaskcb=None):
|
||||
+ """Retrieve a key for a package. If needed, use the given
|
||||
+ callback to prompt whether the key should be imported.
|
||||
+
|
||||
+ :param po: the package object to retrieve the key of
|
||||
+ :param askcb: Callback function to use to ask permission to
|
||||
+ import a key. The arguments *askcb* should take are the
|
||||
+ package object, the userid of the key, and the keyid
|
||||
+ :param fullaskcb: Callback function to use to ask permission to
|
||||
+ import a key. This differs from *askcb* in that it gets
|
||||
+ passed a dictionary so that we can expand the values passed.
|
||||
+ :raises: :class:`dnf.exceptions.Error` if there are errors
|
||||
+ retrieving the keys
|
||||
+ """
|
||||
+ if po._from_cmdline:
|
||||
+ # raise an exception, because po.repoid is not in self.repos
|
||||
+ msg = _('Unable to retrieve a key for a commandline package: %s')
|
||||
+ raise ValueError(msg % po)
|
||||
+
|
||||
+ repo = self.base.repos[po.repoid]
|
||||
+ key_installed = repo.id in self._repo_set_imported_gpg_keys
|
||||
+ keyurls = [] if key_installed else repo.gpgkey
|
||||
+
|
||||
+ def _prov_key_data(msg):
|
||||
+ msg += _('. Failing package is: %s') % (po) + '\n '
|
||||
+ msg += _('GPG Keys are configured as: %s') % \
|
||||
+ (', '.join(repo.gpgkey))
|
||||
+ return msg
|
||||
+
|
||||
+ user_cb_fail = False
|
||||
+ self._repo_set_imported_gpg_keys.append(repo.id)
|
||||
+ for keyurl in keyurls:
|
||||
+ keys = dnf.crypto.retrieve(keyurl, repo)
|
||||
+
|
||||
+ for info in keys:
|
||||
+ # Check if key is already installed
|
||||
+ if self.keyInstalled(info.fingerprint):
|
||||
+ msg = _('GPG key at %s (0x%s) is already installed')
|
||||
+ logger.info(msg, keyurl, info.short_id)
|
||||
+ continue
|
||||
+
|
||||
+ # DNS Extension: create a key object, pass it to the verification class
|
||||
+ # and print its result as an advice to the user.
|
||||
+ if self.base.conf.gpgkey_dns_verification:
|
||||
+ dns_input_key = dnf.dnssec.KeyInfo.from_rpm_key_object(info.userid,
|
||||
+ info.raw_key)
|
||||
+ dns_result = dnf.dnssec.DNSSECKeyVerification.verify(dns_input_key)
|
||||
+ logger.info(dnf.dnssec.nice_user_msg(dns_input_key, dns_result))
|
||||
+
|
||||
+ # Try installing/updating GPG key
|
||||
+ info.url = keyurl
|
||||
+ if self.base.conf.gpgkey_dns_verification:
|
||||
+ dnf.crypto.log_dns_key_import(info, dns_result)
|
||||
+ else:
|
||||
+ dnf.crypto.log_key_import(info)
|
||||
+ rc = False
|
||||
+ if self.base.conf.assumeno:
|
||||
+ rc = False
|
||||
+ elif self.base.conf.assumeyes:
|
||||
+ # DNS Extension: We assume, that the key is trusted in case it is valid,
|
||||
+ # its existence is explicitly denied or in case the domain is not signed
|
||||
+ # and therefore there is no way to know for sure (this is mainly for
|
||||
+ # backward compatibility)
|
||||
+ # FAQ:
|
||||
+ # * What is PROVEN_NONEXISTENCE?
|
||||
+ # In DNSSEC, your domain does not need to be signed, but this state
|
||||
+ # (not signed) has to be proven by the upper domain. e.g. when example.com.
|
||||
+ # is not signed, com. servers have to sign the message, that example.com.
|
||||
+ # does not have any signing key (KSK to be more precise).
|
||||
+ if self.base.conf.gpgkey_dns_verification:
|
||||
+ if dns_result in (dnf.dnssec.Validity.VALID,
|
||||
+ dnf.dnssec.Validity.PROVEN_NONEXISTENCE):
|
||||
+ rc = True
|
||||
+ logger.info(dnf.dnssec.any_msg(_("The key has been approved.")))
|
||||
+ else:
|
||||
+ rc = False
|
||||
+ logger.info(dnf.dnssec.any_msg(_("The key has been rejected.")))
|
||||
+ else:
|
||||
+ rc = True
|
||||
+
|
||||
+ # grab the .sig/.asc for the keyurl, if it exists if it
|
||||
+ # does check the signature on the key if it is signed by
|
||||
+ # one of our ca-keys for this repo or the global one then
|
||||
+ # rc = True else ask as normal.
|
||||
+
|
||||
+ elif fullaskcb:
|
||||
+ rc = fullaskcb({"po": po, "userid": info.userid,
|
||||
+ "hexkeyid": info.short_id,
|
||||
+ "keyurl": keyurl,
|
||||
+ "fingerprint": info.fingerprint,
|
||||
+ "timestamp": info.timestamp})
|
||||
+ elif askcb:
|
||||
+ rc = askcb(po, info.userid, info.short_id)
|
||||
+
|
||||
+ if not rc:
|
||||
+ user_cb_fail = True
|
||||
+ continue
|
||||
+
|
||||
+ # Import the key
|
||||
+ # XXX: raw_key of second info erroneously contains first and
|
||||
+ # second key. Probably a bug in key parser.
|
||||
+ #logger.debug(_("Multisig: Importing a key: {}").format(info.raw_key))
|
||||
+ result = self.importKey(info)
|
||||
+ if result == False:
|
||||
+ msg = _('Key import failed')
|
||||
+ raise dnf.exceptions.Error(_prov_key_data(msg))
|
||||
+ logger.info(_('Key imported successfully'))
|
||||
+ key_installed = True
|
||||
+
|
||||
+ if not key_installed and user_cb_fail:
|
||||
+ raise dnf.exceptions.Error(_("Didn't install any keys"))
|
||||
+
|
||||
+ if not key_installed:
|
||||
+ msg = _('The GPG keys listed for the "%s" repository are '
|
||||
+ 'already installed but they are not correct for this '
|
||||
+ 'package.\n'
|
||||
+ 'Check that the correct key URLs are configured for '
|
||||
+ 'this repository.') % repo.name
|
||||
+ raise dnf.exceptions.Error(_prov_key_data(msg))
|
||||
+
|
||||
+ # Check if the newly installed keys helped
|
||||
+ result, errmsg = self._sig_check_pkg(po)
|
||||
+ if result != 0:
|
||||
+ if keyurls:
|
||||
+ msg = _("Import of key(s) didn't help, wrong key(s)?")
|
||||
+ logger.info(msg)
|
||||
+ errmsg = ucd(errmsg)
|
||||
+ raise dnf.exceptions.Error(_prov_key_data(errmsg))
|
||||
+
|
||||
+ def gpgsigcheck(self, pkgs):
|
||||
+ """Perform GPG signature verification on the given packages,
|
||||
+ installing keys if possible.
|
||||
+
|
||||
+ :param pkgs: a list of package objects to verify the GPG
|
||||
+ signatures of
|
||||
+ :raises: Will raise :class:`Error` if there's a problem
|
||||
+ """
|
||||
+ error_messages = []
|
||||
+ for po in pkgs:
|
||||
+ result, errmsg = self._sig_check_pkg(po)
|
||||
+
|
||||
+ if result == 0:
|
||||
+ # Verified ok, or verify not req'd
|
||||
+ continue
|
||||
+
|
||||
+ elif result == 1:
|
||||
+ ay = self.base.conf.assumeyes and not self.base.conf.assumeno
|
||||
+ if (not sys.stdin or not sys.stdin.isatty()) and not ay:
|
||||
+ raise dnf.exceptions.Error(_('Refusing to automatically import keys when running ' \
|
||||
+ 'unattended.\nUse "-y" to override.'))
|
||||
+
|
||||
+ # the callback here expects to be able to take options which
|
||||
+ # userconfirm really doesn't... so fake it
|
||||
+ fn = lambda x, y, z: self.base.output.userconfirm()
|
||||
+ try:
|
||||
+ self._get_key_for_package(po, fn)
|
||||
+ except (dnf.exceptions.Error, ValueError) as e:
|
||||
+ error_messages.append(str(e))
|
||||
+
|
||||
+ else:
|
||||
+ # Fatal error
|
||||
+ error_messages.append(errmsg)
|
||||
+
|
||||
+ if error_messages:
|
||||
+ for msg in error_messages:
|
||||
+ logger.critical(msg)
|
||||
+ raise dnf.exceptions.Error(_("GPG check FAILED"))
|
||||
+
|
||||
--
|
||||
2.50.1
|
||||
|
||||
242
SOURCES/0024-multisig-Do-not-parse-OpenPGP-keys.patch
Normal file
242
SOURCES/0024-multisig-Do-not-parse-OpenPGP-keys.patch
Normal file
@ -0,0 +1,242 @@
|
||||
From 3fd00a0bf41ac2c9342e0ba1d8550ee1ac5f2604 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
|
||||
Date: Fri, 12 Sep 2025 18:12:38 +0200
|
||||
Subject: [PATCH] multisig: Do not parse OpenPGP keys
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
If a key in OpenPGP version 6 format format was defined for
|
||||
a repository, the key was always omitted and never imported to pqrpm
|
||||
key store. As a result, packages signed with that key could not be
|
||||
verified:
|
||||
|
||||
[...]
|
||||
Importing GPG key 0xC9D3B13C:
|
||||
Userid : "rsa4k_user"
|
||||
Fingerprint: CE77 8FBE 1E8D 4DD2 7691 FB33 CA58 1189 C9D3 B13C
|
||||
From : /root/repo/rsa4k.cert
|
||||
Key imported successfully
|
||||
foo 16 MB/s | 16 kB 00:00
|
||||
Import of key(s) didn't help, wrong key(s)?
|
||||
Public key for foo-1.0-1.noarch.rpm is not installed. Failing package is: foo-1.0-1.noarch
|
||||
GPG Keys are configured as: file:///root/repo/rsa4k.cert, file:///root/repo/mldsa87.cert
|
||||
Error: GPG check FAILED
|
||||
|
||||
The cause was that multisig plugin called dnf.crypto.retrieve() to
|
||||
obtain a list of OpenPGP objects from the gpgkey URLs. That DNF
|
||||
function uses dnf.crypto.rawkey2infos() to parse the OpenPGP packets
|
||||
with a gpgme library. But library does not support OpenPGPv6. As
|
||||
a result, multisig got an empty list of OpenPGP objects and dis not
|
||||
import any key in OpenPGPv6 format into pqrm key store.
|
||||
|
||||
Considering that the only available OpenPGPv6 parser is rpm-sequoia
|
||||
library wrapped by pqrpm's librpmio library, and this plugin cannot
|
||||
load them into its name space not to clash with the system librpmio
|
||||
library, an ideal fix would have to develop standalone executable on
|
||||
top of them and parse it output by this plugin. And considering that
|
||||
best code is no code,
|
||||
|
||||
I removed printing any details about the imported keys from this
|
||||
plugin. The only details about the key this plugin now prints is URL
|
||||
or local path to the key file:
|
||||
|
||||
Importing GPG keys from: /root/repos/opgp6/mldsa65.cert
|
||||
Is this ok [y/N]: y
|
||||
Key imported successfully
|
||||
|
||||
Because the user ID and key ID were also required for DNSSEC
|
||||
validation, I had to remove that code. This patch also removes all
|
||||
code that became unreachable because of this fix.
|
||||
|
||||
Another downside is that now the plugin cannot does not check whether
|
||||
a key has already been imported, resulting always asking a using for
|
||||
importing the keys associated with a repository the unverifiable
|
||||
package comes from.
|
||||
|
||||
Resolve: https://issues.redhat.com/browse/RHEL-114424
|
||||
Signed-off-by: Petr Písař <ppisar@redhat.com>
|
||||
---
|
||||
plugins/multisig.py | 115 +++++++++++++++-----------------------------
|
||||
1 file changed, 40 insertions(+), 75 deletions(-)
|
||||
|
||||
diff --git a/plugins/multisig.py b/plugins/multisig.py
|
||||
index 8735a26..f29e41f 100644
|
||||
--- a/plugins/multisig.py
|
||||
+++ b/plugins/multisig.py
|
||||
@@ -1,16 +1,26 @@
|
||||
from __future__ import print_function, absolute_import, unicode_literals
|
||||
import dnf
|
||||
-import dnf.crypto
|
||||
-import dnf.dnssec
|
||||
import dnf.exceptions
|
||||
from dnf.i18n import ucd
|
||||
import dnf.rpm.transaction
|
||||
import dnf.transaction
|
||||
+import dnf.util
|
||||
from dnfpluginscore import _, logger
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
+class MultiSigKey(object):
|
||||
+ def __init__(self, data, url):
|
||||
+ self.id_ = None
|
||||
+ self.fingerprint = None
|
||||
+ self.timestamp = None
|
||||
+ self.raw_key = data
|
||||
+ self.url = url
|
||||
+ self.userid = None
|
||||
+ self.short_id = None
|
||||
+ self.rpm_id = None
|
||||
+
|
||||
class MultiSig(dnf.Plugin):
|
||||
"""
|
||||
This plugin verifies signatures of RPM packages by executing an
|
||||
@@ -168,33 +178,6 @@ class MultiSig(dnf.Plugin):
|
||||
logger.debug(_("Multisig: verification result: {} (code={})").format(msg, result))
|
||||
return result, msg
|
||||
|
||||
- def keyInstalled(self, fingerprint):
|
||||
- '''
|
||||
- Return if the GPG key described by the given fingerprint is installed
|
||||
- in the multisig keyring.
|
||||
-
|
||||
- Return values:
|
||||
- - True key is installed
|
||||
- - False otherwise
|
||||
- Trows: If rpmkeys program could not been executed.
|
||||
-
|
||||
- No effort is made to handle duplicates.
|
||||
- '''
|
||||
- # XXX: rpmkeys expects lowercase
|
||||
- # <https://github.com/rpm-software-management/rpm/issues/3721>
|
||||
- logger.debug(_("Multisig: Checking a presence of key={}").format(fingerprint))
|
||||
- args = (self.rpmkeys_executable,
|
||||
- '--root', self.base.conf.installroot,
|
||||
- '--list', fingerprint.lower())
|
||||
- p = subprocess.run(
|
||||
- args=args,
|
||||
- executable=self.rpmkeys_executable,
|
||||
- cwd='/',
|
||||
- stdin=subprocess.DEVNULL,
|
||||
- stdout=subprocess.DEVNULL,
|
||||
- stderr=subprocess.DEVNULL)
|
||||
- return p.returncode == 0
|
||||
-
|
||||
def importKey(self, key):
|
||||
'''
|
||||
Import given Key object into the multisig keyring.
|
||||
@@ -232,6 +215,28 @@ class MultiSig(dnf.Plugin):
|
||||
returncode, stdout, stderr))
|
||||
return returncode == 0
|
||||
|
||||
+ def retrieve(self, keyurl, repo=None):
|
||||
+ """Retrieve a content of a key file specified by the URL using
|
||||
+ repository's proxy configuration.
|
||||
+
|
||||
+ :param keyurl URL of the key file
|
||||
+ :param repo repository object
|
||||
+ :returns: list of MultiSigKey objects populated from the key file
|
||||
+ """
|
||||
+ if keyurl.startswith('http:'):
|
||||
+ logger.warning(_("retrieving repo key for %s unencrypted from %s"), repo.id, keyurl)
|
||||
+ with dnf.util._urlopen(keyurl, repo=repo) as handle:
|
||||
+ # This is a place for parsing the key file and populating key ID etc.
|
||||
+ keyinfos = [MultiSigKey(handle.read(), keyurl)]
|
||||
+ return keyinfos
|
||||
+
|
||||
+ def log_key_import(self, keyinfo):
|
||||
+ """Print and log details about keys to be imported.
|
||||
+ """
|
||||
+ msg = (_('Importing GPG keys from: %s') %
|
||||
+ (keyinfo.url.replace("file://", "")))
|
||||
+ logger.critical("%s", msg)
|
||||
+
|
||||
def _get_key_for_package(self, po, askcb=None, fullaskcb=None):
|
||||
"""Retrieve a key for a package. If needed, use the given
|
||||
callback to prompt whether the key should be imported.
|
||||
@@ -264,53 +269,17 @@ class MultiSig(dnf.Plugin):
|
||||
user_cb_fail = False
|
||||
self._repo_set_imported_gpg_keys.append(repo.id)
|
||||
for keyurl in keyurls:
|
||||
- keys = dnf.crypto.retrieve(keyurl, repo)
|
||||
+ keys = self.retrieve(keyurl, repo)
|
||||
|
||||
for info in keys:
|
||||
- # Check if key is already installed
|
||||
- if self.keyInstalled(info.fingerprint):
|
||||
- msg = _('GPG key at %s (0x%s) is already installed')
|
||||
- logger.info(msg, keyurl, info.short_id)
|
||||
- continue
|
||||
-
|
||||
- # DNS Extension: create a key object, pass it to the verification class
|
||||
- # and print its result as an advice to the user.
|
||||
- if self.base.conf.gpgkey_dns_verification:
|
||||
- dns_input_key = dnf.dnssec.KeyInfo.from_rpm_key_object(info.userid,
|
||||
- info.raw_key)
|
||||
- dns_result = dnf.dnssec.DNSSECKeyVerification.verify(dns_input_key)
|
||||
- logger.info(dnf.dnssec.nice_user_msg(dns_input_key, dns_result))
|
||||
-
|
||||
# Try installing/updating GPG key
|
||||
info.url = keyurl
|
||||
- if self.base.conf.gpgkey_dns_verification:
|
||||
- dnf.crypto.log_dns_key_import(info, dns_result)
|
||||
- else:
|
||||
- dnf.crypto.log_key_import(info)
|
||||
+ self.log_key_import(info)
|
||||
rc = False
|
||||
if self.base.conf.assumeno:
|
||||
rc = False
|
||||
elif self.base.conf.assumeyes:
|
||||
- # DNS Extension: We assume, that the key is trusted in case it is valid,
|
||||
- # its existence is explicitly denied or in case the domain is not signed
|
||||
- # and therefore there is no way to know for sure (this is mainly for
|
||||
- # backward compatibility)
|
||||
- # FAQ:
|
||||
- # * What is PROVEN_NONEXISTENCE?
|
||||
- # In DNSSEC, your domain does not need to be signed, but this state
|
||||
- # (not signed) has to be proven by the upper domain. e.g. when example.com.
|
||||
- # is not signed, com. servers have to sign the message, that example.com.
|
||||
- # does not have any signing key (KSK to be more precise).
|
||||
- if self.base.conf.gpgkey_dns_verification:
|
||||
- if dns_result in (dnf.dnssec.Validity.VALID,
|
||||
- dnf.dnssec.Validity.PROVEN_NONEXISTENCE):
|
||||
- rc = True
|
||||
- logger.info(dnf.dnssec.any_msg(_("The key has been approved.")))
|
||||
- else:
|
||||
- rc = False
|
||||
- logger.info(dnf.dnssec.any_msg(_("The key has been rejected.")))
|
||||
- else:
|
||||
- rc = True
|
||||
+ rc = True
|
||||
|
||||
# grab the .sig/.asc for the keyurl, if it exists if it
|
||||
# does check the signature on the key if it is signed by
|
||||
@@ -318,11 +287,8 @@ class MultiSig(dnf.Plugin):
|
||||
# rc = True else ask as normal.
|
||||
|
||||
elif fullaskcb:
|
||||
- rc = fullaskcb({"po": po, "userid": info.userid,
|
||||
- "hexkeyid": info.short_id,
|
||||
- "keyurl": keyurl,
|
||||
- "fingerprint": info.fingerprint,
|
||||
- "timestamp": info.timestamp})
|
||||
+ rc = fullaskcb({"po": po,
|
||||
+ "keyurl": keyurl})
|
||||
elif askcb:
|
||||
rc = askcb(po, info.userid, info.short_id)
|
||||
|
||||
@@ -331,8 +297,7 @@ class MultiSig(dnf.Plugin):
|
||||
continue
|
||||
|
||||
# Import the key
|
||||
- # XXX: raw_key of second info erroneously contains first and
|
||||
- # second key. Probably a bug in key parser.
|
||||
+ # XXX: raw_key contains all keys found in the key file.
|
||||
#logger.debug(_("Multisig: Importing a key: {}").format(info.raw_key))
|
||||
result = self.importKey(info)
|
||||
if result == False:
|
||||
--
|
||||
2.51.0
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
From e1aebc68eb031f3e91ed39a0b145589f1a4a1734 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Petr=20P=C3=ADsa=C5=99?= <ppisar@redhat.com>
|
||||
Date: Fri, 3 Oct 2025 12:23:11 +0200
|
||||
Subject: [PATCH] multisig: Rename dnf4-multisig(8) manual page to
|
||||
dnf-multisig(8)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
To align with all other plugin manual pages.
|
||||
Create dnf4-multisig(8) symlink for compatibility.
|
||||
|
||||
FILE(CREATE_LINK) is available since cmake 3.14.
|
||||
|
||||
Resolve: https://issues.redhat.com/browse/RHEL-117134
|
||||
Signed-off-by: Petr Písař <ppisar@redhat.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
dnf-plugins-core.spec | 2 +-
|
||||
doc/CMakeLists.txt | 4 ++++
|
||||
doc/conf.py | 2 +-
|
||||
4 files changed, 7 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index a1eea7b..86225e7 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -1,5 +1,5 @@
|
||||
PROJECT (dnf-plugins-core NONE)
|
||||
-CMAKE_MINIMUM_REQUIRED (VERSION 2.4)
|
||||
+CMAKE_MINIMUM_REQUIRED (VERSION 3.14)
|
||||
|
||||
if (NOT WITHOUT_LOCAL)
|
||||
set (WITHOUT_LOCAL "0")
|
||||
diff --git a/dnf-plugins-core.spec b/dnf-plugins-core.spec
|
||||
index cb3b1b8..ff6beea 100644
|
||||
--- a/dnf-plugins-core.spec
|
||||
+++ b/dnf-plugins-core.spec
|
||||
@@ -40,7 +40,7 @@ License: GPLv2+
|
||||
URL: https://github.com/rpm-software-management/dnf-plugins-core
|
||||
Source0: %{url}/archive/%{version}/%{name}-%{version}.tar.gz
|
||||
BuildArch: noarch
|
||||
-BuildRequires: cmake
|
||||
+BuildRequires: cmake >= 3.14
|
||||
BuildRequires: gettext
|
||||
# Documentation
|
||||
%if %{with python3}
|
||||
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
|
||||
index 297506a..75e74bb 100644
|
||||
--- a/doc/CMakeLists.txt
|
||||
+++ b/doc/CMakeLists.txt
|
||||
@@ -48,6 +48,10 @@ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-migrate.8
|
||||
endif()
|
||||
|
||||
if (${PYTHON_VERSION_MAJOR} STREQUAL "3")
|
||||
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf-multisig.8
|
||||
+ DESTINATION share/man/man8)
|
||||
+FILE(CREATE_LINK dnf-multisig.8 ${CMAKE_CURRENT_BINARY_DIR}/dnf4-multisig.8
|
||||
+ SYMBOLIC)
|
||||
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/dnf4-multisig.8
|
||||
DESTINATION share/man/man8)
|
||||
endif()
|
||||
diff --git a/doc/conf.py b/doc/conf.py
|
||||
index 2845d18..225ae5f 100644
|
||||
--- a/doc/conf.py
|
||||
+++ b/doc/conf.py
|
||||
@@ -301,7 +301,7 @@ if sys.version_info[0] < 3:
|
||||
man_pages.append(('migrate', 'dnf-migrate', u'DNF migrate Plugin', AUTHORS, 8))
|
||||
|
||||
if sys.version_info[0] == 3:
|
||||
- man_pages.append(('multisig', 'dnf4-multisig', u'DNF multisig Plugin', AUTHORS, 8))
|
||||
+ man_pages.append(('multisig', 'dnf-multisig', u'DNF multisig Plugin', AUTHORS, 8))
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
--
|
||||
2.51.0
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
Name: dnf-plugins-core
|
||||
Version: 4.3.0
|
||||
Release: 20%{?dist}
|
||||
Release: 24%{?dist}
|
||||
Summary: Core Plugins for DNF
|
||||
License: GPLv2+
|
||||
URL: https://github.com/rpm-software-management/dnf-plugins-core
|
||||
@ -57,9 +57,13 @@ Patch18: 0018-system-upgrade-change-http-to-https-in-unit-file.patch
|
||||
Patch19: 0019-reposync-Respect-norepopath-with-metadata-path.patch
|
||||
Patch20: 0020-needs-restarting-Get-boot-time-from-systemd-UnitsLoa.patch
|
||||
Patch21: 0021-dnf-copr-enable-on-Asahi-Fedora-Linux-Remix-guesses.patch
|
||||
Patch22: 0022-reposync-Avoid-multiple-downloads-of-duplicate-packa.patch
|
||||
Patch23: 0023-multisig-A-new-plugin-for-verifying-extraordinary-RP.patch
|
||||
Patch24: 0024-multisig-Do-not-parse-OpenPGP-keys.patch
|
||||
Patch25: 0025-multisig-Rename-dnf4-multisig-8-manual-page-to-dnf-m.patch
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: cmake
|
||||
BuildRequires: cmake >= 3.14
|
||||
BuildRequires: gettext
|
||||
# Documentation
|
||||
%if %{with python3}
|
||||
@ -320,6 +324,18 @@ Obsoletes: python-dnf-plugins-extras-migrate < %{dnf_plugins_extra}
|
||||
Migrate Plugin for DNF, Python 2 version. Migrates history, group and yumdb data from yum to dnf.
|
||||
%endif
|
||||
|
||||
%if %{with python3}
|
||||
%package -n python3-dnf-plugin-multisig
|
||||
Summary: Multisig Plugin for DNF
|
||||
Requires: pqrpm
|
||||
Requires: python3-%{name} = %{version}-%{release}
|
||||
Provides: dnf-plugin-multisig = %{version}-%{release}
|
||||
|
||||
%description -n python3-dnf-plugin-multisig
|
||||
Multisig Plugin for DNF, Python 3 version. The plugin verifies multiple RPMv6
|
||||
signatures on RPMv4 packages by using an external rpmkeys program.
|
||||
%endif
|
||||
|
||||
%if %{with python2}
|
||||
%package -n python2-dnf-plugin-post-transaction-actions
|
||||
Summary: Post transaction actions Plugin for DNF
|
||||
@ -735,6 +751,13 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%exclude %{_mandir}/man8/dnf-migrate.*
|
||||
%endif
|
||||
|
||||
%if %{with python3}
|
||||
%files -n python3-dnf-plugin-multisig
|
||||
%{python3_sitelib}/dnf-plugins/multisig.*
|
||||
%{python3_sitelib}/dnf-plugins/__pycache__/multisig.*
|
||||
%{_mandir}/man8/dnf*-multisig.*
|
||||
%endif
|
||||
|
||||
%if %{with python2}
|
||||
%files -n python2-dnf-plugin-post-transaction-actions
|
||||
%config(noreplace) %{_sysconfdir}/dnf/plugins/post-transaction-actions.conf
|
||||
@ -804,6 +827,19 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Fri Oct 03 2025 Petr Pisar <ppisar@redhat.com> - 4.3.0-24
|
||||
- Rename dnf4-multisig(8) manual page to dnf-multisig(8) (RHEL-117134)
|
||||
|
||||
* Mon Sep 15 2025 Petr Pisar <ppisar@redhat.com> - 4.3.0-23
|
||||
- Fix importing OpenPGPv6 keys (RHEL-114424)
|
||||
|
||||
* Wed Jun 25 2025 Petr Pisar <ppisar@redhat.com> - 4.3.0-22
|
||||
- Add multisig plugin (RHEL-100157)
|
||||
|
||||
* Tue Mar 11 2025 Marek Blaha <mblaha@redhat.com> - 4.3.0-21
|
||||
- reposync: Avoid multiple downloads of duplicate packages (RHEL-64320)
|
||||
- Fix bogus changelog entry date
|
||||
|
||||
* Mon Dec 16 2024 Jan Kolarik <jkolarik@redhat.com> - 4.3.0-20
|
||||
- Add forgotten changelog
|
||||
|
||||
@ -865,7 +901,7 @@ ln -sf %{_mandir}/man1/%{yum_utils_subpackage_name}.1.gz %{buildroot}%{_mandir}/
|
||||
* Thu Jan 05 2023 Nicola Sella <nsella@redhat.com> - 4.3.0-3
|
||||
- Remove requirement of python3-distro
|
||||
|
||||
* Wed Dec 03 2022 Nicola Sella <nsella@redhat.com> - 4.3.0-2
|
||||
* Sat Dec 03 2022 Nicola Sella <nsella@redhat.com> - 4.3.0-2
|
||||
- Move system-upgrade plugin to core (RhBug:2054235)
|
||||
- offline-upgrade: add support for security filters (RhBug:1939975)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user