diff --git a/README.md b/README.md index 3bceee4d..8238d41a 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,6 @@ Setup Known to work on Debian Buster (10) and newer. Required packages: * ostree - * gnupg * python3 Image signing diff --git a/config/defaults.ini b/config/defaults.ini index d4cd16c1..940807e8 100644 --- a/config/defaults.ini +++ b/config/defaults.ini @@ -47,7 +47,6 @@ tmpconfig = ${tmpdir}/config.ini tmpfullconfig = ${tmpdir}/fullconfig.ini baselib = ${srcdir}/lib/eib.sh ssh_options = -i ${sysconfdir}/ssh-key.pem -o StrictHostKeyChecking=no -keyring = ${tmpdir}/eib-keyring.gpg manifestdir = ${tmpdir}/manifest [buildroot] diff --git a/lib/eib.py b/lib/eib.py index 7531d7ea..bbfea1ad 100644 --- a/lib/eib.py +++ b/lib/eib.py @@ -40,7 +40,6 @@ import struct import subprocess import sys -import tempfile import time logger = logging.getLogger(__name__) @@ -402,49 +401,27 @@ def setup_logging(): logging.basicConfig(level=level, format=log_format, datefmt=date_format) -def get_keyring(config): - """Get the path to the temporary GPG keyring +def get_ostree_trusted_keys(config): + """Get the paths to all ostree GPG trusted keys - If it doesn't exist, it will be created. + All GPG keys in data/keys/*.asc and /data/keys/*.asc are + included. """ - keyring = config['build']['keyring'] - - if not os.path.isfile(keyring): - logger.info('Creating temporary GPG keyring %s', keyring) - - keyspaths = [os.path.join(config['build']['datadir'], 'keys')] - if 'localdatadir' in config['build']: - keyspaths.append(os.path.join(config['build']['localdatadir'], - 'keys')) - - keysdirs = list(filter(os.path.isdir, keyspaths)) - if len(keysdirs) == 0: - raise ImageBuildError('No gpg keys directories at', - ' or '.join(keyspaths)) - - keys = list(itertools.chain.from_iterable( - [glob.iglob(os.path.join(d, '*.asc')) for d in keysdirs] - )) - if len(keys) == 0: - raise ImageBuildError('No gpg keys in', ' or '.join(keysdirs)) - - # Use a temporary gpg homedir - with tempfile.TemporaryDirectory(dir=config['build']['tmpdir'], - prefix='eib-keyring') as homedir: - # Import the keys - for key in keys: - logger.info('Importing GPG key %s', key) - subprocess.check_call(['gpg', '--batch', '--quiet', - '--homedir', homedir, - '--import', key]) - - # Export all the keys as a normal PGP stream since newer - # gnupg imports to a keybox. - subprocess.check_call(['gpg', '--batch', '--quiet', - '--homedir', homedir, - '--export', '--output', keyring]) - - return keyring + keyspaths = [os.path.join(config['build']['datadir'], 'keys')] + if 'localdatadir' in config['build']: + keyspaths.append(os.path.join(config['build']['localdatadir'], 'keys')) + + keysdirs = list(filter(os.path.isdir, keyspaths)) + if len(keysdirs) == 0: + raise ImageBuildError('No gpg keys directories in', ' '.join(keyspaths)) + + keys = sorted(itertools.chain.from_iterable( + glob.iglob(os.path.join(d, '*.asc')) for d in keysdirs + )) + if len(keys) == 0: + raise ImageBuildError('No gpg keys in', ' '.join(keysdirs)) + + return keys def disk_usage(path): diff --git a/run-build b/run-build index f5055f48..82bc0624 100755 --- a/run-build +++ b/run-build @@ -592,8 +592,8 @@ class ImageBuilder(object): else: remote_url = self.config['ostree']['dev_pull_url'] - # Make sure the keyring is created - keyring = eib.get_keyring(self.config) + # Get the list of ostree trusted keys. + keys = eib.get_ostree_trusted_keys(self.config) # Recreate the remote setup to ensure there aren't any stale settings. Don't # bother GPG verifying the summary if it's being fetched over HTTPS. This often @@ -611,17 +611,20 @@ class ImageBuilder(object): '--if-exists', remote, ]) - subprocess.check_call([ + remote_add_cmd = [ 'ostree', f'--repo={repo_path}', 'remote', 'add', f'--set=gpg-verify-summary={gpg_verify_summary_str}', - f'--gpg-import={keyring}', + ] + remote_add_cmd += [f'--gpg-import={key}' for key in keys] + remote_add_cmd += [ remote, remote_url, self.config['ostree']['ref'], - ]) + ] + subprocess.check_call(remote_add_cmd) # Prune the builder's ostree to keep the local repo from growing unbounded. Only # the latest commit on each ref is needed to minimize the pull size. diff --git a/tests/eib/test_keyring.py b/tests/eib/test_keyring.py deleted file mode 100644 index 325c0221..00000000 --- a/tests/eib/test_keyring.py +++ /dev/null @@ -1,125 +0,0 @@ -# Tests for eib keyring handling - -import eib -import os -import pytest -import shutil -import subprocess -import tempfile - -from ..util import TESTSDIR, TEST_KEY_IDS - - -@pytest.fixture -def keyring_config(tmp_path, config): - config['build']['tmpdir'] = str(tmp_path) - - keyring = tmp_path / 'keyring.gpg' - config['build']['keyring'] = str(keyring) - - datadir = tmp_path / 'data' - config['build']['datadir'] = str(datadir) - - localdatadir = tmp_path / 'local' / 'data' - config['build']['localdatadir'] = str(localdatadir) - - return config - - -def test_errors(keyring_config): - """Test errors from get_keyring""" - with pytest.raises(eib.ImageBuildError, match='No gpg keys directories'): - eib.get_keyring(keyring_config) - - os.makedirs(os.path.join(keyring_config['build']['datadir'], 'keys')) - os.makedirs(os.path.join(keyring_config['build']['localdatadir'], 'keys')) - with pytest.raises(eib.ImageBuildError, match='No gpg keys in'): - eib.get_keyring(keyring_config) - - -def test_create_once(keyring_config, caplog): - """Test keyring is only created once""" - keysdir = os.path.join(keyring_config['build']['datadir'], 'keys') - testdatadir = os.path.join(TESTSDIR, 'data') - os.makedirs(keysdir) - shutil.copy2(os.path.join(testdatadir, 'test1.asc'), keysdir) - - keyring = eib.get_keyring(keyring_config) - assert keyring == keyring_config['build']['keyring'] - assert os.path.exists(keyring) - assert 'Creating temporary GPG keyring' in caplog.text - - caplog.clear() - eib.get_keyring(keyring_config) - assert 'Creating temporary GPG keyring' not in caplog.text - - -def get_keyring_ids(keyring): - """Get the public key IDs from a GPG keyring""" - # gpg insists on creating a homedir, so appease it - with tempfile.TemporaryDirectory() as homedir: - # --show-keys would be more convenient, but that's only - # --available in newer gnupg - output = subprocess.check_output( - ('gpg', '--homedir', homedir, '--list-keys', '--with-colons', - '--no-default-keyring', '--keyring', keyring) - ) - for line in output.decode('utf-8').splitlines(): - parts = line.split(':') - if parts[0] == 'pub': - yield parts[4] - - -def test_imported_keys(keyring_config): - """Test the keys are imported correctly""" - keysdir = os.path.join(keyring_config['build']['datadir'], 'keys') - localkeysdir = os.path.join(keyring_config['build']['localdatadir'], - 'keys') - testdatadir = os.path.join(TESTSDIR, 'data') - os.makedirs(keysdir) - os.makedirs(localkeysdir) - - shutil.copy2(os.path.join(testdatadir, 'test1.asc'), keysdir) - keyring = eib.get_keyring(keyring_config) - key_ids = set(get_keyring_ids(keyring)) - assert key_ids == {TEST_KEY_IDS['test1']} - - os.unlink(keyring) - shutil.copy2(os.path.join(testdatadir, 'test2.asc'), keysdir) - keyring = eib.get_keyring(keyring_config) - key_ids = set(get_keyring_ids(keyring)) - assert key_ids == {TEST_KEY_IDS['test1'], TEST_KEY_IDS['test2']} - - os.unlink(keyring) - shutil.copy2(os.path.join(testdatadir, 'test3.asc'), localkeysdir) - keyring = eib.get_keyring(keyring_config) - key_ids = set(get_keyring_ids(keyring)) - assert key_ids == set(TEST_KEY_IDS.values()) - - -def test_verification(keyring_config, tmp_path): - """Test that signatures can be verified from the generated keyring""" - testdatadir = os.path.join(TESTSDIR, 'data') - - homedir = tmp_path / 'gnupg' - homedir.mkdir(mode=0o700) - subprocess.check_call( - ('gpg', '--homedir', str(homedir), '--import', - os.path.join(testdatadir, 'test1.key')) - ) - - signed_file = tmp_path / 'signed' - subprocess.run( - ('gpg', '--homedir', str(homedir), '--clearsign', - '--output', str(signed_file)), - check=True, input=b'foobar\n' - ) - - keysdir = os.path.join(keyring_config['build']['datadir'], 'keys') - os.makedirs(keysdir) - shutil.copy2(os.path.join(testdatadir, 'test1.asc'), keysdir) - keyring = eib.get_keyring(keyring_config) - - subprocess.check_call( - ('gpgv', '--keyring', keyring, str(signed_file)) - ) diff --git a/tests/eib/test_ostree_trusted_keys.py b/tests/eib/test_ostree_trusted_keys.py new file mode 100644 index 00000000..2ca75732 --- /dev/null +++ b/tests/eib/test_ostree_trusted_keys.py @@ -0,0 +1,61 @@ +# Tests for eib ostree trusted key handling + +import eib +import os +import pytest +import shutil + +from ..util import TESTSDIR + + +@pytest.fixture +def keys_config(tmp_path, config): + config['build']['tmpdir'] = str(tmp_path) + + datadir = tmp_path / 'data' + config['build']['datadir'] = str(datadir) + + localdatadir = tmp_path / 'local' / 'data' + config['build']['localdatadir'] = str(localdatadir) + + return config + + +def test_errors(keys_config): + """Test errors from get_ostree_trusted_keys""" + with pytest.raises(eib.ImageBuildError, match='No gpg keys directories'): + eib.get_ostree_trusted_keys(keys_config) + + os.makedirs(os.path.join(keys_config['build']['datadir'], 'keys')) + os.makedirs(os.path.join(keys_config['build']['localdatadir'], 'keys')) + with pytest.raises(eib.ImageBuildError, match='No gpg keys in'): + eib.get_ostree_trusted_keys(keys_config) + + +def test_get_keys(keys_config): + """Test the keys are gathered correctly""" + keysdir = os.path.join(keys_config['build']['datadir'], 'keys') + localkeysdir = os.path.join(keys_config['build']['localdatadir'], + 'keys') + testdatadir = os.path.join(TESTSDIR, 'data') + os.makedirs(keysdir) + os.makedirs(localkeysdir) + + shutil.copy2(os.path.join(testdatadir, 'test1.asc'), keysdir) + keys = eib.get_ostree_trusted_keys(keys_config) + assert keys == [os.path.join(keysdir, 'test1.asc')] + + shutil.copy2(os.path.join(testdatadir, 'test2.asc'), keysdir) + keys = eib.get_ostree_trusted_keys(keys_config) + assert keys == [ + os.path.join(keysdir, 'test1.asc'), + os.path.join(keysdir, 'test2.asc'), + ] + + shutil.copy2(os.path.join(testdatadir, 'test3.asc'), localkeysdir) + keys = eib.get_ostree_trusted_keys(keys_config) + assert keys == [ + os.path.join(keysdir, 'test1.asc'), + os.path.join(keysdir, 'test2.asc'), + os.path.join(localkeysdir, 'test3.asc'), + ]