Make our container image more reproducible

Fix the following sources of non-determinism in our container image:
1. Touch /etc/apt/sources.list with a UTC timestamp. Else, builds on
   different machines will result to different Unix epochs for that
   file.
2. Turn "date of last password change" in /etc/shadow into a constant
   number.
3. Fix r-s file permissions in groups, due to inconsistent COPY behavior
   in containerized Buildkit. This requires creating a full file
   hierarchy in a separate directory (see new_root/).
4. Set a specific modification time to the entrypoint script, because
   rewrite-timestamp=true does not overwrite it.
This commit is contained in:
Alex Pyrgiotis 2025-02-26 18:40:28 +02:00
parent f2babae671
commit de45069b55
No known key found for this signature in database
GPG key ID: B6C15EBA0357C9AA
2 changed files with 80 additions and 52 deletions

View file

@ -4,7 +4,7 @@
ARG DEBIAN_IMAGE_DATE=20250224 ARG DEBIAN_IMAGE_DATE=20250224
FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim as dangerzone-image FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim AS dangerzone-image
ARG GVISOR_ARCHIVE_DATE=20250217 ARG GVISOR_ARCHIVE_DATE=20250217
ARG DEBIAN_ARCHIVE_DATE=20250226 ARG DEBIAN_ARCHIVE_DATE=20250226
@ -22,8 +22,8 @@ RUN \
--mount=type=bind,source=./container_helpers/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ --mount=type=bind,source=./container_helpers/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \
--mount=type=bind,source=./container_helpers/gvisor.key,target=/tmp/gvisor.key \ --mount=type=bind,source=./container_helpers/gvisor.key,target=/tmp/gvisor.key \
: "Hacky way to set a date for the Debian snapshot repos" && \ : "Hacky way to set a date for the Debian snapshot repos" && \
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list.d/debian.sources && \ touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list.d/debian.sources && \
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list && \ touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list && \
repro-sources-list.sh && \ repro-sources-list.sh && \
: "Setup APT to install gVisor from its separate APT repo" && \ : "Setup APT to install gVisor from its separate APT repo" && \
apt-get update && \ apt-get update && \
@ -52,9 +52,13 @@ RUN mkdir /opt/libreoffice_ext && cd /opt/libreoffice_ext \
&& rm /root/.wget-hsts && rm /root/.wget-hsts
# Create an unprivileged user both for gVisor and for running Dangerzone. # Create an unprivileged user both for gVisor and for running Dangerzone.
# XXX: Make the shadow field "date of last password change" a constant
# number.
RUN addgroup --gid 1000 dangerzone RUN addgroup --gid 1000 dangerzone
RUN adduser --uid 1000 --ingroup dangerzone --shell /bin/true \ RUN adduser --uid 1000 --ingroup dangerzone --shell /bin/true \
--disabled-password --home /home/dangerzone dangerzone --disabled-password --home /home/dangerzone dangerzone \
&& chage -d 99999 dangerzone \
&& rm /etc/shadow-
# Copy Dangerzone's conversion logic under /opt/dangerzone, and allow Python to # Copy Dangerzone's conversion logic under /opt/dangerzone, and allow Python to
# import it. # import it.
@ -165,20 +169,47 @@ RUN mkdir /home/dangerzone/.containers
# The `ln` binary, even if you specify it by its full path, cannot run # The `ln` binary, even if you specify it by its full path, cannot run
# (probably because `ld-linux.so` can't be found). For this reason, we have # (probably because `ld-linux.so` can't be found). For this reason, we have
# to create the symlinks beforehand, in a previous build stage. Then, in an # to create the symlinks beforehand, in a previous build stage. Then, in an
# empty contianer image (scratch images), we can copy these symlinks and the # empty container image (scratch images), we can copy these symlinks and the
# /usr, and stich everything together. # /usr, and stitch everything together.
############################################################################### ###############################################################################
# Create the filesystem hierarchy that will be used to symlink /usr. # Create the filesystem hierarchy that will be used to symlink /usr.
RUN mkdir /new_root RUN mkdir -p \
RUN mkdir /new_root/root /new_root/run /new_root/tmp /new_root \
RUN chmod 777 /new_root/tmp /new_root/root \
/new_root/run \
/new_root/tmp \
/new_root/home/dangerzone/dangerzone-image/rootfs
# XXX: Remove /etc/resolv.conf, so that the network configuration of the host
# does not leak.
RUN cp -r /etc /var /new_root/ \
&& rm /new_root/etc/resolv.conf
RUN cp -r /etc /opt /usr /new_root/home/dangerzone/dangerzone-image/rootfs \
&& rm /new_root/home/dangerzone/dangerzone-image/rootfs/etc/resolv.conf
RUN ln -s /home/dangerzone/dangerzone-image/rootfs/usr /new_root/usr RUN ln -s /home/dangerzone/dangerzone-image/rootfs/usr /new_root/usr
RUN ln -s usr/bin /new_root/bin RUN ln -s usr/bin /new_root/bin
RUN ln -s usr/lib /new_root/lib RUN ln -s usr/lib /new_root/lib
RUN ln -s usr/lib64 /new_root/lib64 RUN ln -s usr/lib64 /new_root/lib64
RUN ln -s usr/sbin /new_root/sbin RUN ln -s usr/sbin /new_root/sbin
RUN ln -s usr/bin /new_root/home/dangerzone/dangerzone-image/rootfs/bin
RUN ln -s usr/lib /new_root/home/dangerzone/dangerzone-image/rootfs/lib
RUN ln -s usr/lib64 /new_root/home/dangerzone/dangerzone-image/rootfs/lib64
# Fix permissions in /home/dangerzone, so that our entrypoint script can make
# changes in the following folders.
RUN chown dangerzone:dangerzone \
/new_root/home/dangerzone \
/new_root/home/dangerzone/dangerzone-image/
# Fix permissions in /tmp, so that it can be used by unprivileged users.
RUN chmod 777 /new_root/tmp
COPY container_helpers/entrypoint.py /new_root
# HACK: For reasons that we are not sure yet, we need to explicitly specify the
# modification time of this file.
RUN touch -d ${DEBIAN_ARCHIVE_DATE}Z /new_root/entrypoint.py
## Final image ## Final image
@ -188,24 +219,7 @@ FROM scratch
# /usr can be a symlink. # /usr can be a symlink.
COPY --from=dangerzone-image /new_root/ / COPY --from=dangerzone-image /new_root/ /
# Copy the bare minimum to run Dangerzone in the inner container image.
COPY --from=dangerzone-image /etc/ /home/dangerzone/dangerzone-image/rootfs/etc/
COPY --from=dangerzone-image /opt/ /home/dangerzone/dangerzone-image/rootfs/opt/
COPY --from=dangerzone-image /usr/ /home/dangerzone/dangerzone-image/rootfs/usr/
RUN ln -s usr/bin /home/dangerzone/dangerzone-image/rootfs/bin
RUN ln -s usr/lib /home/dangerzone/dangerzone-image/rootfs/lib
RUN ln -s usr/lib64 /home/dangerzone/dangerzone-image/rootfs/lib64
# Copy the bare minimum to let the security scanner find vulnerabilities.
COPY --from=dangerzone-image /etc/ /etc/
COPY --from=dangerzone-image /var/ /var/
# Allow our entrypoint script to make changes in the following folders.
RUN chown dangerzone:dangerzone /home/dangerzone /home/dangerzone/dangerzone-image/
# Switch to the dangerzone user for the rest of the script. # Switch to the dangerzone user for the rest of the script.
USER dangerzone USER dangerzone
COPY container_helpers/entrypoint.py /
ENTRYPOINT ["/entrypoint.py"] ENTRYPOINT ["/entrypoint.py"]

View file

@ -4,7 +4,7 @@
ARG DEBIAN_IMAGE_DATE={{DEBIAN_IMAGE_DATE}} ARG DEBIAN_IMAGE_DATE={{DEBIAN_IMAGE_DATE}}
FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim as dangerzone-image FROM debian:bookworm-${DEBIAN_IMAGE_DATE}-slim AS dangerzone-image
ARG GVISOR_ARCHIVE_DATE={{GVISOR_ARCHIVE_DATE}} ARG GVISOR_ARCHIVE_DATE={{GVISOR_ARCHIVE_DATE}}
ARG DEBIAN_ARCHIVE_DATE={{DEBIAN_ARCHIVE_DATE}} ARG DEBIAN_ARCHIVE_DATE={{DEBIAN_ARCHIVE_DATE}}
@ -22,8 +22,8 @@ RUN \
--mount=type=bind,source=./container_helpers/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \ --mount=type=bind,source=./container_helpers/repro-sources-list.sh,target=/usr/local/bin/repro-sources-list.sh \
--mount=type=bind,source=./container_helpers/gvisor.key,target=/tmp/gvisor.key \ --mount=type=bind,source=./container_helpers/gvisor.key,target=/tmp/gvisor.key \
: "Hacky way to set a date for the Debian snapshot repos" && \ : "Hacky way to set a date for the Debian snapshot repos" && \
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list.d/debian.sources && \ touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list.d/debian.sources && \
touch -d ${DEBIAN_ARCHIVE_DATE} /etc/apt/sources.list && \ touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list && \
repro-sources-list.sh && \ repro-sources-list.sh && \
: "Setup APT to install gVisor from its separate APT repo" && \ : "Setup APT to install gVisor from its separate APT repo" && \
apt-get update && \ apt-get update && \
@ -52,9 +52,13 @@ RUN mkdir /opt/libreoffice_ext && cd /opt/libreoffice_ext \
&& rm /root/.wget-hsts && rm /root/.wget-hsts
# Create an unprivileged user both for gVisor and for running Dangerzone. # Create an unprivileged user both for gVisor and for running Dangerzone.
# XXX: Make the shadow field "date of last password change" a constant
# number.
RUN addgroup --gid 1000 dangerzone RUN addgroup --gid 1000 dangerzone
RUN adduser --uid 1000 --ingroup dangerzone --shell /bin/true \ RUN adduser --uid 1000 --ingroup dangerzone --shell /bin/true \
--disabled-password --home /home/dangerzone dangerzone --disabled-password --home /home/dangerzone dangerzone \
&& chage -d 99999 dangerzone \
&& rm /etc/shadow-
# Copy Dangerzone's conversion logic under /opt/dangerzone, and allow Python to # Copy Dangerzone's conversion logic under /opt/dangerzone, and allow Python to
# import it. # import it.
@ -165,20 +169,47 @@ RUN mkdir /home/dangerzone/.containers
# The `ln` binary, even if you specify it by its full path, cannot run # The `ln` binary, even if you specify it by its full path, cannot run
# (probably because `ld-linux.so` can't be found). For this reason, we have # (probably because `ld-linux.so` can't be found). For this reason, we have
# to create the symlinks beforehand, in a previous build stage. Then, in an # to create the symlinks beforehand, in a previous build stage. Then, in an
# empty contianer image (scratch images), we can copy these symlinks and the # empty container image (scratch images), we can copy these symlinks and the
# /usr, and stich everything together. # /usr, and stitch everything together.
############################################################################### ###############################################################################
# Create the filesystem hierarchy that will be used to symlink /usr. # Create the filesystem hierarchy that will be used to symlink /usr.
RUN mkdir /new_root RUN mkdir -p \
RUN mkdir /new_root/root /new_root/run /new_root/tmp /new_root \
RUN chmod 777 /new_root/tmp /new_root/root \
/new_root/run \
/new_root/tmp \
/new_root/home/dangerzone/dangerzone-image/rootfs
# XXX: Remove /etc/resolv.conf, so that the network configuration of the host
# does not leak.
RUN cp -r /etc /var /new_root/ \
&& rm /new_root/etc/resolv.conf
RUN cp -r /etc /opt /usr /new_root/home/dangerzone/dangerzone-image/rootfs \
&& rm /new_root/home/dangerzone/dangerzone-image/rootfs/etc/resolv.conf
RUN ln -s /home/dangerzone/dangerzone-image/rootfs/usr /new_root/usr RUN ln -s /home/dangerzone/dangerzone-image/rootfs/usr /new_root/usr
RUN ln -s usr/bin /new_root/bin RUN ln -s usr/bin /new_root/bin
RUN ln -s usr/lib /new_root/lib RUN ln -s usr/lib /new_root/lib
RUN ln -s usr/lib64 /new_root/lib64 RUN ln -s usr/lib64 /new_root/lib64
RUN ln -s usr/sbin /new_root/sbin RUN ln -s usr/sbin /new_root/sbin
RUN ln -s usr/bin /new_root/home/dangerzone/dangerzone-image/rootfs/bin
RUN ln -s usr/lib /new_root/home/dangerzone/dangerzone-image/rootfs/lib
RUN ln -s usr/lib64 /new_root/home/dangerzone/dangerzone-image/rootfs/lib64
# Fix permissions in /home/dangerzone, so that our entrypoint script can make
# changes in the following folders.
RUN chown dangerzone:dangerzone \
/new_root/home/dangerzone \
/new_root/home/dangerzone/dangerzone-image/
# Fix permissions in /tmp, so that it can be used by unprivileged users.
RUN chmod 777 /new_root/tmp
COPY container_helpers/entrypoint.py /new_root
# HACK: For reasons that we are not sure yet, we need to explicitly specify the
# modification time of this file.
RUN touch -d ${DEBIAN_ARCHIVE_DATE}Z /new_root/entrypoint.py
## Final image ## Final image
@ -188,24 +219,7 @@ FROM scratch
# /usr can be a symlink. # /usr can be a symlink.
COPY --from=dangerzone-image /new_root/ / COPY --from=dangerzone-image /new_root/ /
# Copy the bare minimum to run Dangerzone in the inner container image.
COPY --from=dangerzone-image /etc/ /home/dangerzone/dangerzone-image/rootfs/etc/
COPY --from=dangerzone-image /opt/ /home/dangerzone/dangerzone-image/rootfs/opt/
COPY --from=dangerzone-image /usr/ /home/dangerzone/dangerzone-image/rootfs/usr/
RUN ln -s usr/bin /home/dangerzone/dangerzone-image/rootfs/bin
RUN ln -s usr/lib /home/dangerzone/dangerzone-image/rootfs/lib
RUN ln -s usr/lib64 /home/dangerzone/dangerzone-image/rootfs/lib64
# Copy the bare minimum to let the security scanner find vulnerabilities.
COPY --from=dangerzone-image /etc/ /etc/
COPY --from=dangerzone-image /var/ /var/
# Allow our entrypoint script to make changes in the following folders.
RUN chown dangerzone:dangerzone /home/dangerzone /home/dangerzone/dangerzone-image/
# Switch to the dangerzone user for the rest of the script. # Switch to the dangerzone user for the rest of the script.
USER dangerzone USER dangerzone
COPY container_helpers/entrypoint.py /
ENTRYPOINT ["/entrypoint.py"] ENTRYPOINT ["/entrypoint.py"]