installtest/src/installer_ssh.rs
//! **The `gunnar-installer` medium, installed, booted, and REACHED over SSH —
//! the mechanism, with exactly one writer.**
//!
//! `tests/installer_ssh_drive.rs` is the PROOF (it asserts, it emits rollup
//! rows, it fails a build). `bench/examples/nornir-bench.rs`'s
//! `gunnar.installer_ssh` arm is the MEASUREMENT (it reports numbers and has no
//! opinion about them). They are the same six operations — read the
//! declaration, pack the payload, compose the medium, install to a blank disk,
//! boot the installed disk with the medium detached, authenticate — and the
//! whole reason this module exists is that they must not be two copies of them.
//!
//! The precedent is this crate's own: `iso.rs`, `kvm.rs` and `container.rs` each
//! hold a `run(...)` the install tests and the `gunnar.install_*` bench arms
//! both call. This is the fourth shape, the one none of those three cover —
//! `gunnar-appliance-iso` is `kind = "iso"`, a LIVE medium whose `/data` is RAM
//! and whose identity dies at reboot; `gunnar-installer` is `kind = "installer"`,
//! it writes a GPT and `/data` is a partition. `installtest/src/iso.rs:84`
//! pins `.iso-out/gunnar-appliance-iso.iso`, so `gunnar.install_iso` has never
//! measured the installed shape and no arm in the suite did.
//!
//! Nothing here asserts. `Verdict::assert_reached` is the one exception and it
//! is called by the test, never by the arm: a benchmark that panics on a
//! product defect reports nothing at all, and a red row is more use than a
//! missing one.
use std::io::{Read, Write};
use std::net::TcpStream;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::time::{Duration, Instant};
use korp_installer::isoproof::{self, IsoInstallProof, KvmPreflight};
use korp_installer::kvmproof;
use nornir_build_thing::variant::BuildVariantSet;
/// The proof name every skip and row is spoken under.
pub const PROOF: &str = "gunnar_installer_ssh";
/// The declared installer cell — `kind = "installer"`, the one with a
/// `target_disk_gib`. NOT `gunnar-appliance-iso`, which is `kind = "iso"`, a
/// LIVE medium whose `/data` is RAM and whose identity dies at reboot.
pub const CELL: &str = "gunnar-installer";
/// The appliance variant the cell lays down.
pub const VARIANT: &str = "gunnar-server";
/// The product, as the rollup spells it.
pub const PRODUCT: &str = "gunnar";
/// The banner line that says the SSH listener is BOUND. `serve.rs:1413` writes
/// `ssh <addr>` and then, with the control plane declared off,
/// `grpc disabled` — so this marker is strictly after the listener exists.
pub const SERVING_MARKER: &str = "grpc disabled";
/// The identification string gunnar's own SSH server sends — `gunnar-ssh`
/// `config.rs:155`, `format!("SSH-2.0-gunnar_{}", env!("CARGO_PKG_VERSION"))`.
pub const SSH_IDENT_PREFIX: &str = "SSH-2.0-gunnar";
/// The one sentence `DispatchError::client_message` produces for a repository
/// that is absent OR forbidden. Reaching it means a channel was opened, which
/// means userauth passed.
pub const SERVER_VOICE: &str = "repository not found, or you do not have access";
/// What a rejected key gets from stock `ssh`, one layer earlier.
pub const USERAUTH_REFUSED: &str = "Permission denied (publickey)";
/// A repository that does not exist, and cannot *by reading*: a freshly
/// installed P3 is formatted empty. Asking for it with `git-upload-pack` is
/// how the server is made to speak — reads never create, not even for the
/// admin, so this stays absent for the whole of `boot_and_reach`.
pub const ABSENT_REPO: &str = "demo/hello";
/// The repository the FIRST-PUSH door creates: the admin's first `git push`
/// on an installed appliance names it, and that push — not a verb, not a
/// control plane — brings it into existence (`gunnar_ssh::Control::
/// create_for_push`). `boot_and_first_push` is the KVM proof.
pub const DOOR_REPO: &str = "nordisk/first";
/// Scratch on DISK — sparse 64 GiB images have no business in `/tmp` (RAM).
pub const SCRATCH: &str = "~/scratch/gunnar-installer-ssh";
/// Where the declared variant's artifact lives.
pub fn build_target_dir() -> PathBuf {
std::env::var_os("GUNNAR_EXE_TARGET_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| crate::expand_home("~/scratch/cargo/gunnar"))
}
/// One row in the rollup, through the harness's own emitter.
pub fn row(check: &str, pass: bool, detail: &str) {
isoproof::emit_kvm_row(PRODUCT, check, pass, detail);
}
/// A named, LOUD skip: it prints AND it records a non-pass row. A skip that
/// only prints leaves the rollup cell as empty as no run at all, and an empty
/// cell reads like "not attempted" rather than "attempted and not proven".
pub fn skip(check: &str, msg: &str) {
eprintln!("SKIP {PROOF}/{check}: {msg}");
row(check, false, msg);
}
// ── the machinery ───────────────────────────────────────────────────────────
/// Everything both legs share: the preflight, the declaration, the packed
/// payload and the admin keypair the medium will be composed with.
pub struct Context {
pub pf: KvmPreflight,
pub cell: nornir_build_thing::installer::InstallerBuild,
pub work: PathBuf,
/// The payload the composer streams onto P2.
pub app: PathBuf,
/// The PRIVATE half of the key that will be staged as `/data/admin.pub`.
pub admin_key: PathBuf,
/// A key the medium has never seen — rung 4.
pub stranger_key: PathBuf,
/// `korp.entry=` from the declaration.
pub entry: String,
}
/// One installed disk.
pub struct Guest {
pub name: String,
pub disk: PathBuf,
pub workdir: PathBuf,
/// The cmdline read back off the installed ESP.
pub cmdline: String,
}
/// A composed medium.
pub struct Medium {
pub iso: PathBuf,
pub bytes: u64,
pub compose_ms: u128,
}
impl Context {
pub fn open(leg: &str) -> Option<Self> {
let repo = crate::workspace_root();
// `KORP_TEST_KERNEL` is the harness's env; `KORP_KERNEL` is what the
// forge's ISO test uses on this box. Accept either.
if std::env::var_os(isoproof::TEST_KERNEL_ENV).is_none() {
if let Some(k) = std::env::var_os("KORP_KERNEL") {
std::env::set_var(isoproof::TEST_KERNEL_ENV, k);
}
}
let Some(pf) = KvmPreflight::detect(PROOF) else {
skip(
"host_capable",
"qemu / OVMF / KORP_TEST_KERNEL missing — see stderr",
);
return None;
};
for tool in ["ssh", "ssh-keygen"] {
if which(tool).is_none() {
eprintln!("SKIP {PROOF}: no {tool} on PATH — the client half is stock OpenSSH");
skip("host_capable", &format!("no {tool} on PATH"));
return None;
}
}
// ── the DECLARATION, as the one writer ──
let set = BuildVariantSet::discover(&repo)
.expect("read .nornir/build-variants.toml")
.unwrap_or_else(|| panic!("gunnar declares a build space at {}", repo.display()));
let cell = set
.installers
.iter()
.find(|i| i.name == CELL)
.unwrap_or_else(|| panic!("gunnar declares a `[[build.installer]]` named {CELL}"))
.clone();
// The admin channel is the DECLARATION's, never this drive's. A proof
// that typed `--admin-key` itself would prove nothing about the medium.
let admin_spec = cell
.params
.iter()
.find(|p| p.name == "admin_key")
.expect("the cell declares an `admin_key` parameter (a8ba0665)");
assert!(
admin_spec.required,
"the bootstrap admin must be `required = true` — an appliance that ships without one \
authorises nobody, for ever, and `--no-grpc` leaves no way to fix it in the field"
);
assert_eq!(
admin_spec.staged_file, "admin.pub",
"the admin key lands as a STAGED FILE: an OpenSSH key line contains spaces and the \
`korp.env.*` channel is a whitespace-split kernel command line with no escapes"
);
let params = cell.effective_boot_params();
let argv = params
.iter()
.find_map(|p| p.strip_prefix("korp.args="))
.expect("the cell declares the app's argv tail as `korp.args=`");
assert!(
argv.contains("--admin-key") && argv.contains(&format!("@/{}", "data/admin.pub")),
"the declared argv must READ the staged file — the staging and the flag are two \
halves of one channel and either alone is inert: korp.args={argv}"
);
let entry = params
.iter()
.find_map(|p| p.strip_prefix("korp.entry="))
.expect("the cell names its product with korp.entry=")
.to_string();
// ── the artifact the cell lays down ──
let variant = set.get(VARIANT).expect("the chained variant is declared");
let mut bin = build_target_dir();
if let Some(t) = variant.target.as_deref() {
bin = bin.join(t);
}
let bin = bin
.join(variant.profile.label())
.join(variant.bin.as_deref().expect("the variant names a bin"));
if !bin.is_file() {
eprintln!(
"SKIP {PROOF}: the declared artifact is not at {} — run `{}` with \
CARGO_TARGET_DIR={} first",
bin.display(),
variant.command_line(),
build_target_dir().display()
);
skip(
"app_payload_packed",
&format!("no artifact at {}", bin.display()),
);
return None;
}
// ── the SUPPLIED admin key, read BEFORE the scratch tree is cleared ──
//
// MEASURED here 2026-08-26: the obvious ordering — clear the tree, then
// resolve the key — deleted the key it was about to be handed, because
// the natural place to keep the previous run's pair is inside that very
// tree (`GUNNAR_ADMIN_KEY=.../one/admin_ed25519`). The bytes are taken
// first and re-planted below, so where the operator keeps the pair is
// none of this function's business.
let supplied = match std::env::var_os("GUNNAR_ADMIN_KEY").map(PathBuf::from) {
Some(k) => {
let pubh = k.with_extension("pub");
let (Ok(sec), Ok(pubk)) = (std::fs::read(&k), std::fs::read(&pubh)) else {
panic!(
"GUNNAR_ADMIN_KEY={} needs BOTH halves readable: the private key and \
{} beside it",
k.display(),
pubh.display()
)
};
eprintln!(
"ADMIN KEY supplied: {} ({} B public half)",
k.display(),
pubk.len()
);
Some((sec, pubk))
}
None => None,
};
let work = crate::expand_home(SCRATCH).join(leg);
let _ = std::fs::remove_dir_all(&work);
std::fs::create_dir_all(&work).expect("scratch on disk");
// ── the payload, through the ONE codec ──
let raw = std::fs::read(&bin).expect("read the declared appliance binary");
let raw_len = raw.len();
let packed = korp_installer::payload::pack_znippy(&entry, raw, 1)
.expect("pack the app .znippy under the declared entry");
let app = work.join("gunnar-app.znippy");
std::fs::write(&app, &packed).expect("stage the app payload");
eprintln!(
"PACKED: {raw_len} → {} bytes, entry `{entry}`",
packed.len()
);
// ── the two keys ──
//
// `crate::keygen` is the ONE key minter in this crate and
// its header records why `ssh-keygen` is admissible here: gunnar's
// zero-shell rule is about what the SERVER does. A second spelling of
// key generation in this file would be the twin that rule is kept
// single to avoid.
// REUSING a medium means reusing the key it was composed WITH. A fresh
// mint against an old medium would be refused at userauth exactly as a
// stranger's key is, and this drive would report the product defect it
// is looking for while the defect was in the harness. So the reuse path
// REQUIRES the key and says so; it does not guess.
let admin_key = work.join("admin_ed25519");
match supplied {
Some((sec, pubk)) => {
std::fs::write(&admin_key, sec).expect("re-plant the supplied private half");
std::fs::write(admin_key.with_extension("pub"), pubk)
.expect("re-plant the supplied public half");
// `ssh` REFUSES a private key any group or other can read, and a
// copy lands 0644 under the default umask. A refusal here would
// look exactly like the appliance rejecting the key.
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&admin_key, std::fs::Permissions::from_mode(0o600))
.expect("0600 on the private half, or `ssh` refuses to use it");
}
None => {
assert!(
std::env::var_os("GUNNAR_INSTALLER_ISO").is_none(),
"GUNNAR_INSTALLER_ISO reuses a medium that was composed with SOME admin key; \
set GUNNAR_ADMIN_KEY=<private key path> to the one it carries, or unset \
GUNNAR_INSTALLER_ISO and let this drive compose a matched pair"
);
crate::keygen(&admin_key).expect("mint the bootstrap admin key");
}
}
let stranger_key = work.join("stranger_ed25519");
crate::keygen(&stranger_key).expect("mint a key the medium never sees");
assert_ne!(
std::fs::read(admin_key.with_extension("pub")).expect("admin pub"),
std::fs::read(stranger_key.with_extension("pub")).expect("stranger pub"),
"the two keys must differ or rung 4 proves nothing"
);
Some(Context {
pf,
cell,
work,
app,
admin_key,
stranger_key,
entry,
})
}
/// Compose the medium through the SAME door `dwarves buildthing --installer`
/// drives, with the admin key supplied as the declared parameter.
pub fn compose(&self) -> Option<Medium> {
let repo = crate::workspace_root();
if let Some(p) = std::env::var_os("GUNNAR_INSTALLER_ISO").map(PathBuf::from) {
if p.is_file() {
eprintln!(
"REUSING a composed medium: {} — composition is NOT exercised this run",
p.display()
);
row(
"installer_iso_composed",
false,
&format!("SKIPPED: reused {}", p.display()),
);
let bytes = std::fs::metadata(&p).expect("stat the reused medium").len();
return Some(Medium {
iso: p,
bytes,
compose_ms: 0,
});
}
}
// The root of trust is armed with the published DEVELOPMENT key, so the
// installed ESP carries a real `korp/app.sig` + `korp/root.pub`. With
// this UNSET the composition is an UNENROLLED medium and bakes
// `korp.require_signed=0` onto the installed cmdline — a start-app that
// execs whatever bytes are on P2. Arming it here is not decoration.
std::env::set_var(
korp_installer::rootkey::SIGNING_KEY_ENV,
korp_installer::rootkey::DEVELOPMENT_SELECTOR,
);
let admin_pub = self.admin_key.with_extension("pub");
let params = vec![format!("admin_key=@{}", admin_pub.display())];
let t0 = Instant::now();
let iso = match nornir_build_thing::suite_cli::run_installer(
&repo,
PRODUCT,
Some(CELL),
&self.pf.kernel,
&self.app,
¶ms,
&korp_installer::isocompose::ForgeComposer,
) {
Ok(p) => p,
Err(e) => {
row("installer_iso_composed", false, &format!("{e:#}"));
panic!("composing {CELL} with the declared admin_key: {e:#}");
}
};
let compose_ms = t0.elapsed().as_millis();
let bytes = std::fs::metadata(&iso)
.unwrap_or_else(|e| {
panic!(
"the composer claimed success but produced no {}: {e}",
iso.display()
)
})
.len();
assert!(
bytes > 8 * 1024 * 1024,
"a medium carrying a kernel and a payload is not {bytes} bytes"
);
// The key is ON the volume, read with the crate's own ISO-9660 reader —
// no mount, no loop device, no root. A composed value nobody can read
// back off the medium is a claim, not a measurement.
let mut vol = korp_installer::iso9660::Iso9660::open(
std::fs::File::open(&iso).expect("open the composed medium"),
)
.expect("the composed medium is ISO-9660");
let on_iso = vol
.read_file("admin.pub")
.expect("the ISO volume carries admin.pub at its root (staged_file)");
let want = std::fs::read(&admin_pub).expect("read the admin public half");
assert_eq!(
String::from_utf8_lossy(&on_iso).trim(),
String::from_utf8_lossy(&want).trim(),
"the admin.pub ON THE MEDIUM is the key this drive composed with, byte for byte"
);
eprintln!(
"MEDIUM: {} — {bytes} bytes, admin.pub staged ({} B)",
iso.display(),
on_iso.len()
);
row(
"installer_iso_composed",
true,
&format!(
"{} ({bytes} B) in {compose_ms} ms, volume_id={}, admin.pub on the volume",
iso.display(),
self.cell.volume_id
),
);
Some(Medium {
iso,
bytes,
compose_ms,
})
}
/// LIFETIME 1 for one guest: a blank disk, the medium on VIRTIO (trap 1,
/// applied by the harness), an unattended install.
pub fn install_one(&self, medium: &Medium, idx: usize) -> Guest {
let name = format!("g{idx}");
let disk = self.work.join(format!("{name}.img"));
let disk_arg = disk.to_str().expect("a UTF-8 scratch path").to_string();
let workdir = self.work.join(format!("{name}-install"));
let admin_pub = std::fs::read(self.admin_key.with_extension("pub")).expect("admin pub");
let t0 = Instant::now();
let installed = IsoInstallProof {
product: PRODUCT,
check: "install_ok",
iso: &medium.iso,
disk: &disk,
disk_bytes: self.cell.target_disk_bytes(),
disk_args: self.cell.kvm_disk_args(&disk_arg),
workdir: &workdir,
marker: korp_installer::INSTALL_OK_MARKER,
timeout: Duration::from_secs(900),
expect_serial: &["INSTALLER: partition", "INSTALLER: esp"],
expect_cmdline: &[
"korp.entry=gunnar",
"korp.env.GUNNAR_APPLIANCE=1",
"korp.env.GUNNAR_DATA_ROOT=/data",
// The half of the channel that READS the staged file. Without
// it the key on P3 is a file nothing opens.
"--admin-key",
"@/data/admin.pub",
],
// ── WHY `admin.pub` IS NOT ASSERTED HERE, THOUGH IT IS READ BELOW ──
//
// MEASURED 2026-08-26, twice, one run each way: the identical
// install passed this byte-compare on the first run and FAILED it on
// the second (`gunnar: the installed data home carries admin.pub`,
// isoproof.rs:330) with a byte-identical install serial, including
// `INSTALLER: payload admin.pub materialized off the ISO volume (100
// bytes)`. The install is not flaky; the READBACK is, and it is the
// same defect the sibling drive names for a different file:
// `boot_iso_until` ends in `child.kill()` the instant INSTALL-OK
// lands, so P3's FAT directory is whatever QEMU had flushed at that
// microsecond. A small file written late is a coin toss.
//
// Making a coin toss a hard gate would mean this drive failed half
// the time for a reason that has nothing to do with the appliance —
// exactly the kind of green/red nobody can read. It is observed
// below as its own row instead, and the LOAD-BEARING proof of the
// same fact is strictly stronger and not racy: the booted appliance
// AUTHENTICATES that key. A file that had not landed could not
// authenticate anybody.
expect_data_files: &[],
detail: &format!(
"{name}: {} GiB target from {}",
self.cell.target_disk_gib,
medium.iso.display()
),
}
.run(&self.pf);
let install_ms = t0.elapsed().as_millis();
eprintln!(
"INSTALLED {name} in {install_ms} ms — cmdline: {}",
installed.cmdline.trim()
);
// The staged file on P3, OBSERVED. See the `expect_data_files` note
// above for why this is a row and not a gate.
match installed.data_file("admin.pub") {
Some(got) if got == admin_pub => {
eprintln!(
" P3 admin.pub: {} B, byte-identical to the composed key",
got.len()
)
}
Some(got) => eprintln!(
" P3 admin.pub: {} B and NOT the composed key ({} B) — that is a real \
disagreement, not the flush race",
got.len(),
admin_pub.len()
),
None => eprintln!(
" P3 admin.pub: not readable off the image — the flush race (see above); \
the SSH leg below is what settles whether it landed"
),
}
// The signing gate, read off the disk rather than believed. An UNSIGNED
// medium bakes `korp.require_signed=0`, and shipping one as if it were
// fine is the failure this line refuses to let pass silently.
assert!(
installed.cmdline.contains("korp.require_signed=1"),
"the installed cmdline must require a signed payload — an unsigned medium bakes \
`korp.require_signed=0` and the start-app execs whatever is on P2: {}",
installed.cmdline.trim()
);
Guest {
name,
disk,
workdir: self.work.join(format!("g{idx}-boot")),
cmdline: installed.cmdline,
}
}
/// The boot argv for LIFETIMES 2+3, with the medium **absent** (trap 2 —
/// it is not attached at all, not merely deprioritised) and the guest's
/// :2222 forwarded to a fresh host port. ONE writer, because the assert in
/// here is what keeps a careless edit from re-attaching the medium: trap 2
/// cost an earlier run 35 unattended reinstalls, each one reformatting the
/// data partition.
fn boot_args(&self, guest: &Guest) -> (Vec<String>, u16) {
let host_port = isoproof::free_port();
let disk_arg = guest
.disk
.to_str()
.expect("a UTF-8 scratch path")
.to_string();
let mut args = self.cell.kvm_disk_args(&disk_arg);
assert!(
!args.iter().any(|a| a.ends_with(".iso") || a == "-cdrom"),
"the boot leg must carry NO medium: {args:?}"
);
isoproof::attach_slirp_nic(&mut args, Some((host_port, 2222)));
std::fs::create_dir_all(&guest.workdir).expect("the boot workdir");
(args, host_port)
}
/// LIFETIMES 2+3 in one: boot the installed disk with the medium absent,
/// hold the guest RUNNING past the serving marker, and reach it.
pub fn boot_and_reach(&self, guest: &Guest, timeout: Duration) -> Verdict {
let (args, host_port) = self.boot_args(guest);
let state: Mutex<Verdict> = Mutex::new(Verdict::default());
let done = AtomicBool::new(false);
let t0 = Instant::now();
// `boot_disk_until_verdict` is `kvmproof`'s own held-open door: it polls
// the serial log to `marker`, then keeps the guest alive while `hold`
// forms the verdict. Everything about the boot — the qemu argv, the
// OVMF staging, the NUL-stripped poll — is the harness's; only the
// verdict is this drive's.
let serial = kvmproof::boot_disk_until_verdict(
&self.pf.qemu,
&self.pf.ovmf_code,
&self.pf.ovmf_vars,
&args,
&guest.workdir,
SERVING_MARKER,
timeout,
|| {
if done.load(Ordering::SeqCst) {
return true;
}
let mut v = state.lock().expect("verdict");
if self.probe(host_port, &mut v, t0) {
done.store(true, Ordering::SeqCst);
return true;
}
false
},
Duration::from_secs(120),
);
let mut v = state.into_inner().expect("verdict");
v.serial_bytes = serial.len();
v.panicked = serial.contains("Kernel panic");
v.reached_marker = serial.contains(SERVING_MARKER);
v.serial = serial;
v.port = host_port;
v.cmdline = guest.cmdline.clone();
v
}
/// One probe attempt. Returns true when the verdict is FORMED (either way):
/// the hold ends on a formed verdict, not on a good one.
fn probe(&self, port: u16, v: &mut Verdict, t0: Instant) -> bool {
// Rung 1: a bound listener speaking gunnar's own identification string.
if v.banner.is_none() {
match read_ident(port) {
Some(b) => {
v.banner_ms = Some(t0.elapsed().as_millis());
if !b.starts_with(SSH_IDENT_PREFIX) {
v.note = Some(format!("the listener is not gunnar's: {b:?}"));
return true;
}
v.banner = Some(b);
}
None => return false,
}
}
// Rung 2+3: the STAGED admin key, and the server's own voice.
let known = self.work.join(format!("known_hosts.{port}"));
let _ = std::fs::remove_file(&known);
let admin = ssh_verb(&self.admin_key, &known, port, ABSENT_REPO);
// Rung 4: a key the medium never saw, against the SAME pinned host key.
let stranger = ssh_verb(&self.stranger_key, &known, port, ABSENT_REPO);
v.host_key = read_pinned_host_key(&known);
v.admin_said = admin.clone();
v.stranger_said = stranger.clone();
v.authenticated = admin.contains(SERVER_VOICE) && !admin.contains(USERAUTH_REFUSED);
v.stranger_refused = stranger.contains(USERAUTH_REFUSED);
if v.authenticated {
v.auth_ms = Some(t0.elapsed().as_millis());
}
// A transient "Connection refused" while the listener finishes binding
// is not a verdict. Only a real answer from either side ends the hold.
v.authenticated || admin.contains(USERAUTH_REFUSED) || admin.contains(SERVER_VOICE)
}
/// **The FIRST-PUSH door, on the installed appliance itself.**
///
/// `boot_and_reach` proves the admin key opens a session and is then told
/// "repository not found" — the brick, measured 2026-08-25. This leg
/// proves the unbricking: with the guest held open, a real `git push`
/// (stock git, the STAGED admin key) to [`DOOR_REPO`] — a name no control
/// plane ever created — succeeds, the ref is listed back, a second push
/// goes through the ordinary path, and the stranger's key still dies at
/// userauth. Reads stay barren: the same admin's `git-upload-pack` for
/// [`ABSENT_REPO`] still gets the fused refusal, in the same session
/// window, so the door is the push and only the push.
///
/// Stock `git` on the host is admissible for the same reason stock
/// OpenSSH and `ssh-keygen` are (see `crate::keygen`): gunnar's zero-shell
/// rule is about what the SERVER executes, and the only honest client for
/// a git server is git.
pub fn boot_and_first_push(&self, guest: &Guest, timeout: Duration) -> PushVerdict {
assert!(
which("git").is_some(),
"the first-push leg drives stock `git`; none is on PATH"
);
// The working tree the admin pushes, prepared before the guest boots.
// sha256, because the door creates the repository with the server
// default and a sha1 tree would be refused by git itself.
let tree = self.work.join(format!("{}-push-tree", guest.name));
std::fs::create_dir_all(&tree).expect("the push working tree");
let content = "the first bytes pushed onto this appliance\n";
git_in(
&tree,
&[
"init",
"--quiet",
"--object-format=sha256",
"--initial-branch=main",
".",
],
);
std::fs::write(tree.join("hello.txt"), content).expect("stage the file");
git_in(&tree, &["add", "hello.txt"]);
git_in(&tree, &["commit", "--quiet", "-m", "the first commit"]);
let (args, host_port) = self.boot_args(guest);
let state: Mutex<PushVerdict> = Mutex::new(PushVerdict::default());
let done = AtomicBool::new(false);
let serial = kvmproof::boot_disk_until_verdict(
&self.pf.qemu,
&self.pf.ovmf_code,
&self.pf.ovmf_vars,
&args,
&guest.workdir,
SERVING_MARKER,
timeout,
|| {
if done.load(Ordering::SeqCst) {
return true;
}
let mut v = state.lock().expect("verdict");
if self.push_probe(host_port, &tree, &mut v) {
done.store(true, Ordering::SeqCst);
return true;
}
false
},
Duration::from_secs(120),
);
let mut v = state.into_inner().expect("verdict");
v.serial_bytes = serial.len();
v.panicked = serial.contains("Kernel panic");
v.reached_marker = serial.contains(SERVING_MARKER);
v.serial = serial;
v.port = host_port;
v
}
/// One attempt at the whole push ladder. Returns true when the verdict is
/// FORMED, either way. Everything after the first real server answer runs
/// in ONE pass: once the push lands, the repository exists, so this is not
/// re-enterable past that point — which is why a transient "Connection
/// refused" must return `false` BEFORE anything is created.
fn push_probe(&self, port: u16, tree: &Path, v: &mut PushVerdict) -> bool {
if v.banner.is_none() {
match read_ident(port) {
Some(b) => {
if !b.starts_with(SSH_IDENT_PREFIX) {
v.note = Some(format!("the listener is not gunnar's: {b:?}"));
return true;
}
v.banner = Some(b);
}
None => return false,
}
}
let known = self.work.join(format!("known_hosts.push.{port}"));
let _ = std::fs::remove_file(&known);
// Rung A: a READ of an absent repository, with the admin key, still
// gets the fused refusal — the door must not have turned reads into
// creation. This also settles that the listener is really up; only a
// real answer lets the ladder continue.
let read = ssh_verb(&self.admin_key, &known, port, ABSENT_REPO);
if !(read.contains(SERVER_VOICE) || read.contains(USERAUTH_REFUSED)) {
return false; // still binding; retry
}
v.read_said = read.clone();
v.read_still_refused = read.contains(SERVER_VOICE) && !read.contains(USERAUTH_REFUSED);
if !v.read_still_refused {
return true; // formed: the admin key does not even authenticate
}
v.host_key = read_pinned_host_key(&known);
let url = format!("ssh://git@127.0.0.1:{port}/{DOOR_REPO}.git");
let admin_ssh = git_ssh_command(&self.admin_key, &known);
// Rung B: THE FIRST PUSH — to a repository nothing ever created.
let (ok, said) = git_ssh(tree, &admin_ssh, &["push", &url, "HEAD:refs/heads/main"]);
v.first_push_ok = ok;
v.first_push_said = said;
if !v.first_push_ok {
return true; // formed: the door did not open
}
// Rung C: the ref is really there — listed back off the appliance.
let (ok, said) = git_ssh(tree, &admin_ssh, &["ls-remote", &url, "refs/heads/main"]);
v.listed = ok && said.contains("refs/heads/main");
v.listed_said = said;
// Rung D: the SECOND push goes through the ordinary resolve.
std::fs::write(tree.join("hello.txt"), "the second commit\n").expect("edit");
git_in(tree, &["commit", "--quiet", "-am", "two"]);
let (ok, said) = git_ssh(tree, &admin_ssh, &["push", &url, "HEAD:refs/heads/main"]);
v.second_push_ok = ok;
v.second_push_said = said;
// Rung E: the stranger still dies at userauth, one layer before any
// of this exists.
let stranger_ssh = git_ssh_command(&self.stranger_key, &known);
let (_, said) = git_ssh(tree, &stranger_ssh, &["push", &url, "HEAD:refs/heads/main"]);
v.stranger_refused = said.contains(USERAUTH_REFUSED);
v.stranger_said = said;
true
}
}
/// What one guest's boot proved.
#[derive(Default)]
pub struct Verdict {
pub port: u16,
pub banner: Option<String>,
pub banner_ms: Option<u128>,
pub auth_ms: Option<u128>,
/// The SSH host key stock `ssh` pinned on the first connection — WHICH
/// server answered. The fleet leg counts these.
pub host_key: Option<String>,
pub admin_said: String,
pub stranger_said: String,
pub authenticated: bool,
pub stranger_refused: bool,
pub reached_marker: bool,
pub panicked: bool,
pub serial: String,
pub serial_bytes: usize,
pub cmdline: String,
pub note: Option<String>,
}
impl Verdict {
pub fn summary(&self) -> String {
format!(
"port {} banner={:?} auth={} stranger_refused={} host_key={} marker={} panic={} \
({} B serial)",
self.port,
self.banner.as_deref().unwrap_or("—"),
self.authenticated,
self.stranger_refused,
self.host_key
.as_deref()
.map(|k| &k[..k.len().min(24)])
.unwrap_or("—"),
self.reached_marker,
self.panicked,
self.serial_bytes
)
}
pub fn bench_line(&self, m: &Medium, g: &Guest) -> String {
format!(
"compose {} ms, medium {} B, boot→banner {} ms, boot→authenticated {} ms, cmdline {:?}, \
disk {}",
m.compose_ms,
m.bytes,
self.banner_ms.unwrap_or_default(),
self.auth_ms.unwrap_or_default(),
self.cmdline.trim(),
g.disk.display()
)
}
/// **The exchange, verbatim, on the PASSING path too.**
///
/// A proof whose evidence is only visible when it fails is a proof nobody can
/// quote. These four lines are the whole claim of this file and they cost
/// nothing to print, so a reader of the log can see WHO was let in and WHO
/// was not rather than taking two booleans on trust.
pub fn print_exchange(&self) {
eprintln!("── the SSH exchange on 127.0.0.1:{} ──", self.port);
eprintln!(" ident : {:?}", self.banner.as_deref().unwrap_or("—"));
eprintln!(
" hostkey: {}",
self.host_key.as_deref().unwrap_or("— (nothing pinned)")
);
eprintln!(
" ADMIN key (staged on P3 as /data/admin.pub):\n{}",
indent(&self.admin_said)
);
eprintln!(
" STRANGER key (the medium never saw it):\n{}",
indent(&self.stranger_said)
);
}
/// The whole ladder, asserted in the order a failure is most informative.
pub fn assert_reached(&self, name: &str) {
assert!(
self.reached_marker,
"{name}: the installed disk never reached {SERVING_MARKER:?} — it did not serve at \
all, so there was nothing to SSH into.\n{}",
tail(&self.serial, 40)
);
assert!(
!self.panicked,
"{name}: the boot panicked:\n{}",
tail(&self.serial, 30)
);
let banner = self.banner.as_deref().unwrap_or("");
assert!(
banner.starts_with(SSH_IDENT_PREFIX),
"{name}: nothing on the forwarded port answered with gunnar's own identification \
string (got {banner:?}); note={:?}\n{}",
self.note,
tail(&self.serial, 40)
);
assert!(
self.authenticated,
"{name}: the STAGED admin key did not open a session. `ssh` said:\n{}\nserial:\n{}",
self.admin_said,
tail(&self.serial, 40)
);
assert!(
self.stranger_refused,
"{name}: a key the medium NEVER SAW was not refused at userauth — rung 3 means \
nothing without it, because `client_message` fuses absent and forbidden into one \
sentence. `ssh` said:\n{}",
self.stranger_said
);
assert!(
self.host_key.is_some(),
"{name}: stock `ssh` pinned no host key, so nothing here proves WHICH server answered"
);
}
}
// ── the client half: stock OpenSSH, one verb ────────────────────────────────
/// Ask the appliance for `repo` over its own SSH, with `key`, pinning the host
/// key into `known` on the first connection. Returns everything the client and
/// the server said, merged — the caller reads it for the two sentences that
/// live at two different protocol layers.
pub fn ssh_verb(key: &Path, known: &Path, port: u16, repo: &str) -> String {
// The one git verb the server admits, spelled the way stock `git` spells
// it. gunnar has no shell to ask for.
ssh_exec(key, known, port, &format!("git-upload-pack '/{repo}'"))
}
/// Run ONE exec line on the appliance's SSH with `key`, pinning the host key
/// into `known` — [`ssh_verb`]'s dial with the line chosen by the caller.
/// Returns `exit=<code>` and everything the client and the server said.
pub fn ssh_exec(key: &Path, known: &Path, port: u16, line: &str) -> String {
Command::new("ssh")
.args([
"-i",
&key.display().to_string(),
"-o",
"IdentitiesOnly=yes",
"-o",
"StrictHostKeyChecking=accept-new",
"-o",
&format!("UserKnownHostsFile={}", known.display()),
"-o",
"LogLevel=ERROR",
"-o",
"ConnectTimeout=10",
"-o",
"BatchMode=yes",
"-p",
&port.to_string(),
"git@127.0.0.1",
line,
])
.output()
.map(|o| {
format!(
"exit={:?}\n{}{}",
o.status.code(),
String::from_utf8_lossy(&o.stdout),
String::from_utf8_lossy(&o.stderr)
)
})
.unwrap_or_else(|e| format!("spawning ssh: {e}"))
}
/// **The sealed box's tunnel-address exec line** (T17): the verb
/// `gunnar-ssh` answers (`TUNNEL_ADDRESS_VERB`) and one CIDR.
pub fn tunnel_address_line(address: std::net::Ipv4Addr, prefix: u8) -> String {
format!("gunnar-tunnel-address {address}/{prefix}")
}
/// What [`ssh_exec`] of [`tunnel_address_line`] said, judged: `Ok` = the box
/// holds exactly `want` (`a.b.c.d/p`) and names its key; `Err` = anything else,
/// with the words. `None` = no answer yet (the listener was not up) — not a
/// verdict, the hold keeps polling.
pub fn judge_tunnel_answer(
said: &str,
want: &str,
) -> Option<Result<gunnar_proto::AssignTunnelAddressResponse, String>> {
let body: String = said
.lines()
.skip_while(|l| l.starts_with("exit="))
.collect::<Vec<_>>()
.join("\n");
if !said.contains("wg_public_key ") && !said.contains("error: ") {
return None;
}
Some(match gunnar_proto::parse_tunnel_answer(&body) {
Ok(a) if a.address == want => Ok(a),
Ok(a) => Err(format!("the box holds {:?}, not {want}", a.address)),
Err(why) => Err(why),
})
}
#[cfg(test)]
mod tunnel_answer_tests {
use super::*;
#[test]
fn the_drives_line_and_its_judge_agree_with_the_server_writer() {
assert_eq!(
tunnel_address_line("10.77.0.2".parse().unwrap(), 16),
"gunnar-tunnel-address 10.77.0.2/16"
);
let a = gunnar_proto::AssignTunnelAddressResponse {
wg_public_key: "6yUuA2NvUdRuoHwFmS9OD1V8pFpXmDgVQjJ+YEptGm4=".into(),
address: "10.77.0.2/16".into(),
changed: true,
tunnel_up: true,
detail: "wg0 is up".into(),
};
let said = format!("exit=Some(0)\n{}", gunnar_proto::render_tunnel_answer(&a));
let ok = judge_tunnel_answer(&said, "10.77.0.2/16")
.expect("an answer")
.expect("the address");
assert_eq!(ok.wg_public_key, a.wg_public_key);
assert!(
judge_tunnel_answer(&said, "10.77.0.3/16").unwrap().is_err(),
"another address is not a pass"
);
assert!(
judge_tunnel_answer("exit=Some(1)\nerror: never re-pointed\n", "10.77.0.2/16")
.unwrap()
.is_err()
);
assert!(
judge_tunnel_answer(
"exit=Some(255)\nssh: connect to host 127.0.0.1 port 2: Connection refused\n",
"x"
)
.is_none(),
"no listener yet is no verdict"
);
}
}
/// `GIT_SSH_COMMAND` for `key`, pinning into `known` — the SAME options
/// [`ssh_verb`] uses, spelled once each, so both halves of a proof talk to
/// the same pinned host key.
pub fn git_ssh_command(key: &Path, known: &Path) -> String {
format!(
"ssh -i {} -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \
-o UserKnownHostsFile={} -o LogLevel=ERROR -o ConnectTimeout=10 -o BatchMode=yes",
key.display(),
known.display()
)
}
/// Run stock `git` in `cwd` with a hermetic environment (no system or user
/// config, a fixed identity), and panic on failure — for the LOCAL plumbing
/// (`init`, `add`, `commit`) whose failure is a harness fault, not a verdict.
pub fn git_in(cwd: &Path, args: &[&str]) {
let (ok, said) = git_ssh(cwd, "ssh", args);
assert!(
ok,
"local `git {args:?}` failed in {}:\n{said}",
cwd.display()
);
}
/// Run stock `git` in `cwd` with `ssh` as its transport. Returns success and
/// the merged output — the REMOTE half, where failure IS the measurement.
pub fn git_ssh(cwd: &Path, ssh: &str, args: &[&str]) -> (bool, String) {
let out = Command::new("git")
.current_dir(cwd)
.env("GIT_SSH_COMMAND", ssh)
.env("GIT_TERMINAL_PROMPT", "0")
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("HOME", cwd)
.env("GIT_AUTHOR_NAME", "Installer Drive")
.env("GIT_AUTHOR_EMAIL", "drive@installtest.invalid")
.env("GIT_COMMITTER_NAME", "Installer Drive")
.env("GIT_COMMITTER_EMAIL", "drive@installtest.invalid")
.args(args)
.output();
match out {
Ok(o) => (
o.status.success(),
format!(
"exit={:?}\n{}{}",
o.status.code(),
String::from_utf8_lossy(&o.stdout),
String::from_utf8_lossy(&o.stderr)
),
),
Err(e) => (false, format!("spawning git: {e}")),
}
}
/// What one guest's boot proved about the FIRST-PUSH door.
#[derive(Default)]
pub struct PushVerdict {
pub port: u16,
pub banner: Option<String>,
pub host_key: Option<String>,
/// The admin's `git-upload-pack` for [`ABSENT_REPO`]: reads must still be
/// refused with the fused sentence, door or no door.
pub read_said: String,
pub read_still_refused: bool,
pub first_push_ok: bool,
pub first_push_said: String,
pub listed: bool,
pub listed_said: String,
pub second_push_ok: bool,
pub second_push_said: String,
pub stranger_refused: bool,
pub stranger_said: String,
pub reached_marker: bool,
pub panicked: bool,
pub serial: String,
pub serial_bytes: usize,
pub note: Option<String>,
}
impl PushVerdict {
/// The exchange, verbatim, on the passing path too — same rule as
/// [`Verdict::print_exchange`].
pub fn print_exchange(&self) {
eprintln!("── the first-push door on 127.0.0.1:{} ──", self.port);
eprintln!(" ident : {:?}", self.banner.as_deref().unwrap_or("—"));
eprintln!(
" hostkey : {}",
self.host_key.as_deref().unwrap_or("— (nothing pinned)")
);
eprintln!(
" READ of {ABSENT_REPO} (admin key):\n{}",
indent(&self.read_said)
);
eprintln!(
" FIRST PUSH to {DOOR_REPO}:\n{}",
indent(&self.first_push_said)
);
eprintln!(" ls-remote:\n{}", indent(&self.listed_said));
eprintln!(" SECOND PUSH:\n{}", indent(&self.second_push_said));
eprintln!(" STRANGER push:\n{}", indent(&self.stranger_said));
}
/// The whole ladder, asserted in the order a failure is most informative.
pub fn assert_created(&self, name: &str) {
assert!(
self.reached_marker,
"{name}: the installed disk never reached {SERVING_MARKER:?}\n{}",
tail(&self.serial, 40)
);
assert!(
!self.panicked,
"{name}: the boot panicked:\n{}",
tail(&self.serial, 30)
);
let banner = self.banner.as_deref().unwrap_or("");
assert!(
banner.starts_with(SSH_IDENT_PREFIX),
"{name}: nothing answered with gunnar's identification string (got {banner:?}); \
note={:?}",
self.note
);
assert!(
self.read_still_refused,
"{name}: the admin's READ of {ABSENT_REPO} was not the fused refusal — either the \
key no longer authenticates or reads have started creating. `ssh` said:\n{}",
self.read_said
);
assert!(
self.first_push_ok,
"{name}: the admin's FIRST PUSH to {DOOR_REPO} was refused — the appliance is still \
the brick measured 2026-08-25. git said:\n{}\nserial:\n{}",
self.first_push_said,
tail(&self.serial, 40)
);
assert!(
self.listed,
"{name}: the pushed ref did not list back — the push 'succeeded' into nothing:\n{}",
self.listed_said
);
assert!(
self.second_push_ok,
"{name}: the SECOND push failed — created, but not a working repository:\n{}",
self.second_push_said
);
assert!(
self.stranger_refused,
"{name}: the stranger's key was not refused at userauth — the door is only \
acceptable while it is invisible to every other key. git said:\n{}",
self.stranger_said
);
assert!(
self.host_key.is_some(),
"{name}: no host key pinned, so nothing says WHICH server answered"
);
}
}
/// The one host key `accept-new` pinned — WHICH server answered.
pub fn read_pinned_host_key(known: &Path) -> Option<String> {
let text = std::fs::read_to_string(known).ok()?;
let mut keys: Vec<String> = text
.lines()
.filter(|l| !l.trim().is_empty() && !l.starts_with('#'))
.filter_map(|l| l.split_whitespace().nth(2).map(str::to_string))
.collect();
keys.sort();
keys.dedup();
// Two answers on one pinned address is not a result, it is a substitution.
(keys.len() == 1).then(|| keys.remove(0))
}
/// Connect and read the SSH identification string. `None` while nothing answers.
pub fn read_ident(port: u16) -> Option<String> {
let mut s = TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_secs(2),
)
.ok()?;
s.set_read_timeout(Some(Duration::from_secs(3))).ok()?;
let mut buf = [0u8; 256];
let n = s.read(&mut buf).ok()?;
(n > 0).then(|| {
// Say something back so the server's own logs record a peer that spoke
// rather than a half-open probe it has to time out.
let _ = s.write_all(b"SSH-2.0-probe\r\n");
String::from_utf8_lossy(&buf[..n]).trim().to_string()
})
}
fn which(bin: &str) -> Option<PathBuf> {
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|d| d.join(bin))
.find(|p| p.is_file())
})
}
/// Four spaces in front of every line, so a multi-line capture reads as one
/// block instead of merging into the log around it.
fn indent(s: &str) -> String {
s.lines()
.map(|l| format!(" {l}"))
.collect::<Vec<_>>()
.join("\n")
}
fn tail(s: &str, n: usize) -> String {
let lines: Vec<&str> = s.lines().collect();
lines[lines.len().saturating_sub(n)..].join("\n")
}