Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
76477e9
CI: Remove the "trivially update" ability
almet Mar 5, 2026
b42ccc5
CI: place the ci.yml in the correct folder
almet Mar 5, 2026
746f3ad
CI: Put reusable workflows at top-level
almet Mar 5, 2026
47d8546
CI: Remove install deps lint step
almet Mar 5, 2026
b62fe96
CI: update Docker build context
almet Mar 5, 2026
cce4197
Fix the format of pyproject.toml
almet Mar 9, 2026
646242e
Derive the image-name from the git ref
almet Mar 9, 2026
a0e45f6
Add tests
almet Mar 18, 2026
3049f79
Create the venv before using it
almet Mar 18, 2026
5f39cef
Add an --only-local flag to run local tests
almet Mar 18, 2026
ac02aa1
Don't add a newline in image-id.txt
almet Mar 18, 2026
0ad90c5
Merge the logic of containerized and local tests
almet Mar 19, 2026
bc09f87
Add the official dangerzone keys in the repo
almet Mar 19, 2026
44d209c
Update the README
almet Mar 19, 2026
c15cc4a
FIXME: --only-local to --local
almet Mar 19, 2026
6a49ae9
FIXUP: update readme
almet Mar 19, 2026
d79ee23
Retag latest as old-latest when publishing new images
almet Mar 19, 2026
950cf15
Relax the minimum python version for this package
almet Mar 19, 2026
eeca222
FIXUP: relax even more
almet Mar 19, 2026
37f35b3
Another update: more formats to convert, relax dependencies in pyproj…
almet Mar 20, 2026
e07a433
Fix retag latest mechanism
almet Mar 31, 2026
c3f003f
Use h2orestart from Debian repository
almet Mar 31, 2026
219d280
Publish to dangerzone-alpha/v1 for the testing phase
almet Mar 31, 2026
a66b3b3
security: env var allowlist + testable OCI config builder
jw408 Apr 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions .github/ci.yml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ on:
required: false
type: string
default: "dangerzone-tests"
trivially_update_container:
required: false
type: boolean
default: false
secrets:
registry_token:
required: true
Expand All @@ -44,12 +40,6 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Install dev. dependencies
run: |-
sudo apt-get update
sudo apt-get install -y git python3-poetry --no-install-recommends
poetry install --only package

- name: Verify that the Dockerfile matches the committed template and params
run: |-
cp Dockerfile Dockerfile.orig
Expand All @@ -68,11 +58,6 @@ jobs:
with:
fetch-depth: 0

- name: Update the contents of the inner container if asked-for
if: ${{ inputs.trivially_update_container }}
run: |
echo "# A comment to generate a new container hash $RANDOM" >> conversion/doc_to_pixels.py

- name: Compute image parameters
id: params
run: |
Expand Down Expand Up @@ -107,11 +92,6 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: Update the contents of the inner container if asked-for
if: ${{ inputs.trivially_update_container }}
run: |
echo "# A comment to generate a new container hash" >> conversion/doc_to_pixels.py

- name: Prepare
run: |
platform=${{ matrix.platform.name }}
Expand All @@ -135,7 +115,7 @@ jobs:
id: build
uses: docker/build-push-action@v6
with:
context: ./dangerzone/
context: ./src/
file: Dockerfile
build-args: |
DEBIAN_ARCHIVE_DATE=${{ needs.prepare.outputs.debian_archive_date }}
Expand Down Expand Up @@ -331,3 +311,39 @@ jobs:
run: |-
cosign sign --recursive -d --key=${{ inputs.key_name }}.key "${{ env.IMAGE_URI }}"
shell: bash

retag-latest:
if: ${{ startsWith(inputs.image_name, 'dangerzone-testing/') }}
runs-on: ubuntu-latest
needs:
- merge
steps:
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ inputs.registry_user }}
password: ${{ secrets.registry_token }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Set base image
run: |
image_with_tag="${{ needs.merge.outputs.image }}"
# Only keep the image name (strip the part after ":"
echo "BASE_IMAGE=${image_with_tag%:*}" >> "$GITHUB_ENV"

- name: Capture previous latest
run: |
docker buildx imagetools inspect "$BASE_IMAGE:latest" --format '{{json .Manifest}}' > manifest || true
if [ -s manifest ]; then
digest=$(jq -r .digest manifest)
docker buildx imagetools create -t "$BASE_IMAGE:old-latest" "$BASE_IMAGE@${digest}"
fi

- name: Update latest tag
run: |
docker buildx imagetools create \
-t "$BASE_IMAGE:latest" \
"$BASE_IMAGE@${{ needs.merge.outputs.digest_root }}"
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Tests
on:
pull_request:
push:
branches:
- main
- "test/**"
schedule:
- cron: "2 0 * * *" # Run every day at 02:00 UTC.
workflow_dispatch:

permissions:
packages: write
actions: read # for detecting the Github Actions environment.
id-token: write # for creating OIDC tokens for signing.

jobs:
tests:
name: Run tests
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: astral-sh/setup-uv@v4
with:
python-version: "3.13"
enable-cache: true

- name: Build Dangerzone image
run: python3 build-image.py

- name: Load local image for tests
run: |
podman load -i container.tar | sed -e 's/Loaded image: //' | tr -d '\n' > tests/share/image-id.txt

- name: Install dependencies
run: uv venv && uv pip install -e ".[dev]"

- name: Run tests
run: uv run pytest

build-container-image:
name: Build, push and sign container image
uses: ./.github/workflows/build-push.yml
with:
registry: "ghcr.io/${{ github.repository_owner }}"
registry_user: ${{ github.repository_owner }}
image_name: "dangerzone-testing/${{ github.head_ref || github.ref_name }}/v1"
reproduce: false
sign: true
key_name: "tests/assets/dangerzone-testing"
secrets:
registry_token: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ on:

jobs:
build-push-image:
uses: ./.github/workflows/actions/build-push.yml
uses: ./.github/workflows/build-push.yml
with:
registry: ghcr.io/${{ github.repository_owner }}
registry_user: ${{ github.actor }}
image_name: dangerzone/v1
image_name: dangerzone-alpha/v1
reproduce: true
# Pass sign=false here as signing is a manual process for the nightly images.
sign: false
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
image-id.txt
container.tar
.python-version
*.pyc
13 changes: 7 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ RUN \
python3 python3-fitz libreoffice-nogui libreoffice-java-common \
python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
runsc unzip && \
: "Get H2Orestart from Debian: download .deb and extract (can't install due to libreoffice-core vs libreoffice-core-nogui conflict)" && \
mkdir -p /opt/libreoffice_ext/ && \
install -dm777 /usr/lib/libreoffice/share/extensions/ && \
apt-get download libreoffice-h2orestart && \
dpkg-deb --extract libreoffice-h2orestart_*.deb /tmp/h2orestart && \
mv /tmp/h2orestart/usr/lib/libreoffice/share/extensions/h2orestart /opt/libreoffice_ext/ && \
rm -rf /tmp/h2orestart libreoffice-h2orestart_*.deb && \
: "Clean up for improving reproducibility (optional)" && \
rm -rf /var/cache/fontconfig/ && \
rm -rf /etc/ssl/certs/java/cacerts && \
Expand All @@ -57,12 +64,6 @@ RUN touch /opt/dangerzone/dangerzone/__init__.py
# Copy only the Python code, and not any produced .pyc files.
COPY conversion/*.py /opt/dangerzone/dangerzone/conversion/

# Copy the H2Orestart.oxt LibreOffice plugin, which is managed by Mazette.
# TODO: Use H20restart from Debian
# RUN mkdir -p /opt/libreoffice_ext/
# RUN install -dm777 /usr/lib/libreoffice/share/extensions/
# COPY ./helpers/h2orestart.oxt /opt/libreoffice_ext/

# Create a directory that will be used by gVisor as the place where it will
# store the state of its containers.
RUN mkdir /home/dangerzone/.containers
Expand Down
13 changes: 7 additions & 6 deletions Dockerfile.in
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ RUN \
python3 python3-fitz libreoffice-nogui libreoffice-java-common \
python3-magic default-jre-headless fonts-noto-cjk fonts-dejavu \
runsc unzip && \
: "Get H2Orestart from Debian: download .deb and extract (can't install due to libreoffice-core vs libreoffice-core-nogui conflict)" && \
mkdir -p /opt/libreoffice_ext/ && \
install -dm777 /usr/lib/libreoffice/share/extensions/ && \
apt-get download libreoffice-h2orestart && \
dpkg-deb --extract libreoffice-h2orestart_*.deb /tmp/h2orestart && \
mv /tmp/h2orestart/usr/lib/libreoffice/share/extensions/h2orestart /opt/libreoffice_ext/ && \
rm -rf /tmp/h2orestart libreoffice-h2orestart_*.deb && \
: "Clean up for improving reproducibility (optional)" && \
rm -rf /var/cache/fontconfig/ && \
rm -rf /etc/ssl/certs/java/cacerts && \
Expand All @@ -57,12 +64,6 @@ RUN touch /opt/dangerzone/dangerzone/__init__.py
# Copy only the Python code, and not any produced .pyc files.
COPY conversion/*.py /opt/dangerzone/dangerzone/conversion/

# Copy the H2Orestart.oxt LibreOffice plugin, which is managed by Mazette.
# TODO: Use H20restart from Debian
# RUN mkdir -p /opt/libreoffice_ext/
# RUN install -dm777 /usr/lib/libreoffice/share/extensions/
# COPY ./helpers/h2orestart.oxt /opt/libreoffice_ext/

# Create a directory that will be used by gVisor as the place where it will
# store the state of its containers.
RUN mkdir /home/dangerzone/.containers
Expand Down
71 changes: 64 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,73 @@
# Dangerzone-image

This repository contains the dangerzone container image that is used to perform "document to pixels" conversions. This container is used by [dangerzone](https://dangerzone.rocks) to do the secure conversions of the documents.
This repository contains the dangerzone container image that is used to perform "document to pixels" conversions. This container is used by [dangerzone](https://dangerzone.rocks) to securely converst its documents.

## Using the container image

The image is published on a monthly basis on the container registry, alongside their Cosign signatures.
The image is published on a monthly basis on the container registry, alongside their Cosign signatures.
Additionally, nightly and development branches are published under the `dangerzone-testing` namespace.

| Channel | Location | Signed? | Use it for |
| ------- | -------------------------------------- | ---------- | ----------- |
| Stable | `ghcr.io/freedomofpress/dangerzone/v1` | ✅ ([prod keys](/freedomofpress-dangerzone.pub)) | Production |
| Nightly | `ghcr.io/freedomofpress/dangerzone-testing/main/v1` | ✅ ([testing keys](/tests/assets/dangerzone-testing.pub)) | Development |
| Branch | `ghcr.io/freedomofpress/dangerzone-testing/<branch-name>/v1` | ✅ ([testing keys](/tests/assets/dangerzone-testing.pub)) | Development |

## What this container provides

This container provides a way to convert documents to pixel buffers, using a secure sandbox.

The security of the sandbox is provided by different layers:

- The container uses [gVisor](/docs/gvisor.md), an application Kernel that provides a strong layer of isolation between running applications and the host operating system. It is written in a memory-safe language (Go) and runs in userspace.
- Additionally, it is expected that this container is run with specific flags and a specific seccomp policy, to unsure that users are not mapped in the container, that no network is available in the container, etc. See the "how to use" section.

We also provide the following guarantees, related to the distribution of the image:

- The container is [signed](/docs/sign-image) in an auditable way, using Cosign
- Ultimately, the container is [reproducible](/docs/reproducibility.md), and so one can verify that it can be rebuilt, resulting to the same digests.

## How to use this container?

The recommanded way to use this container is via these flags. They require to defined a specific seccomp policy. Seccomp policies is a way to define which system calls are authorized inside the container.

Here is a podman command with the proper flags, and [the gvisor seccomp policy](tests/share/seccomp.gvisor.json).

```bash
podman run \\
--log-driver none \\
--security-opt no-new-privileges \\
--userns nomap \\
--security-opt fseccomp=seccomp.gvisor.json
--cap-drop all \\
--cap-add SYS_CHROOT \\
--security-opt label=type:container_engine_t \\
--network=none \\
-u dangerzone \\
--rm -i ghcr.io/freedomofpress/dangerzone/v1 \\
/usr/bin/python3 -m dangerzone.conversion.doc_to_pixels
```
ghcr.io/freedomofpress/dangerzone/v1
```

## dangerzone-docs-to-pixels python package

The code that runs inside the container is packaged under the name "dangerzone-insecure-conversion".
This is considered insecure because it doesn't run by default inside a sandbox.
## dangerzone-insecure-conversion python package

> [!WARNING]
> Do not use this unless you are certain about what you are doing.
> Do not use this to convert documents that should be processed safely!

The python code that runs inside the container is packaged under the name "dangerzone-insecure-conversion". It's considered insecure because the intended way to run dangerzone is by using a hardened sandbox, which is provided by dangerzone.

With that being said, there are situations where it's useful to run this code on its own, for instance when adding new file formats.

### Running the tests

```bash
uv pip install -e .
uv run pytest

# Or, if you prefer to run the tests outside the sandbox:
uv run pytest --local

# It's also possible to run tests in parallel if you have multiple cores:
uv run --with pytest-xdist pytest -n 6
```
2 changes: 1 addition & 1 deletion build-image.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pathlib import Path

BUILD_CONTEXT = "src"
IMAGE_NAME = "ghcr.io/freedomofpress/dangerzone/v1"
IMAGE_NAME = "ghcr.io/freedomofpress/dangerzone-alpha/v1"
if platform.system() in ["Darwin", "Windows"]:
CONTAINER_RUNTIME = "docker"
elif platform.system() == "Linux":
Expand Down
4 changes: 4 additions & 0 deletions freedomofpress-dangerzone.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3CUlnwh825Z5wQAJV6TekEi6bCK2
YvAg7A+aeerM5hvc4CCCJFBae5AjnsjGHv4V/3xzssck6e51W+b0jU4qVw==
-----END PUBLIC KEY-----
16 changes: 16 additions & 0 deletions freedomofpress-dangerzone.pub.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEBMq+td12us8r1D0v86zGD2LqUcsFAmj3mCcACgkQ86zGD2Lq
Ucu6Qg/8D14Wed5CJzc/jS8qbrbevASiRT/xuszWXZ3I4SrzIsIu7Vt5vq/kBokQ
8BGtyrc19MEkIJOSwoF7q12/DdzVoPqRhWJrcAR2ecNcLCXq39hKQOdUsQSll6vJ
3vsaoHZoMhTkAjwLWZP1KcKeQrmlWEt/EADp2NlixMME2hyuJOSrdpA0qJhbaz4L
Hfu4cStbDeVEiAJ6sHGyFHsV2wSlXyC4tK7Vjl7WRmRE4NIrh/oCv1N4YTSmgUBT
dlKWqMc8O/jP0Y1zmtcUUuRVMD3IRUGJU2cSldw0e4AyW78ZyUIZfSc8V51C7lwX
iOJmtEVQRbI0zS7W+ChNUNTM3lC4uUrUow/kB9AgDKyCbvLK7R8W1fJgim456Fsh
jBHkpcimfF0oenaTG1uuJOVXCzWyPIt8nXtcBz9dOAIus8X+v5zefRuXxibvJkoa
ADIX5dje4nsf41s1z2aZW7linf9oz2/jdNgQG9ZTSHa8e5vS5DgsUlC6abMzjpcI
3cgd2dV2SZcKLJ6BH0QhgaeMmtqbaTGHx9d1JUbQ4aCw+2d7w47Fw+FCEuaDY3e+
/NYscq0A9v3xYHJrp9v1AQbJG4MIs0W8MzG671Q1nQxZudTrR1qiJMoETKQKmDaJ
/BAeUx3vxBp3+y40lL6I8tUFleKMe1oEuDmzlNSWXwI7xeB8H1I=
=zh94
-----END PGP SIGNATURE-----
Loading