container: Add support for retrieving GPG keys using wget

- Check for errors when running the provisioning script.

- Check for errors when installing missing packages.

Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
fliu 2023-07-13 01:33:37 +00:00 committed by Sunil Mohan Adapa
parent de856b0d9c
commit da1e4ed505
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2

View File

@ -151,9 +151,11 @@ URLS = URLS_AMD64
TRUSTED_KEYS = ['D4B069124FCF43AA1FCD7FBC2ACFC1E15AF82D8C'] TRUSTED_KEYS = ['D4B069124FCF43AA1FCD7FBC2ACFC1E15AF82D8C']
KEY_SERVER = 'keyserver.ubuntu.com' KEY_SERVER = 'keyserver.ubuntu.com'
KEY_SERVER_HTTPS_API = \
'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x{key_id}'
PROVISION_SCRIPT = ''' PROVISION_SCRIPT = '''
set -x set -xe pipefail
cd /freedombox/ cd /freedombox/
@ -185,8 +187,8 @@ sudo chmod a+rw geckodriver.log
sudo mkdir -p .pytest_cache/ sudo mkdir -p .pytest_cache/
sudo chmod --recursive a+rw .pytest_cache/ sudo chmod --recursive a+rw .pytest_cache/
sudo chmod a+w /freedombox sudo chmod a+w /freedombox
sudo chmod --recursive --silent a+w htmlcov sudo chmod --recursive --silent a+w htmlcov || true
sudo chmod --silent a+w .coverage sudo chmod --silent a+w .coverage || true
exit 0 exit 0
''' # noqa ''' # noqa
@ -290,6 +292,8 @@ def parse_arguments():
help='Distribution of the image to download and setup') help='Distribution of the image to download and setup')
subparser.add_argument('--image-size', default='16G', subparser.add_argument('--image-size', default='16G',
help='Disk image size to resize to after download') help='Disk image size to resize to after download')
subparser.add_argument('--hkp-client', choices=('gpg', 'wget'),
default='gpg', help='Client for key retrieval')
# Print IP address # Print IP address
subparser = subparsers.add_parser( subparser = subparsers.add_parser(
@ -339,6 +343,8 @@ def parse_arguments():
subparser.add_argument('--distribution', choices=distributions, subparser.add_argument('--distribution', choices=distributions,
default=default_distribution, default=default_distribution,
help='Distribution of the image to update') help='Distribution of the image to update')
subparser.add_argument('--hkp-client', choices=('gpg', 'wget'),
default='gpg', help='Client for key retrieval')
# Display help message when no args are passed # Display help message when no args are passed
if len(sys.argv) == 1: if len(sys.argv) == 1:
@ -383,6 +389,7 @@ def _verify_dependencies():
'sgdisk': 'gdisk', 'sgdisk': 'gdisk',
'btrfs': 'btrfs-progs', 'btrfs': 'btrfs-progs',
'nmcli': 'network-manager', 'nmcli': 'network-manager',
'dnsmasq': 'dnsmasq',
'ssh': 'openssh-client', 'ssh': 'openssh-client',
'ssh-keygen': 'openssh-client', 'ssh-keygen': 'openssh-client',
} }
@ -405,8 +412,8 @@ def _verify_dependencies():
sys.exit(1) sys.exit(1)
logger.info('Running apt for missing packages: %s', logger.info('Running apt for missing packages: %s',
' '.join(missing_commands)) ' '.join(missing_packages))
subprocess.run(['sudo', 'apt', 'install'] + missing_packages, check=False) subprocess.run(['sudo', 'apt', 'install'] + missing_packages, check=True)
def _get_systemd_nspawn_version(): def _get_systemd_nspawn_version():
@ -440,7 +447,30 @@ def _download_file(url, target_file, force=False):
partial_file.rename(target_file) partial_file.rename(target_file)
def _verify_signature(data_file, signature_file): def _receive_keys_with_gpg(gpg_home, key_ids):
"""Use gpg to retrieve and import a list of keys."""
subprocess.run([
'gpg', '--quiet', '--homedir',
str(gpg_home), '--keyserver', KEY_SERVER, '--recv-keys'
] + key_ids, check=True)
def _receive_keys_with_wget(gpg_home, key_ids):
"""Use wget to retrieve a list of keys and import them."""
for key_id in key_ids:
# Download public key
logger.info('Getting public key %s', key_id)
url = KEY_SERVER_HTTPS_API.format(key_id=key_id)
wget_result = subprocess.run(
['wget', '--quiet', '--output-document=-', url], check=True,
capture_output=True)
public_key = wget_result.stdout
subprocess.run(
['gpg', '--quiet', '--homedir',
str(gpg_home), '--import=-'], input=public_key, check=True)
def _verify_signature(hkp_client, data_file, signature_file):
"""Verify the detached signature on a file using GPG.""" """Verify the detached signature on a file using GPG."""
verified_file = signature_file.with_suffix(signature_file.suffix + verified_file = signature_file.with_suffix(signature_file.suffix +
'.verified') '.verified')
@ -452,10 +482,11 @@ def _verify_signature(data_file, signature_file):
gpg_home.chmod(0o700) gpg_home.chmod(0o700)
logger.info('Receiving GPG keys') logger.info('Receiving GPG keys')
subprocess.run([ if hkp_client == 'wget':
'gpg', '--quiet', '--homedir', _receive_keys_with_wget(gpg_home, TRUSTED_KEYS)
str(gpg_home), '--keyserver', KEY_SERVER, '--recv-keys' else:
] + TRUSTED_KEYS, check=True) _receive_keys_with_gpg(gpg_home, TRUSTED_KEYS)
process = subprocess.run( process = subprocess.run(
['gpg', '--quiet', '--homedir', ['gpg', '--quiet', '--homedir',
str(gpg_home), '--armor', '--export'] + TRUSTED_KEYS, check=True, str(gpg_home), '--armor', '--export'] + TRUSTED_KEYS, check=True,
@ -518,7 +549,7 @@ def _get_overlay_folder(distribution):
return folder.resolve() return folder.resolve()
def _download_disk_image(distribution, force=False): def _download_disk_image(distribution, hkp_client, force=False):
"""Download and unpack FreedomBox disk image.""" """Download and unpack FreedomBox disk image."""
work_directory.mkdir(exist_ok=True) work_directory.mkdir(exist_ok=True)
@ -530,7 +561,7 @@ def _download_disk_image(distribution, force=False):
signature_file = target_file.with_suffix(target_file.suffix + '.sig') signature_file = target_file.with_suffix(target_file.suffix + '.sig')
_download_file(url + '.sig', signature_file, force=force) _download_file(url + '.sig', signature_file, force=force)
_verify_signature(target_file, signature_file) _verify_signature(hkp_client, target_file, signature_file)
return _extract_image(target_file) return _extract_image(target_file)
@ -547,6 +578,7 @@ def _get_partition_info(image_file):
if line.startswith('Partition Table:'): if line.startswith('Partition Table:'):
partition_table_type = line.partition(': ')[2] partition_table_type = line.partition(': ')[2]
logger.info('Main partition: %s', last_partition_number)
return partition_table_type, last_partition_number return partition_table_type, last_partition_number
@ -750,6 +782,7 @@ def _setup(image_file, distribution):
_setup_nm_connection(distribution) _setup_nm_connection(distribution)
setup_file.touch() setup_file.touch()
logger.info('Setup completed')
def _create_nspawn_machine(image_file, distribution): def _create_nspawn_machine(image_file, distribution):
@ -820,9 +853,10 @@ def _launch(image_file, distribution):
logger.info('Bringing up host network connection: %s', logger.info('Bringing up host network connection: %s',
f'fbx-{distribution}-shared') f'fbx-{distribution}-shared')
# This command requires dnsmasq
subprocess.run( subprocess.run(
['sudo', 'nmcli', 'connection', 'up', f'fbx-{distribution}-shared'], ['sudo', 'nmcli', 'connection', 'up', f'fbx-{distribution}-shared'],
stdout=subprocess.DEVNULL, check=False) stdout=subprocess.DEVNULL, check=True)
def _stop(distribution): def _stop(distribution):
@ -907,6 +941,7 @@ def _provision(image_file, distribution):
input=PROVISION_SCRIPT.encode()) input=PROVISION_SCRIPT.encode())
provision_file.touch() provision_file.touch()
logger.info('Provision completed')
def _print_banner(distribution): def _print_banner(distribution):
@ -1023,7 +1058,8 @@ def subcommand_up(arguments):
_verify_dependencies() _verify_dependencies()
_get_systemd_nspawn_version() _get_systemd_nspawn_version()
image_file = _download_disk_image(arguments.distribution) image_file = _download_disk_image(arguments.distribution,
arguments.hkp_client)
_resize_disk_image(image_file, arguments.image_size) _resize_disk_image(image_file, arguments.image_size)
_setup(image_file, arguments.distribution) _setup(image_file, arguments.distribution)
_launch(image_file, arguments.distribution) _launch(image_file, arguments.distribution)
@ -1077,7 +1113,8 @@ def subcommand_update(arguments):
"""Update the disk image.""" """Update the disk image."""
if _is_update_required(arguments.distribution): if _is_update_required(arguments.distribution):
logger.info("Updating...") logger.info("Updating...")
_download_disk_image(arguments.distribution, force=True) _download_disk_image(arguments.distribution, arguments.hkp_client,
force=True)
else: else:
logger.info("Already using the latest image") logger.info("Already using the latest image")