From 4ea09b1ba09a3a171bbc81e3d61404c5c1a99d99 Mon Sep 17 00:00:00 2001 From: Elias Bakken Date: Mon, 10 Aug 2026 23:00:31 +0200 Subject: [PATCH] Give each Reflash board its own persistent SSH host key mkimage.sh installed openssh-server during the image build, which generates SSH host keys as a side effect - baked into that one build, they'd be identical across every image and every board flashed from it (#80). That's a real MITM/impersonation risk: the private key extracted from any single image works against every other board running the same build. Deleted the build-time keys and added ssh-keygen-boot.service, which restores (or generates once and saves) them against /mnt/usb instead - the one thing on this board that actually persists across reboots, since the root fs runs from initrd and doesn't. Ordered before both ssh.service and reflash.service so it has /mnt/usb to itself first; mount-unmount-usb has no locking of its own, so overlapping access from two processes at once isn't safe. On a freshly-flashed drive /mnt/usb's second partition doesn't exist yet - it's normally created by expand-usb, called from the Go server's own startup, which runs after this unit specifically so it doesn't race the server for the drive. ssh-keygen-boot calls expand-usb itself first; it's idempotent, so the Go server calling it again moments later is harmless. Live-tested on real hardware through the actual boot sequence (not just invoked directly): flashed a fresh image, confirmed keys are generated and saved to /mnt/usb/ssh_host_keys/ on first boot, then rebooted and confirmed the exact same key (verified by fingerprint) is restored rather than regenerated. Closes #80 --- bin/prod/ssh-keygen-boot | 40 ++++++++++++++++++++++++++++++++++++++++ mkimage.sh | 31 +++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100755 bin/prod/ssh-keygen-boot diff --git a/bin/prod/ssh-keygen-boot b/bin/prod/ssh-keygen-boot new file mode 100755 index 0000000..b1db572 --- /dev/null +++ b/bin/prod/ssh-keygen-boot @@ -0,0 +1,40 @@ +#!/bin/bash + +set -euo pipefail + +LOG_FILE="${LOG_FILE:-/var/log/reflash.log}" +KEY_DIR="${REFLASH_SSH_KEY_DIR:-/mnt/usb/ssh_host_keys}" + +info() { + echo "[info] $1" >> "$LOG_FILE" + echo "$1" +} + +# Build-time SSH host key generation (a side effect of installing +# openssh-server) would bake identical keys into every image and every board +# flashed from it. Keys are deleted at build time instead, and restored (or +# generated once and saved) here from the USB drive - the one thing on this +# board that actually persists across reboots, unlike the initrd root fs. + +# On a freshly-flashed drive, /mnt/usb's partition doesn't exist yet - it's +# normally created by expand-usb, which the Go server calls on its own +# startup. This unit runs before reflash.service specifically so it has +# /mnt/usb to itself first, which means that hasn't happened yet here. +# expand-usb is idempotent (a no-op once the partition already exists), so +# calling it again from reflash.service afterward is harmless. +expand-usb + +mount-unmount-usb mounted rw + +if [ -f "$KEY_DIR/ssh_host_rsa_key" ]; then + info "Restoring SSH host keys from USB storage" + cp "$KEY_DIR"/ssh_host_* /etc/ssh/ +else + info "No saved SSH host keys on USB storage - generating and saving new ones" + ssh-keygen -A + mkdir -p "$KEY_DIR" + cp /etc/ssh/ssh_host_* "$KEY_DIR/" +fi + +mount-unmount-usb unmounted +info "SSH host keys ready" diff --git a/mkimage.sh b/mkimage.sh index 1b8941f..7fc4620 100755 --- a/mkimage.sh +++ b/mkimage.sh @@ -180,6 +180,13 @@ RemainAfterExit=yes [Install] EOF +# Installing openssh-server generated SSH host keys as a side effect - baked +# into this one build, they'd be identical across every image and every +# board flashed from it (#80). Delete them; ssh-keygen-boot.service (set up +# below, outside the chroot) restores or generates them from USB storage +# on boot instead. +rm -f /etc/ssh/ssh_host_* + # Clean up rm -rf /usr/sbin/policy-rc.d rm ./*.deb @@ -253,6 +260,30 @@ cat < "${ROOTFSDIR}"/initrd/etc/systemd/resolved.conf.d/mdns.conf MulticastDNS=yes EOF +# This board's root fs runs from initrd and doesn't persist writes across +# reboots, so keys generated straight into /etc/ssh would be regenerated +# (and thus change) every boot. ssh-keygen-boot restores/saves them against +# /mnt/usb instead - the one thing that actually persists - so a given board +# keeps a stable identity while still not sharing a key with every other +# image/board (#80). Ordered before both ssh.service and reflash.service so +# it has /mnt/usb to itself - mount-unmount-usb has no locking of its own. +cat <"${ROOTFSDIR}"/initrd/etc/systemd/system/ssh-keygen-boot.service +[Unit] +Description=Restore or generate persistent SSH host keys from USB storage (see #80) +Before=ssh.service reflash.service +ConditionPathExists=!/etc/ssh/ssh_host_rsa_key + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/ssh-keygen-boot +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +EOF + +systemctl enable ssh-keygen-boot --root="${ROOTFSDIR}"/initrd + cat <"${ROOTFSDIR}"/initrd/etc/systemd/system/reflash.service [Unit] Description=Refactor flashing server