From 66600f32dc7c4f45638dbcd7a450bb5707f9f9cd Mon Sep 17 00:00:00 2001 From: Alex Pyrgiotis Date: Wed, 26 Feb 2025 18:40:28 +0200 Subject: [PATCH] Remove sources of non-determinism from our image Make our container image more reproducible, by changing the following in our Dockerfile: 1. Touch `/etc/apt/sources.list` with a UTC timestamp. Else, builds on different countries (!?) may result to different Unix epochs for the same date, and therefore different modification time for the file. 2. Turn the third column of `/etc/shadow` (date of last password change) for the `dangerzone` user into a constant number. 3. Fix r-s file permissions in some copied files, due to inconsistent COPY behavior in containerized vs non-containerized Buildkit. This requires creating a full file hierarchy in a separate directory (see new_root/). 4. Set a specific modification time for the entrypoint script, because rewrite-timestamp=true does not overwrite it. --- Dockerfile | 69 ++++++++++++++++++++++++++++++++------------------- Dockerfile.in | 69 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 86 insertions(+), 52 deletions(-) diff --git a/Dockerfile b/Dockerfile index 64b1098..8975a40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ 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 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/gvisor.key,target=/tmp/gvisor.key \ : "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} /etc/apt/sources.list && \ + touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list.d/debian.sources && \ + touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list && \ repro-sources-list.sh && \ : "Setup APT to install gVisor from its separate APT repo" && \ apt-get update && \ @@ -52,9 +52,13 @@ RUN mkdir /opt/libreoffice_ext && cd /opt/libreoffice_ext \ && rm /root/.wget-hsts # 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 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 # import it. @@ -165,20 +169,50 @@ RUN mkdir /home/dangerzone/.containers # 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 # 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 -# /usr, and stich everything together. +# empty container image (scratch images), we can copy these symlinks and the +# /usr, and stitch everything together. ############################################################################### # Create the filesystem hierarchy that will be used to symlink /usr. -RUN mkdir /new_root -RUN mkdir /new_root/root /new_root/run /new_root/tmp -RUN chmod 777 /new_root/tmp +RUN mkdir -p \ + /new_root \ + /new_root/root \ + /new_root/run \ + /new_root/tmp \ + /new_root/home/dangerzone/dangerzone-image/rootfs + +# Copy the /etc and /var directories under the new root directory. Also, +# copy /etc/, /opt, and /usr to the Dangerzone image rootfs. +# +# NOTE: We also have to remove the resolv.conf file, in order to not leak any DNS +# servers added there during image build time. +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 usr/bin /new_root/bin RUN ln -s usr/lib /new_root/lib RUN ln -s usr/lib64 /new_root/lib64 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 @@ -188,24 +222,7 @@ FROM scratch # /usr can be a symlink. 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. USER dangerzone -COPY container_helpers/entrypoint.py / - ENTRYPOINT ["/entrypoint.py"] diff --git a/Dockerfile.in b/Dockerfile.in index af03c89..050cd2a 100644 --- a/Dockerfile.in +++ b/Dockerfile.in @@ -4,7 +4,7 @@ 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 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/gvisor.key,target=/tmp/gvisor.key \ : "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} /etc/apt/sources.list && \ + touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list.d/debian.sources && \ + touch -d ${DEBIAN_ARCHIVE_DATE}Z /etc/apt/sources.list && \ repro-sources-list.sh && \ : "Setup APT to install gVisor from its separate APT repo" && \ apt-get update && \ @@ -52,9 +52,13 @@ RUN mkdir /opt/libreoffice_ext && cd /opt/libreoffice_ext \ && rm /root/.wget-hsts # 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 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 # import it. @@ -165,20 +169,50 @@ RUN mkdir /home/dangerzone/.containers # 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 # 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 -# /usr, and stich everything together. +# empty container image (scratch images), we can copy these symlinks and the +# /usr, and stitch everything together. ############################################################################### # Create the filesystem hierarchy that will be used to symlink /usr. -RUN mkdir /new_root -RUN mkdir /new_root/root /new_root/run /new_root/tmp -RUN chmod 777 /new_root/tmp +RUN mkdir -p \ + /new_root \ + /new_root/root \ + /new_root/run \ + /new_root/tmp \ + /new_root/home/dangerzone/dangerzone-image/rootfs + +# Copy the /etc and /var directories under the new root directory. Also, +# copy /etc/, /opt, and /usr to the Dangerzone image rootfs. +# +# NOTE: We also have to remove the resolv.conf file, in order to not leak any +# DNS servers added there during image build time. +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 usr/bin /new_root/bin RUN ln -s usr/lib /new_root/lib RUN ln -s usr/lib64 /new_root/lib64 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 @@ -188,24 +222,7 @@ FROM scratch # /usr can be a symlink. 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. USER dangerzone -COPY container_helpers/entrypoint.py / - ENTRYPOINT ["/entrypoint.py"]