From 1c1f50f9010317d363d2692da76c0dd9794a721e Mon Sep 17 00:00:00 2001 From: Ken Dreyer Date: Feb 22 2021 18:58:18 +0000 Subject: write-repo-file: add --gpg-key option Prior to this change, compose-write-repo-file unconditionally wrote .repo files with "gpgcheck = 0". Add a new --gpg-key option to compose-write-repo-file. When a user specifies a comma-separated key ID and key URL, we sanity-check that every every RPM is signed by this key. For each variant with signed RPMs, we set the "gpgcheck = 1" and "gpgkey" options in the .repo file. --- diff --git a/compose_utils/repo_file.py b/compose_utils/repo_file.py index 28d4fbe..7ff167f 100644 --- a/compose_utils/repo_file.py +++ b/compose_utils/repo_file.py @@ -13,7 +13,7 @@ REPO = """\ name = {name} baseurl = {baseurl} enabled = {enabled} -gpgcheck = 0 +gpgcheck = {gpgcheck} """ CONTENT_TYPES = { @@ -39,6 +39,35 @@ def is_url(path): return False +def get_variant_gpg_key_id(compose, variant): + """ + If all RPMs are signed by a single key ID, return that key ID. + + :returns str: a key ID, like "fd431d51" for Red Hat's GA signing key, or + "f21541eb" for Red Hat's beta signing key. + :returns None: if any RPMs are unsigned or if RPMs are signed by more than + one key. + """ + key = None + # TODO: research the following: + # - Is "variant.uid" the right value to use here? + # - How does this code relate to debuginfo files? + arches_packages = compose.rpms.rpms[variant.uid] + for arch_packages in arches_packages.values(): + for package in arch_packages.values(): + for rpm in package.values(): + if key is None and rpm['sigkey'] is not None: + key = rpm['sigkey'] + if key is not None and rpm['sigkey'] is None: + # log.info('%s is unsigned', pkgfile['path']) + return None + if key is not None and key != rpm['sigkey']: + # log.info('multiple keys found: %s and %s', key, + # pkgfile['sigkey']) + return None + return key + + def emit(compose, opts, variant, output, content_type="repository"): """Print configuration for a single repository into output.""" paths = getattr(variant.paths, content_type) @@ -86,7 +115,22 @@ def emit(compose, opts, variant, output, content_type="repository"): if not is_url(baseurl): baseurl = "file://" + baseurl - content = REPO.format(name=name, enabled=enabled, baseurl=baseurl) + # Are all the packages in this variant signed by the provided GPG key? + key_url = None + gpgcheck = "0" + if opts.gpg_key: + key_id = get_variant_gpg_key_id(compose, variant) + if key_id == opts.gpg_key[0]: + gpgcheck = "1" + key_url = opts.gpg_key[1] + + content = REPO.format(name=name, + enabled=enabled, + baseurl=baseurl, + gpgcheck=gpgcheck) + if key_url: + content += "gpgkey = %s\n" % key_url + print(content, file=output) @@ -156,6 +200,13 @@ def main(args=None): type=parse_mapping, ) parser.add_argument( + "--gpg-key", + metavar="KEY_ID,KEY_URL", + help="Enable GPG verification. For example: " + "fd431d51,file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release", + type=parse_mapping, + ) + parser.add_argument( "--name-pattern", default="{release_short}-{release_version}-{variant}", help="Pattern for repository names.", diff --git a/doc/compose-write-repo-file.1 b/doc/compose-write-repo-file.1 index 3747876..d854827 100644 --- a/doc/compose-write-repo-file.1 +++ b/doc/compose-write-repo-file.1 @@ -39,6 +39,11 @@ By default local filepaths are created. With this option it is possible to generate URLs. The path prefix will be stripped from path, and replaced with URL prefix. .TP +.BR \-\-gpg\-key = \fIKEY_ID,KEY_URL\fR +Enable GPG verification. For example, \fB\-\-gpg\-key +fd431d51,file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release\fR will set +\fBgpgcheck = 1\fR in the repo file if all RPMs are signed with the \fBfd431d51\fR key. +.TP .BR \-\-name\-pattern = \fIPATTERN\fR Customize name for the repositories. These fragments are available: .sp diff --git a/tests/fixtures/signed.repo b/tests/fixtures/signed.repo new file mode 100644 index 0000000..62ee2c6 --- /dev/null +++ b/tests/fixtures/signed.repo @@ -0,0 +1,7 @@ +[Fedora-Modular-Bikeshed-Server-rpms] +name = Fedora-Modular-Bikeshed-Server-rpms +baseurl = file:///composes/Fedora-Modular-Bikeshed-20171004.n.0/compose/Server/$basearch/os +enabled = 1 +gpgcheck = 1 +gpgkey = file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-modularity + diff --git a/tests/test_repo_file.py b/tests/test_repo_file.py index 285d857..6f7719d 100644 --- a/tests/test_repo_file.py +++ b/tests/test_repo_file.py @@ -12,7 +12,7 @@ except ImportError: import mock from six import StringIO -from .helpers import get_compose_path, get_fixture +from .helpers import get_compose, get_compose_path, get_fixture from compose_utils import repo_file @@ -101,7 +101,28 @@ class TestRepoFile(unittest.TestCase): "/Client/$basearch/os does not start with /foo/bar.\n", mock_err.getvalue() ) + def test_gpg_key(self): + self.success_run( + ["--gpg-key", "a3cc4e62,file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-modularity"], + "Fedora-Modular-Bikeshed-20171004.n.0", + "signed.repo", + ) + def test_is_url(self): self.assertTrue(repo_file.is_url('http://example.com/foobar')) self.assertTrue(repo_file.is_url('https://example.com/foobar')) self.assertFalse(repo_file.is_url('/path/to/foobar')) + + def test_get_variant_gpg_key_id_unsigned(self): + compose = get_compose("DP-1.0-20160315.t.0") + variants = [v for v in compose.info.variants.variants.values()] + for variant in variants: + result = repo_file.get_variant_gpg_key_id(compose, variant) + self.assertIsNone(result) + + def test_get_variant_gpg_key_id_signed(self): + compose = get_compose("Fedora-Modular-Bikeshed-20171004.n.0") + variants = [v for v in compose.info.variants.variants.values()] + variant = variants[0] + result = repo_file.get_variant_gpg_key_id(compose, variant) + self.assertEqual(result, 'a3cc4e62')