Compare commits

..

23 commits

Author SHA1 Message Date
d43bbf68fc
Merge 6c3316089f into 83be5fb151 2025-04-16 13:25:28 +00:00
Alexis Métaireau
6c3316089f
Make the upgrade_container_image() callback argument optional 2025-04-16 15:23:55 +02:00
Alexis Métaireau
c39cd4ea47
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
```
2025-04-16 13:14:39 +02:00
Alexis Métaireau
46b88716d3
Display the {podman,docker} pull progress when installing a new image
The progressbars we see when using this same commands on the
command line doesn't seem to be passed to the python process here,
unfortunately.
2025-04-16 13:14:39 +02:00
Alexis Métaireau
c517c5b3f9
Add a dangerzone-image store-signature CLI command
This can be useful when signatures are missing from the system, for an
already present image, and can be used as a way to fix user issues.
2025-04-16 13:14:39 +02:00
Alexis Métaireau
9aacb0415b
Replace the updater_check setting by updater_check_all
This new setting triggers the same user prompts, but the actual meaning of
it differs, since users will now be accepting to upgrade the container image
rather than just checking for new releases.

Changing the name of the setting will trigger this prompt for all users, effectively
ensuring they want their image to be automatically upgraded.
2025-04-16 13:14:38 +02:00
Alexis Métaireau
62cca90c44
Split updater GUI code from the code checking for release updates
The code making the actual requests and checks now lives in the
`updater.releases` module. The code should be easier to read and to
reason about.

Tests have been updated to reflect this.
2025-04-16 13:14:38 +02:00
Alexis Métaireau
3398684dae
FIXUP commit for signature tests 2025-04-16 13:14:38 +02:00
Alexis Métaireau
0dff082046
Provide an is_update_available function
This function does all the needed checks before returning `True`, making it a good external API.

Under the hood, the registry now has an `is_new_remote_image_available`
which is just for checking the presence of a new image, but doesn't do
any verirications on it, and there is also a new `check_signatures_and_logindex` that ensures that these two are valid.
2025-04-16 13:14:38 +02:00
Alexis Métaireau
bb8ea6c0db
FIXUP: Add a comment to update the DEFAULT_LOG_INDEX with releases 2025-04-16 13:14:38 +02:00
Alexis Métaireau
86f46482e5
FIXUP: throw rather than bools 2025-04-16 13:14:38 +02:00
Alexis Métaireau
9b64d393a5
FIXUP: Use user data dir rather than config 2025-04-16 13:14:38 +02:00
Alexis Métaireau
0f2e67298f
FIXUP: Use exceptions to ease the flow 2025-04-16 13:14:38 +02:00
Alexis Métaireau
f136687b41
Introduce a subprocess_run utility function
This is done to avoid forgetting windows specific arguments when calling `subprocess.run`.
2025-04-16 13:14:38 +02:00
Alexis Métaireau
3b4f8f12be
FIXUP: Use the digest when pulling the container 2025-04-16 13:14:36 +02:00
Alexis Métaireau
affb954103
Add signatures tests 2025-04-16 13:11:39 +02:00
Alexis Métaireau
b6fa4fa35b
parent a19e341378
author Alexis Métaireau <alexis@freedom.press> 1739380992 +0100
committer Alexis Métaireau <alexis@freedom.press> 1744646096 +0200
gpgsig -----BEGIN PGP SIGNATURE-----

 iHUEABYKAB0WIQRFQpTG/4uXFqX2QanGXHqJqP/FbgUCZ/0v0AAKCRDGXHqJqP/F
 bmA8AP9QZjs6bSmxvmzvYvwJa8wYIo6OsdUyQdoZ4kAMd5X7XwEA+WIbuxU/o2bG
 KisPBI+N8LSwwIke3eNS+ne6Vil7qwg=
 =XCYF
 -----END PGP SIGNATURE-----

(WIP) Add tests
2025-04-16 13:11:19 +02:00
Alexis Métaireau
9035620b80
(WIP) Check for container updates rather than using image-id.txt 2025-04-16 13:11:19 +02:00
Alexis Métaireau
c628339250
Add documentation for independent container updates 2025-04-16 13:11:19 +02:00
Alex Pyrgiotis
68f075a315
Publish and attest multi-architecture container images
A new `dangerzone-image attest-provenance` script is now available,
making it possible to verify the attestations of an image published on
the github container registry.

Container images are now build nightly and uploaded to the container
registry.
2025-04-16 13:11:19 +02:00
Alexis Métaireau
92ecad2e81
Download and verify cosign signatures
Signatures are stored in the OCI Manifest v2 registry [0], and are
expected to follow the Cosign Signature Specification [0]

The following CLI utilities are provided with `dangerzone-image`:

For checking new container images, upgrading them and downloading them:

- `upgrade` allows to upgrade the current installed image to the
  last one available on the OCI registry, downloading and storing the
  signatures in the process.
- `verify-local` allows the verify the currently installed image against
  downloaded signatures and public key.

To prepare and install archives on air-gapped environments:

- `prepare-archive` helps to prepare an archive to install on another
  machine
- `load-archive` helps upgrade the local image to the archive given
  in argument.

Signatures are stored locally using the format provided by `cosign
download signature`, and the Rekor log index is used to ensure the
requested-to-install container image is fresher than the one already
present on the system.

[0] https://github.com/sigstore/cosign/blob/main/specs/SIGNATURE_SPEC.md
2025-04-16 13:11:18 +02:00
Alexis Métaireau
e1bdb75435
Add a dangerzone-image CLI script
It contains utilities to interact with OCI registries, like getting the list of
published tags and getting the content of a manifest. It does so
via the use of the Docker Registry API v2 [0].

The script has been added to the `dev_scripts`, and is also installed on
the system under `dangerzone-image`.

[0]  https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry
2025-04-16 13:11:18 +02:00
Alexis Métaireau
83be5fb151
Release container is now using the .tar format
Some checks failed
Tests / run tests (fedora 41) (push) Has been cancelled
Tests / run tests (fedora 42) (push) Has been cancelled
Tests / run tests (ubuntu 22.04) (push) Has been cancelled
Tests / run tests (ubuntu 24.04) (push) Has been cancelled
Tests / run tests (ubuntu 24.10) (push) Has been cancelled
Tests / windows (push) Has been cancelled
Tests / macOS (arch64) (push) Has been cancelled
Tests / macOS (x86_64) (push) Has been cancelled
Tests / build-deb (debian bookworm) (push) Has been cancelled
Tests / build-deb (debian bullseye) (push) Has been cancelled
Tests / build-deb (debian trixie) (push) Has been cancelled
Tests / build-deb (ubuntu 22.04) (push) Has been cancelled
Tests / build-deb (ubuntu 24.04) (push) Has been cancelled
Tests / build-deb (ubuntu 24.10) (push) Has been cancelled
Tests / build-deb (ubuntu 25.04) (push) Has been cancelled
Tests / install-deb (debian bookworm) (push) Has been cancelled
Tests / install-deb (debian bullseye) (push) Has been cancelled
Tests / install-deb (debian trixie) (push) Has been cancelled
Tests / install-deb (ubuntu 22.04) (push) Has been cancelled
Tests / install-deb (ubuntu 24.04) (push) Has been cancelled
Tests / install-deb (ubuntu 24.10) (push) Has been cancelled
Tests / install-deb (ubuntu 25.04) (push) Has been cancelled
Tests / build-install-rpm (fedora 40) (push) Has been cancelled
Tests / build-install-rpm (fedora 41) (push) Has been cancelled
Tests / build-install-rpm (fedora 42) (push) Has been cancelled
Tests / run tests (debian bookworm) (push) Has been cancelled
Tests / run tests (debian bullseye) (push) Has been cancelled
Tests / run tests (debian trixie) (push) Has been cancelled
Tests / run tests (fedora 40) (push) Has been cancelled
Tests / run tests (ubuntu 25.04) (push) Has been cancelled
Update the CI check to account for it.
2025-04-14 15:08:32 +02:00
6 changed files with 10 additions and 78 deletions

View file

@ -20,7 +20,7 @@ jobs:
- name: Download container image for the latest release and load it - name: Download container image for the latest release and load it
run: | run: |
VERSION=$(curl https://api.github.com/repos/freedomofpress/dangerzone/releases/latest | grep "tag_name" | cut -d '"' -f 4) VERSION=$(curl https://api.github.com/repos/freedomofpress/dangerzone/releases/latest | grep "tag_name" | cut -d '"' -f 4)
CONTAINER_FILENAME=container-${VERSION:1}-${{ matrix.arch }}.tar.gz CONTAINER_FILENAME=container-${VERSION:1}-${{ matrix.arch }}.tar
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME} wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
docker load -i ${CONTAINER_FILENAME} docker load -i ${CONTAINER_FILENAME}
- name: Get image tag - name: Get image tag

View file

@ -11,9 +11,7 @@ from .settings import Settings
from .util import get_resource_path, get_subprocess_startupinfo from .util import get_resource_path, get_subprocess_startupinfo
OLD_CONTAINER_NAME = "dangerzone.rocks/dangerzone" OLD_CONTAINER_NAME = "dangerzone.rocks/dangerzone"
CONTAINER_NAME = ( CONTAINER_NAME = "ghcr.io/almet/dangerzone/dangerzone" # FIXME: Change this to the correct container name
"ghcr.io/almet/dangerzone/dangerzone"
) # FIXME: Change this to the correct container name
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -230,7 +228,9 @@ def get_image_id_by_digest(digest: str) -> str:
return process.stdout.decode().strip().split("\n")[0] return process.stdout.decode().strip().split("\n")[0]
def container_pull(image: str, manifest_digest: str, callback: Callable): def container_pull(
image: str, manifest_digest: str, callback: Optional[Callable] = None
):
"""Pull a container image from a registry.""" """Pull a container image from a registry."""
runtime = Runtime() runtime = Runtime()
cmd = [str(runtime.path), "pull", f"{image}@sha256:{manifest_digest}"] cmd = [str(runtime.path), "pull", f"{image}@sha256:{manifest_digest}"]
@ -242,6 +242,7 @@ def container_pull(image: str, manifest_digest: str, callback: Callable):
bufsize=1, bufsize=1,
) )
if callback:
for line in process.stdout: # type: ignore for line in process.stdout: # type: ignore
callback(line) callback(line)

View file

@ -1,10 +1,3 @@
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
from .signatures import (
DEFAULT_PUBKEY_LOCATION,
is_update_available,
upgrade_container_image,
verify_local_image,
)

View file

@ -485,7 +485,7 @@ def prepare_airgapped_archive(image_name: str, destination: str) -> None:
def upgrade_container_image( def upgrade_container_image(
image: str, manifest_digest: str, pubkey: str, callback: Callable image: str, manifest_digest: str, pubkey: str, callback: Optional[Callable] = None
) -> str: ) -> str:
"""Verify and upgrade the image to the latest, if signed.""" """Verify and upgrade the image to the latest, if signed."""
update_available, remote_digest = registry.is_new_remote_image_available(image) update_available, remote_digest = registry.is_new_remote_image_available(image)

View file

@ -8,7 +8,7 @@ from pathlib import Path
try: try:
import platformdirs import platformdirs
except ImportError: except ImportError:
import appdirs as platformdirs # type: ignore[no-redef] import appdirs as platformdirs
def get_config_dir() -> Path: def get_config_dir() -> Path:

View file

@ -265,68 +265,6 @@ def test_stores_signatures_updates_last_log_index(valid_signature, mocker, tmp_p
return_value=100, return_value=100,
) )
# Call store_signatures
with pytest.raises(errors.SignatureMismatch):
store_signatures(signatures, image_digest, TEST_PUBKEY_PATH)
("dangerzone.updater.signatures.get_last_log_index",)
# Verify that the signatures file was not created
assert not (signatures_path / f"{image_digest}.json").exists()
# Verify that the log index file was not updated
assert not (signatures_path / "last_log_index").exists()
def test_stores_signatures_updates_last_log_index(valid_signature, mocker, tmp_path):
"""Test that store_signatures updates the last log index file."""
signatures = [valid_signature]
# Extract the digest from the signature
image_digest = Signature(valid_signature).manifest_digest
signatures = [valid_signature, signature_other_digest]
breakpoint()
valid_signature, signature_other_digest, mocker, tmp_path
"""Test that store_signatures raises an error when a signature's digest doesn't match."""
image_digest = "sha256:123456"
# Mock the signatures path
signatures_path = tmp_path / "signatures"
signatures_path.mkdir()
mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", signatures_path)
# Mock get_log_index_from_signatures
mocker.patch(
"dangerzone.updater.signatures.get_log_index_from_signatures",
return_value=100,
)
# Mock get_last_log_index
mocker.patch(
"dangerzone.updater.signatures.get_last_log_index",
return_value=50,
)
def test_stores_signatures_updates_last_log_index():
pass
def test_get_file_digest():
# Mock the signatures path
signatures_path = tmp_path / "signatures"
signatures_path.mkdir()
mocker.patch("dangerzone.updater.signatures.SIGNATURES_PATH", signatures_path)
# Create an existing last_log_index file with a lower value
with open(signatures_path / "last_log_index", "w") as f:
f.write("50")
# Mock get_log_index_from_signatures to return a higher value
mocker.patch(
"dangerzone.updater.signatures.get_log_index_from_signatures",
return_value=100,
)
# Call store_signatures # Call store_signatures
store_signatures(signatures, image_digest, TEST_PUBKEY_PATH) store_signatures(signatures, image_digest, TEST_PUBKEY_PATH)