mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-05-02 03:32:23 +02:00

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
158 lines
4.5 KiB
Python
158 lines
4.5 KiB
Python
import functools
|
|
import logging
|
|
import sys
|
|
from typing import Any, Callable, TypeVar, cast
|
|
|
|
import click
|
|
|
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class DocumentFilenameException(Exception):
|
|
"""Exception for document-related filename errors."""
|
|
|
|
|
|
class AddedDuplicateDocumentException(DocumentFilenameException):
|
|
"""Exception for a document is added twice."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("A document was added twice")
|
|
|
|
|
|
class InputFileNotFoundException(DocumentFilenameException):
|
|
"""Exception for when an input file does not exist."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Input file not found: make sure you typed it correctly.")
|
|
|
|
|
|
class InputFileNotReadableException(DocumentFilenameException):
|
|
"""Exception for when an input file exists but is not readable."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("You don't have permission to open the input file.")
|
|
|
|
|
|
class NonPDFOutputFileException(DocumentFilenameException):
|
|
"""Exception for when the output file is not a PDF."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Safe PDF filename must end in '.pdf'")
|
|
|
|
|
|
class IllegalOutputFilenameException(DocumentFilenameException):
|
|
"""Exception for when the output file contains illegal characters."""
|
|
|
|
def __init__(self, char: str) -> None:
|
|
super().__init__(f"Illegal character: {char}")
|
|
|
|
|
|
class UnwriteableOutputDirException(DocumentFilenameException):
|
|
"""Exception for when the output file is not writeable."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Safe PDF filename is not writable")
|
|
|
|
|
|
class NotSetInputFilenameException(DocumentFilenameException):
|
|
"""Exception for when the output filename is set before having an
|
|
associated input file."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Input filename has not been set yet.")
|
|
|
|
|
|
class NotSetOutputFilenameException(DocumentFilenameException):
|
|
"""Exception for when the output filename is read before it has been set."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Output filename has not been set yet.")
|
|
|
|
|
|
class NonExistantOutputDirException(DocumentFilenameException):
|
|
"""Exception for when the output dir does not exist."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Output directory does not exist")
|
|
|
|
|
|
class OutputDirIsNotDirException(DocumentFilenameException):
|
|
"""Exception for when the specified output dir is not actually a dir."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Specified output directory is actually not a directory")
|
|
|
|
|
|
class UnwriteableArchiveDirException(DocumentFilenameException):
|
|
"""Exception for when the archive directory cannot be created."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__(
|
|
"Archive directory for storing unsafe documents cannot be created."
|
|
)
|
|
|
|
|
|
class SuffixNotApplicableException(DocumentFilenameException):
|
|
"""Exception for when the suffix cannot be applied to the output filename."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__("Cannot set a suffix after setting an output filename")
|
|
|
|
|
|
def handle_document_errors(func: F) -> F:
|
|
"""Decorator to log document-related errors and exit gracefully."""
|
|
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs): # type: ignore
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except DocumentFilenameException as e:
|
|
if getattr(sys, "dangerzone_dev", False):
|
|
# Show the full traceback only on dev environments.
|
|
msg = "An exception occured while validating a document"
|
|
log.exception(msg)
|
|
click.echo(str(e))
|
|
sys.exit(1)
|
|
|
|
return cast(F, wrapper)
|
|
|
|
|
|
#### Container-related errors
|
|
|
|
|
|
class ContainerException(Exception):
|
|
pass
|
|
|
|
|
|
class ImageNotPresentException(ContainerException):
|
|
pass
|
|
|
|
|
|
class MultipleImagesFoundException(ContainerException):
|
|
pass
|
|
|
|
|
|
class ImageInstallationException(ContainerException):
|
|
pass
|
|
|
|
|
|
class NoContainerTechException(ContainerException):
|
|
def __init__(self, container_tech: str) -> None:
|
|
super().__init__(f"{container_tech} is not installed")
|
|
|
|
|
|
class NotAvailableContainerTechException(ContainerException):
|
|
def __init__(self, container_tech: str, error: str) -> None:
|
|
self.error = error
|
|
self.container_tech = container_tech
|
|
super().__init__(f"{container_tech} is not available")
|
|
|
|
|
|
class UnsupportedContainerRuntime(ContainerException):
|
|
pass
|
|
|
|
|
|
class ContainerPullException(ContainerException):
|
|
pass
|