Compare commits

..

35 commits

Author SHA1 Message Date
Alexis Métaireau
69b332c112
Fix cli.py
Some checks failed
Release multi-arch container image / provenance (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 20.04) (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 / 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 20.04) (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 / build-install-rpm (fedora 40) (push) Has been cancelled
Tests / build-install-rpm (fedora 41) (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 (fedora 41) (push) Has been cancelled
Tests / run tests (ubuntu 20.04) (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
Release multi-arch container image / merge (push) Has been cancelled
2025-02-06 17:11:23 +01:00
Alexis Métaireau
c96c0d6eed
Add the ability to download diffoci for multiple platforms
Some checks are pending
Tests / run-lint (push) Waiting to run
Tests / run tests (ubuntu 24.10) (push) Blocked by required conditions
Tests / check-reproducibility (push) Waiting to run
Tests / build-container-image (push) Waiting to run
Tests / Download and cache Tesseract data (push) Waiting to run
Tests / macOS (arch64) (push) Blocked by required conditions
Tests / macOS (x86_64) (push) Blocked by required conditions
Tests / build-deb (debian bookworm) (push) Blocked by required conditions
Tests / build-deb (debian bullseye) (push) Blocked by required conditions
Tests / build-deb (debian trixie) (push) Blocked by required conditions
Tests / build-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / install-deb (debian bookworm) (push) Blocked by required conditions
Tests / install-deb (debian bullseye) (push) Blocked by required conditions
Tests / install-deb (debian trixie) (push) Blocked by required conditions
Tests / install-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 40) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 41) (push) Blocked by required conditions
Tests / run tests (debian bookworm) (push) Blocked by required conditions
Tests / run tests (debian bullseye) (push) Blocked by required conditions
Tests / run tests (debian trixie) (push) Blocked by required conditions
Release multi-arch container image / build (linux/amd64) (push) Waiting to run
Release multi-arch container image / build (linux/arm64) (push) Waiting to run
Release multi-arch container image / merge (push) Blocked by required conditions
Release multi-arch container image / provenance (push) Blocked by required conditions
2025-02-05 18:06:53 +01:00
Alexis Métaireau
69f4d296ec
Build images every day, on main and test/ commits 2025-02-05 17:07:39 +01:00
Alexis Métaireau
c2d37dfb04
Check signatures before invoking the container.
Also, check for new container images when starting the application.
This replaces the usage of `share/image-id.txt` to ensure the image is trusted.
2025-02-05 16:54:13 +01:00
Alexis Métaireau
60c144aab0
Fixup: remove rntime.py 2025-02-05 16:51:51 +01:00
Alexis Métaireau
ad3d0e4182
Fixup: update docs 2025-02-05 15:40:36 +01:00
Alexis Métaireau
af6b4e0d73
Fixup: use digest instead of hash 2025-02-05 15:40:21 +01:00
Alexis Métaireau
4542f0b4c4
CI: Rename github workflow for multi-arch images publication 2025-02-05 15:03:16 +01:00
Alexis Métaireau
10957dfe02
Fixup: registry, split Accept lines 2025-02-05 14:31:36 +01:00
Alexis Métaireau
94e51840e7
feat(icu): Add verification support for multi-arch images 2025-02-05 14:27:44 +01:00
Alexis Métaireau
e67fbc1e72
fixup: Fix docs 2025-02-04 18:53:24 +01:00
Alex Pyrgiotis
2981ec4450
WIP: Add CI job for multi-arch builds
Some checks failed
Tests / build-container-image (push) Waiting to run
Tests / Download and cache Tesseract data (push) Waiting to run
Tests / windows (push) Blocked by required conditions
Tests / macOS (arch64) (push) Blocked by required conditions
Tests / macOS (x86_64) (push) Blocked by required conditions
Tests / build-deb (debian bookworm) (push) Blocked by required conditions
Tests / build-deb (debian bullseye) (push) Blocked by required conditions
Tests / build-deb (debian trixie) (push) Blocked by required conditions
Tests / build-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / install-deb (debian bookworm) (push) Blocked by required conditions
Tests / install-deb (debian bullseye) (push) Blocked by required conditions
Tests / install-deb (debian trixie) (push) Blocked by required conditions
Tests / install-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 40) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 41) (push) Blocked by required conditions
Tests / run tests (debian bookworm) (push) Blocked by required conditions
Tests / run tests (debian bullseye) (push) Blocked by required conditions
Tests / run tests (debian trixie) (push) Blocked by required conditions
Tests / check-reproducibility (push) Waiting to run
Release container image / build-container-image (push) Waiting to run
Multi-arch build / build (linux/amd64) (push) Has been cancelled
Multi-arch build / build (linux/arm64) (push) Has been cancelled
Multi-arch build / merge (push) Has been cancelled
Multi-arch build / provenance (push) Has been cancelled
2025-02-04 19:44:29 +02:00
Alex Pyrgiotis
22102f29e6
WIP: Verify local image 2025-02-04 19:42:42 +02:00
Alex Pyrgiotis
a77fc938fd
WIP: Make verify-attestation work for SLSA 3 attestations 2025-02-04 19:42:31 +02:00
Alexis Métaireau
aedfc3b9a2
fix(icu): update documentation and fixes
Some checks are pending
Tests / windows (push) Blocked by required conditions
Tests / macOS (arch64) (push) Blocked by required conditions
Tests / macOS (x86_64) (push) Blocked by required conditions
Tests / build-deb (debian bookworm) (push) Blocked by required conditions
Tests / build-deb (debian bullseye) (push) Blocked by required conditions
Tests / build-deb (debian trixie) (push) Blocked by required conditions
Tests / build-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / install-deb (debian bookworm) (push) Blocked by required conditions
Tests / install-deb (debian bullseye) (push) Blocked by required conditions
Tests / install-deb (debian trixie) (push) Blocked by required conditions
Tests / install-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 40) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 41) (push) Blocked by required conditions
Tests / run tests (debian bookworm) (push) Blocked by required conditions
Tests / run tests (debian bullseye) (push) Blocked by required conditions
Tests / run tests (debian trixie) (push) Blocked by required conditions
Tests / run tests (fedora 40) (push) Blocked by required conditions
Tests / run tests (fedora 41) (push) Blocked by required conditions
Tests / run tests (ubuntu 20.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 22.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 24.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 24.10) (push) Blocked by required conditions
Tests / check-reproducibility (push) Waiting to run
Release container image / build-container-image (push) Waiting to run
2025-02-04 16:18:18 +01:00
Alexis Métaireau
97d7b52093
Get image name from signatures for air-gapped archives
This allows to be sure that the image name is verified by a known public
key, rather than relying on an input by the user, which can lead to issues.
2025-02-04 15:32:08 +01:00
Alexis Métaireau
9c2d7a7f7b
Add a dangerzone-image prepare-archive command 2025-02-04 12:38:26 +01:00
Alexis Métaireau
8ae4af8698
Locally store the signatures for oci-images archives
On air-gapped environements, it's now possible to load signatures
generated by `cosign save` commands. The signatures embedded in this
format will be converted to the one used by `cosign download signature`.
2025-02-04 11:49:51 +01:00
Alexis Métaireau
087e5bd1ad
Allow installation on air-gapped systems
- Verify the archive against the known public signature
- Prepare a new archive format (with signature removed)
- Load the new image and retag it with the expected tag

During this process, the signatures are lost and should instead be
converted to a known format. Additionally, the name fo the repository
should ideally come from the signatures rather than from the command
line.
2025-02-03 18:04:24 +01:00
Alexis Métaireau
f7069a9c16
Ensure cosign is installed before trying to use it
Some checks failed
Tests / check-reproducibility (push) Has been cancelled
Release container image / build-container-image (push) Has been cancelled
Tests / windows (push) Has been cancelled
Tests / macOS (arch64) (push) Has been cancelled
Tests / build-deb (ubuntu 22.04) (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 20.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 / 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 20.04) (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 / build-install-rpm (fedora 40) (push) Has been cancelled
Tests / build-install-rpm (fedora 41) (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 (fedora 41) (push) Has been cancelled
Tests / run tests (ubuntu 20.04) (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
2025-01-29 19:31:54 +01:00
Alexis Métaireau
7bbd260c72
Add a dev_scripts/dangerzone-image 2025-01-29 19:31:30 +01:00
Alexis Métaireau
7991a5cb9c
Some more refactoring 2025-01-29 19:14:40 +01:00
Alexis Métaireau
fd1db717b7
Refactoring of dangerzone/updater/* 2025-01-29 17:01:48 +01:00
Alexis Métaireau
d0ab34b422
Move regsitry and cosign utilities to dangerzone/updater/*.
Placing these inside the `dangerzone` python package enables an
inclusion with the software itself, and also makes it possible for
end-users to attest the image.
2025-01-29 15:08:50 +01:00
Alexis Métaireau
cbd4795bf6
Verify podman/docker images against locally stored signatures
Some checks are pending
Tests / run tests (ubuntu 22.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 24.04) (push) Blocked by required conditions
Tests / run tests (ubuntu 24.10) (push) Blocked by required conditions
Tests / run-lint (push) Waiting to run
Tests / build-container-image (push) Waiting to run
Tests / Download and cache Tesseract data (push) Waiting to run
Tests / windows (push) Blocked by required conditions
Tests / macOS (arch64) (push) Blocked by required conditions
Tests / macOS (x86_64) (push) Blocked by required conditions
Tests / build-deb (debian bookworm) (push) Blocked by required conditions
Tests / build-deb (debian bullseye) (push) Blocked by required conditions
Tests / build-deb (debian trixie) (push) Blocked by required conditions
Tests / build-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / build-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / install-deb (debian bookworm) (push) Blocked by required conditions
Tests / install-deb (debian bullseye) (push) Blocked by required conditions
Tests / install-deb (debian trixie) (push) Blocked by required conditions
Tests / install-deb (ubuntu 20.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 22.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.04) (push) Blocked by required conditions
Tests / install-deb (ubuntu 24.10) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 40) (push) Blocked by required conditions
Tests / build-install-rpm (fedora 41) (push) Blocked by required conditions
Tests / run tests (debian bookworm) (push) Blocked by required conditions
Tests / run tests (debian bullseye) (push) Blocked by required conditions
Tests / run tests (debian trixie) (push) Blocked by required conditions
Tests / check-reproducibility (push) Waiting to run
Release container image / build-container-image (push) Waiting to run
2025-01-28 16:21:29 +01:00
Alexis Métaireau
47252cc31d
Automate the verification of image signatures 2025-01-28 16:21:29 +01:00
Alexis Métaireau
bcd1ec2173
Add an utility to retrieve manifest info 2025-01-28 16:21:29 +01:00
Alexis Métaireau
5817650633
Add a script to verify Github attestations 2025-01-28 16:21:29 +01:00
Alexis Métaireau
8f49cd99eb
FIXUP: test 2025-01-28 16:21:29 +01:00
Alexis Métaireau
f0ac1f885f
Add logs 2025-01-28 16:21:29 +01:00
Alexis Métaireau
554736cab3
Remove the tag from the attestation, what we attest is the hash, so no need for it 2025-01-28 16:21:29 +01:00
Alexis Métaireau
891ffe4fec
Add the tag to the subject 2025-01-28 16:21:29 +01:00
Alexis Métaireau
2a80bf0c26
Get the tag from git before retagging it 2025-01-28 16:21:29 +01:00
Alexis Métaireau
ac62a153dc
Checkout with depth:0 otherwise git commands aren't functional 2025-01-28 16:21:29 +01:00
Alexis Métaireau
13449641ca
Build: Use Github runners to build and sign container images on new tags 2025-01-28 16:21:28 +01:00
8 changed files with 136 additions and 160 deletions

View file

@ -37,12 +37,3 @@ ignore:
# [bookworm] - raptor2 <postponed> (Minor issue, revisit when fixed upstream)
#
- vulnerability: CVE-2024-57823
# CVE-2025-0665
# ==============
#
# Debian tracker: https://security-tracker.debian.org/tracker/CVE-2025-0665
# Verdict: Dangerzone is not affected because the vulnerable code is not
# present in Debian Bookworm. Also, libcurl is an HTTP client, and the
# Dangerzone container does not make any network calls.
- vulnerability: CVE-2025-0665

View file

@ -9,7 +9,7 @@ from . import errors
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"
log = logging.getLogger(__name__)
@ -111,7 +111,7 @@ def delete_image_tag(tag: str) -> None:
)
def load_image_tarball_from_gzip() -> None:
def load_image_tarball_in_memory() -> None:
log.info("Installing Dangerzone container image...")
p = subprocess.Popen(
[get_runtime(), "load"],
@ -142,7 +142,7 @@ def load_image_tarball_from_gzip() -> None:
log.info("Successfully installed container image from")
def load_image_tarball_from_tar(tarball_path: str) -> None:
def load_image_tarball_file(tarball_path: str) -> None:
cmd = [get_runtime(), "load", "-i", tarball_path]
subprocess.run(cmd, startupinfo=get_subprocess_startupinfo(), check=True)

View file

@ -3,6 +3,7 @@ from tempfile import NamedTemporaryFile
from . import cosign
# NOTE: You can grab the SLSA attestation for an image/tag pair with the following
# commands:
#
@ -50,11 +51,7 @@ def generate_cue_policy(repo, workflow, commit, branch):
def verify(
image_name: str,
branch: str,
commit: str,
repository: str,
workflow: str,
image_name: str, branch: str, commit: str, repository: str, workflow: str,
) -> bool:
"""
Look up the image attestation to see if the image has been built

View file

@ -97,8 +97,8 @@ def list_remote_tags(image: str) -> None:
@main.command()
@click.argument("image")
def get_manifest(image: str) -> None:
"""Retrieves a remote manifest for a given image and displays it."""
click.echo(registry.get_manifest(image).content)
"""Retrieves a remove manifest for a given image and displays it."""
click.echo(registry.get_manifest(image))
@main.command()
@ -121,7 +121,7 @@ def get_manifest(image: str) -> None:
)
@click.option(
"--workflow",
default=".github/workflows/release-container-image.yml",
default=".github/workflows/multi_arch_build.yml",
help="The path of the GitHub actions workflow this image was created from",
)
def attest_provenance(

View file

@ -52,7 +52,3 @@ class LocalSignatureNotFound(SignatureError):
class CosignNotInstalledError(SignatureError):
pass
class InvalidLogIndex(SignatureError):
pass

View file

@ -28,7 +28,13 @@ ACCEPT_MANIFESTS_HEADER = ",".join(
)
Image = namedtuple("Image", ["registry", "namespace", "image_name", "tag"])
class Image(namedtuple("Image", ["registry", "namespace", "image_name", "tag"])):
__slots__ = ()
@property
def full_name(self) -> str:
tag = f":{self.tag}" if self.tag else ""
return f"{self.registry}/{self.namespace}/{self.image_name}{tag}"
def parse_image_location(input_string: str) -> Image:
@ -52,67 +58,102 @@ def parse_image_location(input_string: str) -> Image:
)
def _get_auth_header(image) -> Dict[str, str]:
auth_url = f"https://{image.registry}/token"
response = requests.get(
auth_url,
params={
"service": f"{image.registry}",
"scope": f"repository:{image.namespace}/{image.image_name}:pull",
},
)
response.raise_for_status()
token = response.json()["token"]
return {"Authorization": f"Bearer {token}"}
class RegistryClient:
def __init__(
self,
image: Image | str,
):
if isinstance(image, str):
image = parse_image_location(image)
self._image = image
self._registry = image.registry
self._namespace = image.namespace
self._image_name = image.image_name
self._auth_token = None
self._base_url = f"https://{self._registry}"
self._image_url = f"{self._base_url}/v2/{self._namespace}/{self._image_name}"
def get_auth_token(self) -> Optional[str]:
if not self._auth_token:
auth_url = f"{self._base_url}/token"
response = requests.get(
auth_url,
params={
"service": f"{self._registry}",
"scope": f"repository:{self._namespace}/{self._image_name}:pull",
},
)
response.raise_for_status()
self._auth_token = response.json()["token"]
return self._auth_token
def get_auth_header(self) -> Dict[str, str]:
return {"Authorization": f"Bearer {self.get_auth_token()}"}
def list_tags(self) -> list:
url = f"{self._image_url}/tags/list"
response = requests.get(url, headers=self.get_auth_header())
response.raise_for_status()
tags = response.json().get("tags", [])
return tags
def get_manifest(
self,
tag: str,
) -> requests.Response:
"""Get manifest information for a specific tag"""
manifest_url = f"{self._image_url}/manifests/{tag}"
headers = {
"Accept": ACCEPT_MANIFESTS_HEADER,
"Authorization": f"Bearer {self.get_auth_token()}",
}
response = requests.get(manifest_url, headers=headers)
response.raise_for_status()
return response
def list_manifests(self, tag: str) -> list:
return (
self.get_manifest(
tag,
)
.json()
.get("manifests")
)
def get_blob(self, digest: str) -> requests.Response:
url = f"{self._image_url}/blobs/{digest}"
response = requests.get(
url,
headers={
"Authorization": f"Bearer {self.get_auth_token()}",
},
)
response.raise_for_status()
return response
def get_manifest_digest(
self, tag: str, tag_manifest_content: Optional[bytes] = None
) -> str:
if not tag_manifest_content:
tag_manifest_content = self.get_manifest(tag).content
return sha256(tag_manifest_content).hexdigest()
def _url(image):
return f"https://{image.registry}/v2/{image.namespace}/{image.image_name}"
# XXX Refactor this with regular functions rather than a class
def get_manifest_digest(image_str: str) -> str:
image = parse_image_location(image_str)
return RegistryClient(image).get_manifest_digest(image.tag)
def list_tags(image_str: str) -> list:
return RegistryClient(image_str).list_tags()
def get_manifest(image_str: str) -> bytes:
image = parse_image_location(image_str)
url = f"{_url(image)}/tags/list"
response = requests.get(url, headers=_get_auth_header(image))
response.raise_for_status()
tags = response.json().get("tags", [])
return tags
def get_manifest(image_str) -> requests.Response:
"""Get manifest information for a specific tag"""
image = parse_image_location(image_str)
manifest_url = f"{_url(image)}/manifests/{image.tag}"
headers = {
"Accept": ACCEPT_MANIFESTS_HEADER,
}
headers.update(_get_auth_header(image))
response = requests.get(manifest_url, headers=headers)
response.raise_for_status()
return response
def list_manifests(image_str) -> list:
return get_manifest(image_str).json().get("manifests")
def get_blob(image, digest: str) -> requests.Response:
response = requests.get(
f"{_url(image)}/blobs/{digest}",
headers={
"Authorization": f"Bearer {_get_auth_token(image)}",
},
)
response.raise_for_status()
return response
def get_manifest_digest(
image_str: str, tag_manifest_content: Optional[bytes] = None
) -> str:
image = parse_image_location(image_str)
if not tag_manifest_content:
tag_manifest_content = get_manifest(image).content
return sha256(tag_manifest_content).hexdigest()
client = RegistryClient(image)
resp = client.get_manifest(image.tag)
return resp.content

View file

@ -4,7 +4,6 @@ import re
import subprocess
import tarfile
from base64 import b64decode, b64encode
from functools import reduce
from hashlib import sha256
from io import BytesIO
from pathlib import Path
@ -28,8 +27,6 @@ def get_config_dir() -> Path:
# XXX Store this somewhere else.
DEFAULT_PUBKEY_LOCATION = get_resource_path("freedomofpress-dangerzone-pub.key")
SIGNATURES_PATH = get_config_dir() / "signatures"
LAST_LOG_INDEX = SIGNATURES_PATH / "last_log_index"
__all__ = [
"verify_signature",
"load_signatures",
@ -130,26 +127,22 @@ def verify_signatures(
return True
def get_last_log_index() -> int:
SIGNATURES_PATH.mkdir(parents=True, exist_ok=True)
if not LAST_LOG_INDEX.exists():
return 0
def upgrade_container_image(image: str, manifest_digest: str, pubkey: str) -> bool:
"""Verify and upgrade the image to the latest, if signed."""
update_available, _ = is_update_available(image)
if not update_available:
raise errors.ImageAlreadyUpToDate("The image is already up to date")
with open(LAST_LOG_INDEX) as f:
return int(f.read())
signatures = get_remote_signatures(image, manifest_digest)
verify_signatures(signatures, manifest_digest, pubkey)
# At this point, the signatures are verified
# We store the signatures just now to avoid storing unverified signatures
store_signatures(signatures, manifest_digest, pubkey)
def get_log_index_from_signatures(signatures: List[Dict]) -> int:
return reduce(
lambda acc, sig: max(acc, sig["Bundle"]["Payload"]["logIndex"]), signatures, 0
)
def write_log_index(log_index: int) -> None:
last_log_index_path = SIGNATURES_PATH / "last_log_index"
with open(log_index, "w") as f:
f.write(str(log_index))
# let's upgrade the image
# XXX Use the image digest here to avoid race conditions
return runtime.container_pull(image)
def _get_blob(tmpdir: str, digest: str) -> Path:
@ -185,7 +178,7 @@ def upgrade_container_image_airgapped(container_tar: str, pubkey: str) -> str:
if not cosign.verify_local_image(tmpdir, pubkey):
raise errors.SignatureVerificationError()
# Remove the signatures from the archive, otherwise podman is not able to load it
# Remove the signatures from the archive.
with open(Path(tmpdir) / "index.json") as f:
index_json = json.load(f)
@ -202,15 +195,6 @@ def upgrade_container_image_airgapped(container_tar: str, pubkey: str) -> str:
image_name, signatures = convert_oci_images_signatures(json.load(f), tmpdir)
log.info(f"Found image name: {image_name}")
# Ensure that we only upgrade if the log index is higher than the last known one
incoming_log_index = get_log_index_from_signatures(signatures)
last_log_index = get_last_log_index()
if incoming_log_index < last_log_index:
raise errors.InvalidLogIndex(
"The log index is not higher than the last known one"
)
image_digest = index_json["manifests"][0].get("digest").replace("sha256:", "")
# Write the new index.json to the temp folder
@ -224,7 +208,7 @@ def upgrade_container_image_airgapped(container_tar: str, pubkey: str) -> str:
archive.add(Path(tmpdir) / "oci-layout", arcname="oci-layout")
archive.add(Path(tmpdir) / "blobs", arcname="blobs")
runtime.load_image_tarball_from_tar(temporary_tar.name)
runtime.load_image_tarball_file(temporary_tar.name)
runtime.tag_image_by_digest(image_digest, image_name)
store_signatures(signatures, image_digest, pubkey)
@ -299,13 +283,9 @@ def store_signatures(signatures: list[Dict], image_digest: str, pubkey: str) ->
Store signatures locally in the SIGNATURE_PATH folder, like this:
~/.config/dangerzone/signatures/
<pubkey-digest>
<image-digest>.json
<image-digest>.json
last_log_index
The last_log_index file is used to keep track of the last log index
processed by the updater.
<pubkey-digest>
<image-digest>.json
<image-digest>.json
The format used in the `.json` file is the one of `cosign download
signature`, which differs from the "bundle" one used afterwards.
@ -364,7 +344,6 @@ def get_remote_signatures(image: str, digest: str) -> List[Dict]:
"""Retrieve the signatures from the registry, via `cosign download`."""
cosign.ensure_installed()
# XXX: try/catch here
process = subprocess.run(
["cosign", "download", "signature", f"{image}@sha256:{digest}"],
capture_output=True,
@ -403,31 +382,3 @@ def prepare_airgapped_archive(image_name, destination):
with tarfile.open(destination, "w") as archive:
archive.add(tmpdir, arcname=".")
def upgrade_container_image(image: str, manifest_digest: str, pubkey: str) -> bool:
"""Verify and upgrade the image to the latest, if signed."""
update_available, _ = is_update_available(image)
if not update_available:
raise errors.ImageAlreadyUpToDate("The image is already up to date")
signatures = get_remote_signatures(image, manifest_digest)
verify_signatures(signatures, manifest_digest, pubkey)
# Ensure that we only upgrade if the log index is higher than the last known one
incoming_log_index = get_log_index_from_signatures(signatures)
last_log_index = get_last_log_index()
if incoming_log_index < last_log_index:
raise errors.InvalidLogIndex(
"The log index is not higher than the last known one"
)
# let's upgrade the image
# XXX Use the image digest here to avoid race conditions
upgraded = runtime.container_pull(image)
# At this point, the signatures are verified
# We store the signatures just now to avoid storing unverified signatures
store_signatures(signatures, manifest_digest, pubkey)
return upgraded

View file

@ -22,13 +22,13 @@ In case of sucess, it will report back:
```
🎉 Successfully verified image
'ghcr.io/freedomofpress/dangerzone/dangerzone:<tag>@sha256:<digest>'
'ghcr.io/freedomofpress/dangerzone/dangerzone:20250129-0.8.0-149-gbf2f5ac@sha256:4da441235e84e93518778827a5c5745d532d7a4079886e1647924bee7ef1c14d'
and its associated claims:
- ✅ SLSA Level 3 provenance
- ✅ GitHub repo: freedomofpress/dangerzone
- ✅ GitHub actions workflow: <workflow>
- ✅ Git branch: <branch>
- ✅ Git commit: <commit>
- ✅ GitHub repo: apyrgio/dangerzone
- ✅ GitHub actions workflow: .github/workflows/multi_arch_build.yml
- ✅ Git branch: test/multi-arch
- ✅ Git commit: bf2f5accc24bd15a4f5c869a7f0b03b8fe48dfb6
```
## Sign and publish the remote image
@ -37,11 +37,11 @@ Once the image has been reproduced locally, we can add a signature to the contai
and update the `latest` tag to point to the proper hash.
```bash
cosign sign --sk ghcr.io/freedomofpress/dangerzone/dangerzone:${TAG}@sha256:${DIGEST}
cosign sign --sk ghcr.io/freedomofpress/dangerzone/dangerzone:20250129-0.8.0-149-gbf2f5ac@sha256:4da441235e84e93518778827a5c5745d532d7a4079886e1647924bee7ef1c14d
# And mark bump latest
crane auth login ghcr.io -u USERNAME --password $(cat pat_token)
crane tag ghcr.io/freedomofpress/dangerzone/dangerzone@sha256:${DIGEST} latest
crane tag ghcr.io/freedomofpress/dangerzone/dangerzone@sha256:4da441235e84e93518778827a5c5745d532d7a4079886e1647924bee7ef1c14d latest
```
## Install updates
@ -49,7 +49,7 @@ crane tag ghcr.io/freedomofpress/dangerzone/dangerzone@sha256:${DIGEST} latest
To check if a new container image has been released, and update your local installation with it, you can use the following commands:
```bash
dangerzone-image upgrade ghcr.io/freedomofpress/dangerzone/dangerzone
dangerzone-image upgrade ghcr.io/almet/dangerzone/dangerzone
```
## Verify locally
@ -57,7 +57,7 @@ dangerzone-image upgrade ghcr.io/freedomofpress/dangerzone/dangerzone
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/freedomofpress/dangerzone/dangerzone
dangerzone-image verify-local ghcr.io/almet/dangerzone/dangerzone
```
## Installing image updates to air-gapped environments
@ -73,7 +73,7 @@ This archive will contain all the needed material to validate that the new conta
On the machine on which you prepare the packages:
```bash
dangerzone-image prepare-archive --output dz-fa94872.tar ghcr.io/freedomofpress/dangerzone/dangerzone@sha256:<digest>
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: