Skip to content

Commit

Permalink
fix(icu): update documentation and fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
almet committed Feb 4, 2025
1 parent 97d7b52 commit aedfc3b
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 25 deletions.
12 changes: 8 additions & 4 deletions dangerzone/container_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import platform
import shutil
import subprocess
from typing import List, Tuple
from typing import List, Optional, Tuple

from . import errors
from .util import get_resource_path, get_subprocess_startupinfo
Expand Down Expand Up @@ -192,10 +192,14 @@ def container_pull(image: str) -> bool:
return process.returncode == 0


def get_local_image_hash(image: str) -> str:
def get_local_image_hash(image: str) -> Optional[str]:
"""
Returns a image hash from a local image name
"""
cmd = [get_runtime_name(), "image", "inspect", image, "-f", "{{.Digest}}"]
result = subprocess.run(cmd, capture_output=True, check=True)
return result.stdout.strip().decode().strip("sha256:")
try:
result = subprocess.run(cmd, capture_output=True, check=True)
except subprocess.CalledProcessError as e:
return None
else:
return result.stdout.strip().decode().strip("sha256:")
22 changes: 14 additions & 8 deletions dangerzone/updater/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from . import attestations, errors, log, registry, signatures

DEFAULT_REPOSITORY = "freedomofpress/dangerzone"
DEFAULT_IMAGE_NAME = "ghcr.io/freedomofpress/dangerzone"
DEFAULT_IMAGE_NAME = "ghcr.io/freedomofpress/dangerzone/dangerzone"
PUBKEY_DEFAULT_LOCATION = get_resource_path("freedomofpress-dangerzone-pub.key")


Expand All @@ -24,14 +24,18 @@ def main(debug: bool) -> None:


@main.command()
@click.argument("image")
@click.argument("image", default=DEFAULT_IMAGE_NAME)
@click.option("--pubkey", default=PUBKEY_DEFAULT_LOCATION)
def upgrade(image: str, pubkey: str) -> None:
"""Upgrade the image to the latest signed version."""
manifest_hash = registry.get_manifest_hash(image)
try:
is_upgraded = signatures.upgrade_container_image(image, manifest_hash, pubkey)
click.echo(f"✅ The local image {image} has been upgraded")
if is_upgraded:
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")

except errors.ImageAlreadyUpToDate as e:
click.echo(f"✅ {e}")
raise click.Abort()
Expand All @@ -56,15 +60,15 @@ def load_archive(image_filename: str, pubkey: str) -> None:

@main.command()
@click.argument("image")
@click.option("--destination", default="dangerzone-airgapped.tar")
def prepare_archive(image: str, destination: str) -> None:
@click.option("--output", default="dangerzone-airgapped.tar")
def prepare_archive(image: str, output: str) -> None:
"""Prepare an archive to upgrade the dangerzone image on an airgapped environment."""
signatures.prepare_airgapped_archive(image, destination)
click.echo(f"✅ Archive {destination} created")
signatures.prepare_airgapped_archive(image, output)
click.echo(f"✅ Archive {output} created")


@main.command()
@click.argument("image")
@click.argument("image", default=DEFAULT_IMAGE_NAME)
@click.option("--pubkey", default=PUBKEY_DEFAULT_LOCATION)
def verify_local(image: str, pubkey: str) -> None:
"""
Expand All @@ -85,6 +89,7 @@ def verify_local(image: str, pubkey: str) -> None:
@main.command()
@click.argument("image")
def list_remote_tags(image: str) -> None:
"""List the tags available for a given image."""
click.echo(f"Existing tags for {image}")
for tag in registry.list_tags(image):
click.echo(tag)
Expand All @@ -93,6 +98,7 @@ def list_remote_tags(image: str) -> None:
@main.command()
@click.argument("image")
def get_manifest(image: str) -> None:
"""Retrieves a remove manifest for a given image and displays it."""
click.echo(registry.get_manifest(image))


Expand Down
4 changes: 4 additions & 0 deletions dangerzone/updater/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class ImageAlreadyUpToDate(UpdaterError):
pass


class ImageNotFound(UpdaterError):
pass


class SignatureError(UpdaterError):
pass

Expand Down
17 changes: 13 additions & 4 deletions dangerzone/updater/signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ def verify_signature(signature: dict, image_hash: str, pubkey: str) -> bool:
signature_bundle = signature_to_bundle(signature)

payload_bytes = b64decode(signature_bundle["Payload"])
if json.loads(payload_bytes)["critical"]["type"] != f"sha256:{image_hash}":
raise errors.SignatureMismatch("The signature does not match the image hash")
payload_hash = json.loads(payload_bytes)["critical"]["image"][
"docker-manifest-digest"
]
if payload_hash != f"sha256:{image_hash}":
raise errors.SignatureMismatch(
f"The signature does not match the image hash ({payload_hash}, {image_hash})"
)

with (
NamedTemporaryFile(mode="w") as signature_file,
Expand Down Expand Up @@ -220,7 +225,7 @@ def _to_cosign_signature(layer: Dict) -> Dict:
"Payload": payload_b64,
"Cert": None,
"Chain": None,
"rekorBundle": bundle,
"Bundle": bundle,
"RFC3161Timestamp": None,
}

Expand Down Expand Up @@ -311,7 +316,11 @@ def verify_local_image(image: str, pubkey: str) -> bool:
Verifies that a local image has a valid signature
"""
log.info(f"Verifying local image {image} against pubkey {pubkey}")
image_hash = runtime.get_local_image_hash(image)
try:
image_hash = runtime.get_local_image_hash(image)
except subprocess.CalledProcessError:
raise errors.ImageNotFound(f"The image {image} does not exist locally")

log.debug(f"Image hash: {image_hash}")
signatures = load_signatures(image_hash, pubkey)
if len(signatures) < 1:
Expand Down
35 changes: 26 additions & 9 deletions docs/developer/independent-container-updates.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Independent Container Updates

Since version 0.9.0, Dangerzone is able to ship container images independently
from issuing a new release of the software.
from releases.

This is useful as images need to be kept updated with the latest security fixes.
One of the main benefits of doing so is to lower the time needed to patch security issues inside the containers.

## Nightly images and attestations
## Checking attestations

Each night, new images are built and pushed to our container registry, alongside
Each night, new images are built and pushed to the container registry, alongside
with a provenance attestation, enabling anybody to ensure that the image has
been originally built by Github CI runners, from a defined source repository (in our case `freedomofpress/dangerzone`).

To verify the attestations against our expectations, use the following command:
```bash
poetry run ./dev_scripts/registry.py attest ghcr.io/freedomofpress/dangerzone/dangerzone:latest --repo freedomofpress/dangerzone
dangerzone-image attest-provenance ghcr.io/freedomofpress/dangerzone/dangerzone --repository freedomofpress/dangerzone
```

In case of sucess, it will report back:
Expand All @@ -22,18 +22,35 @@ In case of sucess, it will report back:
🎉 The image available at `ghcr.io/freedomofpress/dangerzone/dangerzone:latest` has been built by Github runners from the `freedomofpress/dangerzone` repository.
```

## Container updates on air-gapped environments
## Install updates

To check if a new container image has been released, and update your local installation with it, you can use the following commands:

```bash
./dev_scripts/dangerzone-image --debug upgrade ghcr.io/almet/dangerzone/dangerzone
```

## Verify local

You can verify that the image you have locally matches the stored signatures, and that these have been signed with a trusted public key:

```bash
dangerzone-image verify-local ghcr.io/almet/dangerzone/dangerzone
```

## Air-gapped environments

In order to make updates on an air-gapped environment, you will need to prepare an archive for the air-gapped environment. This archive will contain all the needed material to validate that the new container image has been signed and is valid.

On the machine on which you prepare the packages:

```bash
dangerzone-image prepare-archive ghcr.io/almet/dangerzone/dangerzone@sha256:fa948726aac29a6ac49f01ec8fbbac18522b35b2491fdf716236a0b3502a2ca7
dangerzone-image prepare-archive --output dz-fa94872.tar ghcr.io/almet/dangerzone/dangerzone@sha256:fa948726aac29a6ac49f01ec8fbbac18522b35b2491fdf716236a0b3502a2ca7
```

On the airgapped machine, copy the file and run the following command:

```bash
dangerzone-image load-archive
```
dangerzone-image load-archive dz-fa94872.tar
```

0 comments on commit aedfc3b

Please sign in to comment.