diff --git a/image-recipe/_archived/build-auto-installer-iso.sh b/image-recipe/_archived/build-auto-installer-iso.sh index d7a9a44c..0f75c922 100755 --- a/image-recipe/_archived/build-auto-installer-iso.sh +++ b/image-recipe/_archived/build-auto-installer-iso.sh @@ -578,6 +578,42 @@ RUN mkdir -p /var/log/journal && \ # Clean up RUN apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# ─── Strip fleet-shared identity material (audit finding F-03) ────────────── +# +# This image is exported to a tar and extracted VERBATIM onto every disk +# flashed from the resulting ISO, and the ISO is a published artefact. Anything +# identity-shaped left in here is therefore held by every node AND by every +# person who downloaded the ISO. +# +# Two things get baked without anyone asking for them: +# - Debian's openssh-server postinst generates /etc/ssh/ssh_host_* at package +# install time, i.e. inside this container build. +# - the "openssl req" layer above writes /etc/archipelago/ssl/archipelago.key. +# NOTE: this heredoc is UNQUOTED, so backticks here are command substitution +# and would run at build time. Never put backticks in these comments. +# Plus /etc/machine-id, which systemd populates during the build and which +# correlates every node flashed from one ISO. +# +# archipelago-first-boot-secrets.service recreates all of this per device on +# first boot. The point of removing it HERE is to change what a regeneration +# failure costs: with the material stripped, a failure degrades to "no key, the +# service refuses to start" instead of "fleet-shared key, silently" — which is +# the whole of F-03. That makes fail-closed structural rather than procedural. +# +# This must stay the LAST layer: anything that installs packages after it can +# reintroduce host keys. Keep the /etc/archipelago/ssl directory itself so the +# first-boot script's staging swap has somewhere to land. The "openssl req" +# layer above is deliberately left in place — it keeps proving openssl is +# present and keeps the SAN template next to the code that uses it; this layer +# is what makes the shipped output non-shared. +RUN rm -f /etc/ssh/ssh_host_* && \ + rm -f /etc/archipelago/ssl/archipelago.key /etc/archipelago/ssl/archipelago.crt && \ + mkdir -p /etc/archipelago/ssl && \ + : > /etc/machine-id && \ + { [ -L /var/lib/dbus/machine-id ] || rm -f /var/lib/dbus/machine-id ; } && \ + mkdir -p /opt/archipelago && \ + printf 'F-03 identity strip: this rootfs was built with the identity-strip layer.\nRemoved:\n /etc/ssh/ssh_host_*\n /etc/archipelago/ssl/archipelago.key\n /etc/archipelago/ssl/archipelago.crt\nTruncated:\n /etc/machine-id\nRecreated per device by archipelago-first-boot-secrets.service on first boot.\n' > /opt/archipelago/rootfs-identity-stripped DOCKERFILE # Copy nginx snippets for HTTPS (PWA, app proxies) @@ -1631,9 +1667,12 @@ cat > "$WORK_DIR/first-boot-secrets.sh" <<'SECRETSSCRIPT' # SCRIPT IS THE ONLY THING THAT CREATES THEM. That is deliberate. # # The operational consequence, in plain words: if regeneration fails every -# retry, this node has no SSH host key and no TLS key. sshd will not start and -# the nginx TLS listener will not start, so the node cannot be reached over -# the network and recovery requires the physical console. +# retry, this node has no SSH host key, so sshd will not start and the node +# cannot be reached over SSH — recovery requires the physical console. Nothing +# else on the install path creates host keys, so that outcome is certain. +# (TLS is softer: the installer writes a per-node fallback keypair with the +# generic CN=archipelago SAN, so the web UI usually still comes up. That +# fallback is per install, never image-wide, so it does not reopen F-03.) # # That cost was accepted on purpose. The behaviour it replaces was worse: log # a warning, set the completion marker anyway, and run forever on the SSH host @@ -3391,7 +3430,18 @@ RemainAfterExit=yes WantedBy=multi-user.target DIAGSVC -# Ensure SSL cert exists for nginx HTTPS (safety net if rootfs build missed it) +# Ensure SSL cert exists for nginx HTTPS. +# +# Since the F-03 identity-strip layer this is no longer a rarely-taken safety +# net — the rootfs ships with NO TLS keypair, so this branch fires on every +# install. That is fine and deliberate: the installer runs separately on each +# target machine, so the key it writes is per node, not image-wide. It uses the +# generic CN=archipelago SAN; archipelago-first-boot-secrets.service replaces it +# on first boot with one carrying this device's actual hostname. +# +# Consequence worth knowing: if first-boot regeneration fails, the web UI still +# has *a* per-node cert from here, whereas SSH has nothing at all (nothing +# recreates host keys at install time) and sshd will refuse to start. if [ ! -f /mnt/target/etc/archipelago/ssl/archipelago.crt ]; then mkdir -p /mnt/target/etc/archipelago/ssl chroot /mnt/target openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \