Compare commits

..

24 commits

Author SHA1 Message Date
bf9bc16c8c
Merge 2a56c7f35c into 04096380ff 2025-04-14 17:35:41 +00:00
Alexis Métaireau
2a56c7f35c
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-14 18:25:04 +02:00
Alexis Métaireau
a603700ae6
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-14 18:23:33 +02:00
Alexis Métaireau
9599291fa3
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-14 18:11:18 +02:00
Alexis Métaireau
e4b7d400c9
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-14 18:11:16 +02:00
Alexis Métaireau
5a48de46a2
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-14 18:10:57 +02:00
Alexis Métaireau
f668a44cdb
FIXUP commit for signature tests 2025-04-14 18:10:07 +02:00
Alexis Métaireau
6427d7a38b
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-14 18:10:07 +02:00
Alexis Métaireau
4bcd9ee660
FIXUP: Add a comment to update the DEFAULT_LOG_INDEX with releases 2025-04-14 18:10:07 +02:00
Alexis Métaireau
00baf6c87a
FIXUP: throw rather than bools 2025-04-14 18:10:07 +02:00
Alexis Métaireau
0ca9e714f4
FIXUP: Use user data dir rather than config 2025-04-14 18:10:07 +02:00
Alexis Métaireau
7e52fe4459
FIXUP: Use exceptions to ease the flow 2025-04-14 18:10:06 +02:00
Alexis Métaireau
d37630d37b
Introduce a subprocess_run utility function
This is done to avoid forgetting windows specific arguments when calling `subprocess.run`.
2025-04-14 18:10:04 +02:00
Alexis Métaireau
a797a2b6f8
FIXUP: Use the digest when pulling the container 2025-04-14 18:07:00 +02:00
Alexis Métaireau
0907ddc561
Add tests for registry 2025-04-14 18:02:20 +02:00
Alexis Métaireau
87a00d0f38
make the signature tests pass 2025-04-14 18:02:18 +02:00
Alexis Métaireau
26638a5f2a
(WIP) some more tests 2025-04-14 18:01:49 +02:00
Alexis Métaireau
651d988a37
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-14 18:00:48 +02:00
Alexis Métaireau
a19e341378
(WIP) Check for container updates rather than using image-id.txt 2025-04-14 17:49:07 +02:00
Alexis Métaireau
a6a4aa1a3b
Add documentation for independent container updates 2025-04-14 17:46:27 +02:00
Alex Pyrgiotis
e4fccd791f
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-14 17:46:27 +02:00
Alexis Métaireau
ee3689b32a
Add the ability to download diffoci for multiple platforms
The hash list provided on the Github releases page is now bundled in the
`reproduce-image.py` script, and the proper hashes are checked after
download.
2025-04-14 17:45:32 +02:00
Alexis Métaireau
3a6d73dcb8
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-14 17:17:19 +02:00
Alexis Métaireau
ff22c6e160
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-14 16:27:40 +02:00
6 changed files with 78 additions and 10 deletions

View file

@ -20,7 +20,7 @@ jobs:
- name: Download container image for the latest release and load it
run: |
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
CONTAINER_FILENAME=container-${VERSION:1}-${{ matrix.arch }}.tar.gz
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
docker load -i ${CONTAINER_FILENAME}
- name: Get image tag

View file

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

View file

@ -1,3 +1,10 @@
import logging
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(
image: str, manifest_digest: str, pubkey: str, callback: Optional[Callable] = None
image: str, manifest_digest: str, pubkey: str, callback: Callable
) -> str:
"""Verify and upgrade the image to the latest, if signed."""
update_available, remote_digest = registry.is_new_remote_image_available(image)

View file

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

View file

@ -265,6 +265,68 @@ def test_stores_signatures_updates_last_log_index(valid_signature, mocker, tmp_p
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
store_signatures(signatures, image_digest, TEST_PUBKEY_PATH)