Compare commits

..

22 commits

Author SHA1 Message Date
630e6b928e
Merge dce91eaa26 into 83be5fb151 2025-04-22 10:56:10 +00:00
Alexis Métaireau
dce91eaa26
Update the image location to track ghcr.io/freedomofpress 2025-04-22 12:55:49 +02:00
Alexis Métaireau
66b906a8ee
Fix runtime error in repro build
Reference Docker rather than Podman in the error, otherwise it can be misleading.
2025-04-22 12:55:48 +02:00
Alexis Métaireau
06cbb13269
Use a specific error if no signatures files are found 2025-04-22 12:55:48 +02:00
Alexis Métaireau
4c9139201f
Remove duplicated python3 dependency from Dockerfile 2025-04-22 12:55:48 +02:00
Alexis Métaireau
4cedf5bf86
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-22 12:55:47 +02:00
Alexis Métaireau
1079f1335b
Provide a simple function to install the shipped tarball.
It leaves in `dangerzone.updater.install_local_container_tar()`
2025-04-22 12:55:47 +02:00
Alexis Métaireau
a5636b5e74
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-22 12:55:46 +02:00
Alexis Métaireau
acd8717839
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-22 12:55:46 +02:00
Alexis Métaireau
18331d1988
Make the upgrade_container_image() callback argument optional 2025-04-22 12:55:46 +02:00
Alexis Métaireau
c9a6689271
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-22 12:55:45 +02:00
Alexis Métaireau
8d7e965553
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-22 12:55:45 +02:00
Alexis Métaireau
bdceee53d0
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-22 12:55:45 +02:00
Alexis Métaireau
61c8f2a6ad
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-22 12:55:44 +02:00
Alexis Métaireau
d91a09a299
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-22 12:55:44 +02:00
Alexis Métaireau
8d6e5cb8b8
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-22 12:55:44 +02:00
Alexis Métaireau
238ea527e6
Add signatures tests 2025-04-22 12:55:43 +02:00
Alexis Métaireau
6359e488e3
Check for container updates rather than using image-id.txt 2025-04-22 12:55:43 +02:00
Alexis Métaireau
53fbbc6cdf
Add documentation for independent container updates 2025-04-22 12:55:42 +02:00
Alex Pyrgiotis
27a91f9a0e
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-22 12:55:42 +02:00
Alexis Métaireau
a9fec44837
Introduce a subprocess_run utility function
This is done to avoid forgetting windows specific arguments when calling `subprocess.run`.
2025-04-22 12:55:42 +02:00
Alexis Métaireau
a87fd4338b
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-22 12:55:41 +02:00
9 changed files with 43 additions and 9 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 python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
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 python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
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/almet/dangerzone/dangerzone" # FIXME: Change this to the correct container name
CONTAINER_NAME = "ghcr.io/freedomofpress/dangerzone/dangerzone"
log = logging.getLogger(__name__)

View file

@ -181,6 +181,7 @@ def verify_signatures(
for signature in signatures:
verify_signature(signature, image_digest, pubkey)
return True
@ -357,12 +358,21 @@ def load_and_verify_signatures(
pubkey_signatures = signatures_path / get_file_digest(pubkey)
if not pubkey_signatures.exists():
msg = (
f"Cannot find a '{pubkey_signatures}' folder."
f"Cannot find a '{pubkey_signatures}' folder. "
"You might need to download the image signatures first."
)
raise errors.SignaturesFolderDoesNotExist(msg)
with open(pubkey_signatures / f"{image_digest}.json") as f:
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:
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 Podman runtime")
raise RuntimeError("Cannot specify BuildKit arguments using the Docker runtime")
return shlex.split(args.buildkit_args)

View file

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

View file

@ -14,6 +14,13 @@ 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() -> Container:
def provider(skip_image_verification: None) -> Container:
return Container()

View file

@ -209,6 +209,19 @@ 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
):
@ -240,7 +253,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 updated
# Verify that the log index file was not created
assert not (signatures_path / "last_log_index").exists()
@ -370,3 +383,7 @@ 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