banger/images/golden/Dockerfile
Thales Maciel 25a1466947
supply chain: verify signatures and pins across image + kernel builds
Three independent hardenings, addressing a review finding that the
kernel and image build pipelines were relying on HTTPS alone for
artifact integrity.

scripts/make-generic-kernel.sh
- Fetch the detached PGP signature (linux-<ver>.tar.sign) alongside
  the tarball and verify it with gpg before extraction. An isolated
  $GNUPGHOME under the tempdir keeps the kernel signers out of the
  invoking user's keyring.
- Import the three kernel.org release signing keys (Greg KH / Linus /
  Sasha Levin) from keyserver.ubuntu.com, falling back to
  keys.openpgp.org. Ubuntu comes first because keys.openpgp.org strips
  unverified UIDs on upload, leaving gpg with UID-less keys it
  refuses to trust.
- Require VALIDSIG (cryptographic proof) rather than GOODSIG
  (printed even for expired keys) before proceeding. Verified
  end-to-end against a clean tarball (accepts) and a byte-flipped
  tampered copy (rejects with BADSIG).
- gpg + gpgv + xz added to the required-tools check.

images/golden/Dockerfile
- Pin Docker's apt signing key by fingerprint. After downloading
  /etc/apt/keyrings/docker.asc we gpg --show-keys --with-colons it,
  extract the fpr, and compare against the expected
  9DC858229FC7DD38854AE2D88D81803C0EBFCD88. A tampered or swapped key
  aborts the build before any apt repo metadata is fetched.
- Replace `curl https://mise.run | sh` with a pinned GitHub release
  binary (mise v2026.4.18, linux-x64) verified against its published
  sha256. Refuses to build on unknown architectures rather than
  silently installing a binary we have no hash for.
- Add gnupg to the ESSENTIAL apt-get install so the fingerprint check
  has gpg available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:38:13 -03:00

149 lines
6.7 KiB
Docker

# banger golden image — Debian bookworm sandbox for development + testing.
#
# Two sections:
# 1. ESSENTIAL — what banger's lifecycle requires to boot the guest.
# 2. OPINION — developer conveniences curated for banger sandboxes.
#
# Banger's guest agents (vsock agent, network bootstrap, first-boot unit)
# are injected at `banger image pull` time, not baked here. Keeping them
# out means this image stays portable enough to run in other contexts.
FROM debian:bookworm-slim
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
LC_ALL=C.UTF-8
# -------- 1. ESSENTIAL --------
# Banger needs: an init (systemd + udev + dbus), sshd (the only
# control channel), TLS roots + curl (first-boot installs + mise
# installer), gnupg (build-time signing-key verification for the
# Docker apt repo), iproute2 (debugging; `ip` is still useful even
# when the kernel sets IP via cmdline).
#
# udev is a Recommends of the systemd package on Debian. With
# --no-install-recommends it's skipped — and without it systemd never
# activates device units, so fstab mounts of /dev/vdb (banger's work
# disk) hang forever waiting for a device that is already enumerated
# by the kernel but never "seen" by systemd. dbus gets the same
# treatment for the same reason (system-bus-ness services wedge
# without it).
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
systemd systemd-sysv udev dbus \
openssh-server \
ca-certificates \
curl \
gnupg \
iproute2 \
&& rm -rf /var/lib/apt/lists/*
# -------- 2. OPINION --------
# Developer sandbox conveniences. Language runtimes are deliberately
# absent — `mise` (below) handles per-repo `.mise.toml`/`.tool-versions`
# on first `vm run`.
# Core CLI + search/nav + build toolchain + lint/debug + editor/session.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git jq less tree file unzip zip rsync \
ripgrep fd-find \
build-essential pkg-config make \
shellcheck sqlite3 \
iputils-ping dnsutils \
vim-tiny tmux htop \
&& rm -rf /var/lib/apt/lists/*
# Docker CE (with Compose v2 + buildx) from the official apt repo.
# Nested-VM docker gives Compose workflows hostname/port isolation
# per banger VM, which is a big part of the sandbox story.
#
# The apt key is verified against its published fingerprint before
# we commit it to the signed-by keyring, so a tampered download (or
# a TLS compromise against download.docker.com) cannot silently
# swap in an attacker-controlled signing key. Fingerprint source:
# https://docs.docker.com/engine/install/debian/#install-using-the-repository
RUN set -eu; \
expected_fpr=9DC858229FC7DD38854AE2D88D81803C0EBFCD88; \
install -m 0755 -d /etc/apt/keyrings; \
curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.asc; \
got="$(gpg --with-colons --show-keys --fingerprint /tmp/docker.asc | awk -F: '/^fpr:/ {print $10; exit}')"; \
if [ "$got" != "$expected_fpr" ]; then \
echo "docker apt key fingerprint mismatch: got $got, want $expected_fpr" >&2; \
exit 1; \
fi; \
mv /tmp/docker.asc /etc/apt/keyrings/docker.asc; \
chmod a+r /etc/apt/keyrings/docker.asc; \
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable\n' \
"$(dpkg --print-architecture)" > /etc/apt/sources.list.d/docker.list; \
apt-get update; \
apt-get install -y --no-install-recommends \
docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin; \
rm -rf /var/lib/apt/lists/*
# mise — per-repo version manager. Installed from a pinned GitHub
# release asset rather than `curl https://mise.run | sh` so a compromise
# of the installer endpoint can't silently push arbitrary code into
# the golden image.
#
# Update protocol: bump MISE_VERSION + MISE_SHA256 together. Source
# for the hash is the `digest` field on the release asset from
# `gh release view --repo jdx/mise --json assets`, or compute from
# the downloaded file and cross-reference against SHASUMS256.txt on
# the release page.
ARG MISE_VERSION=v2026.4.18
ARG MISE_SHA256_AMD64=6ae2d5f0f23a2f2149bc5d9bf264fe0922a1da843f1903e453516c462b23cc1f
RUN set -eux; \
arch="$(dpkg --print-architecture)"; \
if [ "$arch" != "amd64" ]; then \
echo "mise pin only tracks amd64; add a ${arch} hash to refresh" >&2; \
exit 1; \
fi; \
curl -fsSL -o /tmp/mise "https://github.com/jdx/mise/releases/download/${MISE_VERSION}/mise-${MISE_VERSION}-linux-x64"; \
echo "${MISE_SHA256_AMD64} /tmp/mise" | sha256sum -c -; \
install -m 0755 /tmp/mise /usr/local/bin/mise; \
rm /tmp/mise; \
install -d /etc/profile.d; \
printf '%s\n' 'if [ -x /usr/local/bin/mise ]; then eval "$(/usr/local/bin/mise activate bash)"; fi' \
> /etc/profile.d/mise.sh; \
chmod 0644 /etc/profile.d/mise.sh
# Default branch for any git init inside the sandbox.
RUN git config --system init.defaultBranch main
# `fd-find` installs as `fdfind` on Debian to avoid a long-standing name
# clash. Expose the ergonomic name for interactive use.
RUN ln -s /usr/bin/fdfind /usr/local/bin/fd
# Strip per-image identity so every banger VM gets its own.
# - /etc/machine-id: systemd-firstboot regenerates at boot when empty.
# - SSH host keys: removed here; a ssh.service drop-in (below) runs
# `ssh-keygen -A` before sshd so the VM's first boot generates a
# unique set.
# - /run/sshd tmpfiles entry: Debian's openssh-server package doesn't
# ship one, and ssh.service's own `RuntimeDirectory=sshd` fires too
# late for the ExecStartPre config test, so sshd -t blows up with
# "Missing privilege separation directory: /run/sshd" before the
# daemon ever starts. Creating the dir via tmpfiles.d runs early in
# systemd-tmpfiles-setup, well before ssh.service kicks off.
RUN : > /etc/machine-id \
&& rm -f /etc/ssh/ssh_host_*_key /etc/ssh/ssh_host_*_key.pub \
&& install -d /etc/systemd/system/ssh.service.d \
&& printf '%s\n' \
'[Service]' \
'# Reset main unit ExecStartPre list: Debian ships `sshd -t` as' \
'# the first ExecStartPre, which fails on missing host keys and' \
'# short-circuits the service before ours gets a chance to run.' \
'ExecStartPre=' \
'ExecStartPre=/usr/bin/mkdir -p /run/sshd' \
'ExecStartPre=/usr/bin/ssh-keygen -A' \
'ExecStartPre=/usr/sbin/sshd -t' \
'StandardOutput=journal+console' \
'StandardError=journal+console' \
> /etc/systemd/system/ssh.service.d/banger.conf \
&& rm -f /etc/systemd/system/ssh.service.d/regen-host-keys.conf \
&& printf 'd /run/sshd 0755 root root -\n' > /usr/lib/tmpfiles.d/sshd.conf
# No CMD / ENTRYPOINT: banger boots this via systemd as PID 1 after
# first-boot, not via `docker run`.