0002 — BE re-unlock passphrase: dropbear in the BE, entered over SSH
Status: superseded by 0003 (2026-06-04)
Superseded. ADR 0003 adopts single-prompt key-handoff across kexec (SPEC §17.1 choice (b)). The dropbear-in-the-BE mechanism below is retained as the fallback path (
keyboot.handoff=0, or whenever no handoff key is present — recovery, opt-out), so the implementation here stays live; it is just no longer the primary boot path.
Context
After keyboot kexecs into the boot environment, the BE’s own initramfs
re-opens the LUKS-on-ZFS pool from scratch (SPEC §5 Stage 9 / §16.1 chose
option (a): the BE has its own independent keyfile/passphrase flow — the
keyfile/keys are deliberately NOT carried across the kexec boundary). That
flow needs the keyfile-container passphrase, and SPEC §15.1 left how the
BE obtains it open. The real-hardware bring-up (Hetzner, 2026-06-04) forced
the question:
keyboot be-unlockcallspassphrase::read()→ prompts on the console / reads stdin. On a headless box whose serial console is dead (common — seedocs/hetzner-deploy.md), that blocks forever: the BE initramfs has no SSH, so there’s no remote way to answer.- The only thing that made an unattended boot work was a baked
/etc/keyboot/test/passphrasein the BE initramfs. But the BE initramfs lives unencrypted on the ESP/pool metapath, so a baked passphrase defeats the encryption — fine for CI (--test-passphrase-file), unacceptable for production.
The keyboot unlock environment already solves the equivalent problem well (SPEC §5 Stages 2–4): bring up the network, run dropbear, and let the operator enter the passphrase over SSH (or the console) via a shared FIFO. The BE re-unlock should reuse that model.
Decision
The BE initramfs gets the same network + dropbear + askpass path the keyboot unlock environment uses. No baked passphrase in production.
Mechanism (mirrors the unlock env, kept minimal for the BE):
be-initbrings up the NIC (udhcpc, reusing the coldplug already added for AHCI) and starts dropbear with keyboot’s host keys + the operator’sauthorized_keys, forced-command = an askpass that writes the entered passphrase to a FIFO.be-initrunskeyboot be-unlock … < <FIFO>—be-unlock’s existingpassphrase::read()already reads a single line from stdin when there’s no TTY, so the SSH-entered passphrase flows straight in. The console prompt stays as a fallback (now usable thanks to the writable-console fix).--test-passphrase-filecontinues to short-circuit this for CI only (read_passphrase()already checks/etc/keyboot/test/passphrasefirst).
The operator therefore enters the passphrase twice per boot — once for the unlock env, once for the BE. Accepted for v1 (see Consequences).
Alternatives considered
- Baked passphrase (status quo / CI): simplest, but the BE initramfs is unencrypted, so it stores the passphrase in clear. Test-only. Rejected for production.
- Console-only prompt: no new code, but blocks on headless / dead-serial boxes and offers no remote operator path. Rejected as the only mechanism; retained as a fallback.
- Keyring handoff across kexec (SPEC §15.1 option b): keyboot stashes the keyfile/passphrase in a kernel-keyring slot the BE reads → a single prompt per boot. Attractive UX, but it carries key material across the kexec boundary, which §16.1’s v1 invariant explicitly avoids; needs a focused security review. Deferred — revisit as the single-prompt follow-up.
- TPM-sealing the keyfile: fully unattended, but requires a TPM + a measured-boot story keyboot doesn’t have yet. Deferred.
Consequences
- Two prompts per boot (unlock env + BE). The keyring-handoff alternative above is the documented path to one prompt later.
- BE initramfs grows by dropbear + busybox net tooling + host/authorized keys (~a few hundred KB). Acceptable.
- The BE initramfs now needs networking — reuses the unlock env’s udhcpc/ network grammar; static/VLAN/bond support tracks the unlock-env milestone.
install-osstops baking the passphrase for real installs;--authorized-keys(already added) supplies the BE’s operator key, and keyboot’s SSH host keys are reused. CI keeps--test-passphrase-file.- Supersedes the “baked test passphrase” stopgap recorded in
docs/hetzner-deploy.md; that doc’s open item is closed by this ADR.
Implementation note
Lands in tools/keyboot-install-os/alpine.sh (be-init + the keyboot.files
mkinitfs feature: dropbear, the askpass, host/authorized keys) and is covered
by extending ci/qemu/install-os-boot-test.sh to feed the passphrase over SSH
to the BE instead of relying on the baked file. Debian/Gentoo plugins need the
equivalent be-init wiring.