Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

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")
}