From c39cd4ea47ce529fc19fa3dd1d4f0f73bb8adeeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Tue, 4 Mar 2025 10:09:27 +0100 Subject: [PATCH] Allow a different runtime on `dangerzone-image` commands. This can be done with the newly added `--runtime` flag, which needs to be passed to the first group, e.g: ```bash dangerzone-cli --runtime docker COMMAND ``` --- dangerzone/container_utils.py | 1 - dangerzone/updater/cli.py | 24 ++++++++++++++++++++---- dangerzone/updater/signatures.py | 19 +++++++++++-------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/dangerzone/container_utils.py b/dangerzone/container_utils.py index 26621fb..3a0fbbf 100644 --- a/dangerzone/container_utils.py +++ b/dangerzone/container_utils.py @@ -240,7 +240,6 @@ def container_pull(image: str, manifest_digest: str, callback: Callable): stderr=subprocess.STDOUT, text=True, bufsize=1, - universal_newlines=True, ) for line in process.stdout: # type: ignore diff --git a/dangerzone/updater/cli.py b/dangerzone/updater/cli.py index ee0915b..463722e 100644 --- a/dangerzone/updater/cli.py +++ b/dangerzone/updater/cli.py @@ -1,9 +1,12 @@ #!/usr/bin/python +import functools import logging import click +from .. import container_utils +from ..container_utils import get_runtime_name from . import attestations, errors, log, registry, signatures DEFAULT_REPOSITORY = "freedomofpress/dangerzone" @@ -13,7 +16,8 @@ DEFAULT_IMAGE_NAME = "ghcr.io/freedomofpress/dangerzone/dangerzone" @click.group() @click.option("--debug", is_flag=True) -def main(debug: bool) -> None: +@click.option("--runtime", default=get_runtime_name()) +def main(debug: bool, runtime: str) -> None: if debug: click.echo("Debug mode enabled") level = logging.DEBUG @@ -21,6 +25,10 @@ def main(debug: bool) -> None: level = logging.INFO logging.basicConfig(level=level) + if runtime != get_runtime_name(): + click.echo(f"Using container runtime: {runtime}") + container_utils.RUNTIME_NAME = runtime + @main.command() @click.argument("image", default=DEFAULT_IMAGE_NAME) @@ -28,8 +36,10 @@ def main(debug: bool) -> None: def upgrade(image: str, pubkey: str) -> None: """Upgrade the image to the latest signed version.""" manifest_digest = registry.get_manifest_digest(image) + try: - signatures.upgrade_container_image(image, manifest_digest, pubkey) + callback = functools.partial(click.echo, nl=False) + signatures.upgrade_container_image(image, manifest_digest, pubkey, callback) click.echo(f"✅ The local image {image} has been upgraded") click.echo(f"✅ The image has been signed with {pubkey}") click.echo(f"✅ Signatures has been verified and stored locally") @@ -56,17 +66,23 @@ def store_signatures(image: str, pubkey: str) -> None: @main.command() @click.argument("image_filename") @click.option("--pubkey", default=signatures.DEFAULT_PUBKEY_LOCATION) -def load_archive(image_filename: str, pubkey: str) -> None: +@click.option("--force", is_flag=True) +def load_archive(image_filename: str, pubkey: str, force: bool) -> None: """Upgrade the local image to the one in the archive.""" try: loaded_image = signatures.upgrade_container_image_airgapped( - image_filename, pubkey + image_filename, pubkey, bypass_logindex=force ) click.echo( f"✅ Installed image {image_filename} on the system as {loaded_image}" ) except errors.ImageAlreadyUpToDate as e: click.echo(f"✅ {e}") + except errors.InvalidLogIndex as e: + click.echo(f"❌ Trying to install image older that the currently installed one") + raise click.Abort() + except Exception as e: + click.echo(f"❌ {e}") raise click.Abort() diff --git a/dangerzone/updater/signatures.py b/dangerzone/updater/signatures.py index 6cdf879..3b01b04 100644 --- a/dangerzone/updater/signatures.py +++ b/dangerzone/updater/signatures.py @@ -215,7 +215,9 @@ def _get_blob(tmpdir: str, digest: str) -> Path: return Path(tmpdir) / "blobs" / "sha256" / digest.replace("sha256:", "") -def upgrade_container_image_airgapped(container_tar: str, pubkey: str) -> str: +def upgrade_container_image_airgapped( + container_tar: str, pubkey: str, bypass_logindex: bool = False +) -> str: """ Verify the given archive against its self-contained signatures, then upgrade the image and retag it to the expected tag. @@ -261,14 +263,15 @@ def upgrade_container_image_airgapped(container_tar: str, pubkey: str) -> str: image_name, signatures = convert_oci_images_signatures(json.load(f), tmpdir) log.info(f"Found image name: {image_name}") - # Ensure that we only upgrade if the log index is higher than the last known one - incoming_log_index = get_log_index_from_signatures(signatures) - last_log_index = get_last_log_index() + if not bypass_logindex: + # Ensure that we only upgrade if the log index is higher than the last known one + incoming_log_index = get_log_index_from_signatures(signatures) + last_log_index = get_last_log_index() - if incoming_log_index < last_log_index: - raise errors.InvalidLogIndex( - "The log index is not higher than the last known one" - ) + if incoming_log_index < last_log_index: + raise errors.InvalidLogIndex( + "The log index is not higher than the last known one" + ) image_digest = index_json["manifests"][0].get("digest").replace("sha256:", "")