Compare commits

..

25 commits

Author SHA1 Message Date
77dc106eba
Merge 545e2156b5 into 83be5fb151 2025-04-17 15:27:19 +00:00
Alexis Métaireau
545e2156b5
Skip container signature verification during the tests
This is not required, and skipping them allows to make the whole
test-suite run faster.
2025-04-17 17:26:11 +02:00
Alexis Métaireau
77be24858c
Provide a simple function to install the shipped tarball.
It leaves in `dangerzone.updater.install_local_container_tar()`
2025-04-17 17:23:31 +02:00
Alexis Métaireau
b61a65db01
dangerzone.updater exposes a few funtions, constants and exceptions
This is done to avoid looking at the internal logic of
`dangerzone.updater`. Only the features that actually are part of
the exposed API are exposed, and do not require deep knowledge of the
updater's logic to be used.
2025-04-17 17:19:04 +02:00
Alexis Métaireau
11adba8ab7
Update container installation logic to allow in-place updates
The isolation provider `install()` method is now passed a
`should_upgrade` argument, which is read from the settings and
represents the user decision about updates.

The tests have been updated to reflect these changes.
2025-04-17 17:16:10 +02: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
9 changed files with 9 additions and 43 deletions

View file

@ -35,7 +35,7 @@ RUN \
apt-get update && \
apt-get install -y --no-install-recommends \
python3 python3-fitz libreoffice-nogui libreoffice-java-common \
python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
python3 python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
runsc unzip wget && \
: "Clean up for improving reproducibility (optional)" && \
rm -rf /var/cache/fontconfig/ && \

View file

@ -35,7 +35,7 @@ RUN \
apt-get update && \
apt-get install -y --no-install-recommends \
python3 python3-fitz libreoffice-nogui libreoffice-java-common \
python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
python3 python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
runsc unzip wget && \
: "Clean up for improving reproducibility (optional)" && \
rm -rf /var/cache/fontconfig/ && \

View file

@ -11,7 +11,7 @@ from .settings import Settings
from .util import get_resource_path, get_subprocess_startupinfo
OLD_CONTAINER_NAME = "dangerzone.rocks/dangerzone"
CONTAINER_NAME = "ghcr.io/freedomofpress/dangerzone/dangerzone"
CONTAINER_NAME = "ghcr.io/almet/dangerzone/dangerzone" # FIXME: Change this to the correct container name
log = logging.getLogger(__name__)

View file

@ -181,7 +181,6 @@ def verify_signatures(
for signature in signatures:
verify_signature(signature, image_digest, pubkey)
return True
@ -363,16 +362,7 @@ def load_and_verify_signatures(
)
raise errors.SignaturesFolderDoesNotExist(msg)
signatures_file = pubkey_signatures / f"{image_digest}.json"
if not signatures_file.exists():
msg = (
f"Cannot find a '{signatures_file}' file. "
"You might need to download the image signatures first."
)
raise errors.LocalSignatureNotFound(msg)
with open(signatures_file) as f:
with open(pubkey_signatures / f"{image_digest}.json") as f:
log.debug("Loading signatures from %s", f.name)
signatures = json.load(f)

View file

@ -156,7 +156,7 @@ def parse_buildkit_args(args, runtime: str) -> str:
return []
if runtime != "podman":
raise RuntimeError("Cannot specify BuildKit arguments using the Docker runtime")
raise RuntimeError("Cannot specify BuildKit arguments using the Podman runtime")
return shlex.split(args.buildkit_args)

View file

@ -6,7 +6,7 @@ import sys
from pathlib import Path
BUILD_CONTEXT = "dangerzone"
IMAGE_NAME = "ghcr.io/freedomofpress/dangerzone/dangerzone"
IMAGE_NAME = "dangerzone.rocks/dangerzone"
if platform.system() in ["Darwin", "Windows"]:
CONTAINER_RUNTIME = "docker"
elif platform.system() == "Linux":

View file

@ -14,13 +14,6 @@ from dangerzone.isolation_provider import container
sys.dangerzone_dev = True # type: ignore[attr-defined]
ASSETS_PATH = Path(__file__).parent / "assets"
TEST_PUBKEY_PATH = ASSETS_PATH / "test.pub.key"
INVALID_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "invalid"
VALID_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "valid"
TEMPERED_SIGNATURES_PATH = ASSETS_PATH / "signatures" / "tempered"
# Use this fixture to make `pytest-qt` invoke our custom QApplication.
# See https://pytest-qt.readthedocs.io/en/latest/qapplication.html#testing-custom-qapplications
@pytest.fixture(scope="session")

View file

@ -22,7 +22,7 @@ elif os.environ.get("DUMMY_CONVERSION", False):
@pytest.fixture
def provider(skip_image_verification: None) -> Container:
def provider() -> Container:
return Container()

View file

@ -209,19 +209,6 @@ def test_get_remote_signatures_cosign_error(mocker, fp: FakeProcess):
get_remote_signatures(image, digest)
def test_get_remote_signatures_cosign_error(mocker, fp: FakeProcess):
image = "ghcr.io/freedomofpress/dangerzone/dangerzone"
digest = "123456"
mocker.patch("dangerzone.updater.cosign.ensure_installed", return_value=True)
fp.register_subprocess(
["cosign", "download", "signature", f"{image}@sha256:{digest}"],
returncode=1,
stderr="Error: no signatures associated",
)
with pytest.raises(errors.NoRemoteSignatures):
get_remote_signatures(image, digest)
def test_store_signatures_with_different_digests(
valid_signature, signature_other_digest, mocker, tmp_path
):
@ -253,7 +240,7 @@ def test_store_signatures_with_different_digests(
# 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 created
# Verify that the log index file was not updated
assert not (signatures_path / "last_log_index").exists()
@ -383,7 +370,3 @@ def test_verify_signature_tempered(tempered_signature):
def test_verify_signatures_empty_list():
with pytest.raises(errors.SignatureVerificationError):
verify_signatures([], "1234", TEST_PUBKEY_PATH)
def test_verify_signatures_not_0():
pass