qvm-template: use key specified in the repo definition if possible

This makes the package verified against _only_ the key specified in the
repo config, not all the trusted keys.
If repo does not specify a key, use the default one (change this to a
single file, instead of the whole directory). Existing 'gpgkey' entry
pointing at non-existing file will result in an error.
This commit is contained in:
Marek Marczykowski-Górecki 2021-02-06 04:49:54 +01:00
parent 4f9757ca88
commit b7446afe3b
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 47 additions and 16 deletions

View File

@ -152,7 +152,7 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
]) ])
self.assertAllCalled() self.assertAllCalled()
@mock.patch('qubesadmin.tools.qvm_template.get_keys') @mock.patch('qubesadmin.tools.qvm_template.get_keys_for_repos')
def test_090_install_lock(self, mock_get_keys): def test_090_install_lock(self, mock_get_keys):
class SuccessError(Exception): class SuccessError(Exception):
pass pass
@ -251,6 +251,8 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
keyring='/tmp', keyring='/tmp',
nogpgcheck=False, nogpgcheck=False,
cachedir='/var/cache/qvm-template', cachedir='/var/cache/qvm-template',
repo_files=[],
releasever='4.1',
yes=False, yes=False,
allow_pv=False, allow_pv=False,
pool=None pool=None
@ -357,6 +359,8 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
keyring='/tmp', keyring='/tmp',
nogpgcheck=False, nogpgcheck=False,
cachedir='/var/cache/qvm-template', cachedir='/var/cache/qvm-template',
repo_files=[],
releasever='4.1',
yes=False, yes=False,
allow_pv=True, allow_pv=True,
pool='my-pool' pool='my-pool'
@ -432,6 +436,8 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
keyring='/tmp', keyring='/tmp',
nogpgcheck=False, nogpgcheck=False,
cachedir='/var/cache/qvm-template', cachedir='/var/cache/qvm-template',
repo_files=[],
releasever='4.1',
yes=False, yes=False,
allow_pv=False, allow_pv=False,
pool=None pool=None
@ -496,6 +502,8 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
keyring='/tmp', keyring='/tmp',
nogpgcheck=False, nogpgcheck=False,
cachedir='/var/cache/qvm-template', cachedir='/var/cache/qvm-template',
repo_files=[],
releasever='4.1',
yes=False, yes=False,
allow_pv=False, allow_pv=False,
pool=None pool=None
@ -563,6 +571,8 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
keyring='/tmp', keyring='/tmp',
nogpgcheck=False, nogpgcheck=False,
cachedir='/var/cache/qvm-template', cachedir='/var/cache/qvm-template',
repo_files=[],
releasever='4.1',
yes=False, yes=False,
allow_pv=False, allow_pv=False,
pool=None pool=None
@ -611,6 +621,8 @@ class TC_00_qvm_template(qubesadmin.tests.QubesTestCase):
keyring='/tmp', keyring='/tmp',
nogpgcheck=False, nogpgcheck=False,
cachedir='/var/cache/qvm-template', cachedir='/var/cache/qvm-template',
repo_files=[],
releasever='4.1',
yes=False, yes=False,
allow_pv=False, allow_pv=False,
pool=None pool=None

View File

@ -21,6 +21,7 @@
import argparse import argparse
import collections import collections
import configparser
import datetime import datetime
import enum import enum
import fcntl import fcntl
@ -104,8 +105,10 @@ def get_parser() -> argparse.ArgumentParser:
help=('Specify files containing DNF repository configuration.' help=('Specify files containing DNF repository configuration.'
' Can be used more than once.')) ' Can be used more than once.'))
parser_main.add_argument('--keyring', parser_main.add_argument('--keyring',
default='/etc/qubes/repo-templates/keys', default='/etc/qubes/repo-templates/keys/RPM-GPG-KEY-qubes-4.1-primary',
help='Specify directory containing RPM public keys.') help='Specify a file containing default RPM public key. '
'Individual repositories may point at repo-specific key '
'using \'gpgkey\' option')
parser_main.add_argument('--updatevm', default=UPDATEVM, parser_main.add_argument('--updatevm', default=UPDATEVM,
help=('Specify VM to download updates from.' help=('Specify VM to download updates from.'
' (Set to empty string to specify the current VM.)')) ' (Set to empty string to specify the current VM.)'))
@ -573,18 +576,32 @@ def qrexec_download(
raise ConnectionError( raise ConnectionError(
"qrexec call 'qubes.TemplateDownload' failed.") "qrexec call 'qubes.TemplateDownload' failed.")
def get_keys(key_dir: str) -> typing.List[str]:
"""List gpg keys""" def get_keys_for_repos(repo_files: typing.List[str],
keys = [] releasever: str) -> typing.Dict[str, str]:
for name in os.listdir(key_dir): """List gpg keys
path = os.path.join(key_dir, name)
if os.path.isfile(path): Returns a dict reponame -> key path
keys.append(path) """
keys = {}
for repo_file in repo_files:
repo_config = configparser.ConfigParser()
repo_config.read(repo_file)
for repo in repo_config.sections():
try:
gpgkey_url = repo_config.get(repo, 'gpgkey')
except configparser.NoOptionError:
continue
gpgkey_url = gpgkey_url.replace('$releasever', releasever)
# support only file:// urls
if gpgkey_url.startswith('file://'):
keys[repo] = gpgkey_url[len('file://'):]
return keys return keys
def verify_rpm( def verify_rpm(
path: str, path: str,
keys: typing.List[str], key: str,
nogpgcheck: bool = False nogpgcheck: bool = False
) -> rpm.hdr: ) -> rpm.hdr:
"""Verify the digest and signature of a RPM package and return the package """Verify the digest and signature of a RPM package and return the package
@ -602,7 +619,6 @@ def verify_rpm(
""" """
if not nogpgcheck: if not nogpgcheck:
with tempfile.TemporaryDirectory() as rpmdb_dir: with tempfile.TemporaryDirectory() as rpmdb_dir:
for key in keys:
subprocess.check_call( subprocess.check_call(
['rpmkeys', '--dbpath=' + rpmdb_dir, '--import', key]) ['rpmkeys', '--dbpath=' + rpmdb_dir, '--import', key])
try: try:
@ -834,7 +850,7 @@ def install(
:param override_existing: Whether to override existing packages. Used for :param override_existing: Whether to override existing packages. Used for
reinstall, upgrade, and downgrade operations reinstall, upgrade, and downgrade operations
""" """
keys = get_keys(args.keyring) keys = get_keys_for_repos(args.repo_files, args.releasever)
unverified_rpm_list = [] # rpmfile, reponame unverified_rpm_list = [] # rpmfile, reponame
verified_rpm_list = [] verified_rpm_list = []
@ -847,7 +863,10 @@ def install(
else: else:
path = rpmfile path = rpmfile
package_hdr = verify_rpm(path, keys, args.nogpgcheck) repo_key = keys.get(reponame)
if repo_key is None:
repo_key = args.keyring
package_hdr = verify_rpm(path, repo_key, args.nogpgcheck)
if not package_hdr: if not package_hdr:
parser.error('Package \'%s\' verification failed.' % rpmfile) parser.error('Package \'%s\' verification failed.' % rpmfile)