xtask/tests/installer_iso_token_refusal_kvm.rs
//! **The appliance that would have authenticated NOBODY now says so, on its own
//! console, at first boot.**
//!
//! The estate emits `StaticToken(subject: "console", token_file: Some(…))` into
//! every generated `holger.ron` and nothing anywhere writes that file (§4 "the
//! installer half", 2026-09-21). Before `auth::refuse_disabled_static_token` an
//! appliance installed from such a medium booted, minted its identity, printed
//! `Holger is running.` and served with its console door open to nobody — and
//! the ONLY way to find out was to try to log in.
//!
//! This is that medium, built and booted: a per-site ISO whose `holger.ron`
//! names a token file that nothing on the image creates, installed to a blank
//! disk and then booted from the disk alone with bare-metal semantics.
//!
//! ⚠ **The refusal is a kernel panic here, and that is the point of ?47.**
//! holger is PID 1 on the appliance, so ending the process ends the machine.
//! That is strictly better than the silence it replaces — a panic reaches the
//! console and `/boot/BOOTLOG.TXT`, "authenticates nobody" reaches nothing — but
//! it is why the plan recommends the appliance MINT its own token at first boot
//! the way it already mints its WireGuard identity. This test asserts the panic
//! as a CONSEQUENCE, so the day that ruling lands, this file is what goes red.
//!
//! Heavy and opt-in, the same rig as its siblings:
//!
//! ```text
//! KORP_TEST_KERNEL=/path/to/bzImage \
//! cargo test -p xtask --test installer_iso_token_refusal_kvm -- --ignored --nocapture
//! ```
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use korp_installer::isoproof::{FirstBootProof, IsoInstallProof, KvmPreflight, INSTALL_OK};
const PROOF: &str = "holger appliance refuses a bearer nobody gave it (iso-kvm)";
fn workspace_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.to_path_buf()
}
/// The fixture RON plus the ONE line the estate emits and the medium cannot
/// satisfy. Everything else is the sibling proof's config verbatim, so the only
/// difference between a booting appliance and a refusing one is the bearer.
const SITE_RON: &str = r#"(
repositories: [
(
ron_name: "bundles",
ron_repo_type: "raw",
ron_upstreams: [],
ron_in: None,
ron_out: Some((ron_storage_endpoint: "store", ron_exposed_endpoint: "doors")),
ron_data_dir: Some("/data/store/bundles"),
),
],
exposed_endpoints: [
(ron_name: "doors", ron_url: "0.0.0.0:50051", ron_http_url: Some("0.0.0.0:8443"), insecure: true),
],
storage_endpoints: [
(ron_name: "store", ron_storage_type: "znippy", ron_path: "/data/store/bundles"),
],
auth: Some((methods: [StaticToken(subject: "console", token_file: Some("/data/admin-token"))], require_auth_for_reads: false)),
)
"#;
#[test]
#[ignore = "requires: KVM plus OVMF firmware and a test kernel (KORP_TEST_KERNEL) — the ISO is composed, installed and booted under QEMU; no plain cargo test box has this rig"]
fn an_appliance_whose_token_file_was_never_written_refuses_by_name_at_first_boot() {
let root = workspace_root();
let Some(preflight) = KvmPreflight::detect(PROOF) else {
return;
};
let scratch = root.join("target/installer-iso-token-refusal");
let _ = std::fs::remove_dir_all(&scratch);
std::fs::create_dir_all(&scratch).unwrap();
let ron_path = scratch.join("site-bearer.ron");
std::fs::write(&ron_path, SITE_RON).unwrap();
// ── the medium, through THE VERB ──
let run = Command::new(env!("CARGO"))
.args([
"run",
"--quiet",
"--release",
"--manifest-path",
"xtask/Cargo.toml",
"--",
"installer-iso",
])
.args(["--param", "site=token-refusal-1"])
.args(["--param", "hub_conf=@fixture"])
.arg("--param")
.arg(format!("holger_ron=@{}", ron_path.display()))
.current_dir(&root)
.output()
.expect("spawn installer-iso");
assert!(
run.status.success(),
"installer-iso must master:\n--- stdout ---\n{}\n--- stderr ---\n{}",
String::from_utf8_lossy(&run.stdout),
String::from_utf8_lossy(&run.stderr)
);
let iso = root.join(".iso-out/holger-installer.iso");
assert!(iso.is_file(), "the verb produced {}", iso.display());
let set = holger_build_space::build_variant_set().unwrap();
let cell = set
.installers
.iter()
.find(|i| i.name == "holger-installer")
.unwrap();
let disk = scratch.join("target.raw");
// ── install: unattended, exactly as a site would run it ──
IsoInstallProof {
product: "holger",
check: "install_ok",
iso: &iso,
disk: &disk,
disk_bytes: cell.target_disk_bytes(),
disk_args: cell.kvm_disk_args(disk.to_str().unwrap()),
workdir: &scratch,
marker: INSTALL_OK,
timeout: Duration::from_secs(180),
expect_serial: &[],
expect_cmdline: &["korp.entry=holger", "korp.site=token-refusal-1"],
expect_data_files: &[],
detail: "token-refusal arm: per-site ISO whose holger.ron names a token file the medium does not carry",
}
.run(&preflight);
// ── first boot of the DISK: the appliance shape, no medium attached ──
//
// The marker is the REFUSAL. There is no serving marker to wait for and
// that is the whole finding: this appliance must not reach one.
let boot_dir = scratch.join("firstboot");
let booted = FirstBootProof {
product: "holger",
disk: &disk,
disk_args: cell.kvm_disk_args(&disk.display().to_string()),
workdir: &boot_dir,
marker: "REFUSING TO START",
hostfwd: None,
timeout: Duration::from_secs(180),
expect_serial: &[
// the declared argv reached the exec — this is holger talking, not
// the installer refusing on its behalf
r#"exec argv: ["holger", "start", "--config", "/data/holger.ron"]"#,
// the subject, so an operator knows WHICH method is dead
"console",
// the file to write, by path — the one thing the medium could not give it
"/data/admin-token",
// and why it is dead rather than merely absent
"authenticate NOBODY",
],
refuse_serial: &[
// ★ THE DEFECT, AS A REFUSAL. Before this lane the appliance printed
// exactly this line and served with a door open to nobody.
"Holger is running.",
],
}
.run(&preflight);
// The consequence, asserted rather than discovered: holger is PID 1 here, so
// a refusal ends the machine. See ?47 — the day the appliance mints its own
// token this arm is what goes red, and it should.
assert!(
booted.serial.contains("Kernel panic"),
"holger is PID 1 on the appliance, so its refusal must end the machine — \
if this no longer panics, the refusal is being swallowed somewhere:\n{}",
tail(&booted.serial)
);
}
/// The last of a serial capture, for a failure message that fits on a screen.
fn tail(s: &str) -> String {
let lines: Vec<&str> = s.lines().collect();
lines[lines.len().saturating_sub(40)..].join("\n")
}