xtask/src/downstream.rs
//! **A downstream's medium and its VM** (lane OX-WGDS3, 2026-10-28): the two verbs the ONE-box
//! gate (`gate holger` in private-monetize-ops) lays its downstreams with, beside the laid
//! estate — Holger's own, as `installtest` is.
//!
//! ```text
//! cargo xtask downstream-media --name <n> --upstream <up> --upstream-key <hex> --via front|wireguard
//! --mirror <url> [--ca <pem>] [--tls-name <name>] [--authority <name>] --bearer-file <f> --out <iso>
//! cargo xtask downstream-vm --iso <iso> --work <dir> --door <host-port> [--mem-mb N] [--install-min N]
//! ```
//!
//! * **`downstream-media`** renders the downstream's declaration (holger-recipe
//! [`holger_recipe::downstream_ron`]) and composes `holger-downstream-installer` through the
//! Build Thing as `installtest` composes its medium (no pairing seed: no installer pairs a
//! downstream), the declaration on its release (`holger_ron`) and, `via: front`, the CA of
//! the upstream's front (`downstream_ca`). The medium, its record (`<iso>.medium.json`) and
//! the declaration (`<iso>.holger.ron`) are moved to `--out`, never left in `.iso-out` (a
//! medium with an operator's bearer is never uploaded). Prints `MEDIUM <iso>`.
//! * **`downstream-vm`** lays the medium in KVM on a disk under `--work` once (SeaBIOS, the
//! medium as an IDE cdrom, an 8 GiB boot disk and a 4 GiB data disk; `<work>/laid.ok` says
//! it was laid), then boots the laid disks with ONE user-mode NIC — not restricted, so the
//! guest reaches this host's loopback as `10.0.2.2` — forwarding `127.0.0.1:<door>` to the
//! guest's HTTP door (8443). Once the door answers it prints `UP 127.0.0.1:<door>` and the
//! VM stays up until the verb is stopped: by its process group (SIGTERM to `-<pgid>`
//! reaches QEMU too), a file `<work>/stop`, or its parent ending. Run again, it boots the
//! kept disks: a restart.
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::time::Duration;
use anyhow::{bail, Context, Result};
use holger_recipe::downstream_ron::{self, DownstreamRon, Via};
use holger_recipe::packaging as p;
use korp_installer::isoproof::serve::{Disk, InstallThenServe, MediumAttach, Probe};
use korp_installer::isoproof::SlirpNic;
use korp_installer::kvmproof::{self, Machine};
use nornir_build_thing::variant::ArtifactKind;
use crate::forge_door;
/// The medium both verbs are about.
pub const VARIANT: &str = p::DOWNSTREAM_INSTALLER;
/// The box's HTTP door (the declaration's `ron_http_url`).
pub const HTTP_DOOR: u16 = 8443;
/// The disks of a downstream VM: the boot disk the larger (korp lays the ESP on it).
pub const BOOT_DISK_GIB: u64 = 8;
pub const DATA_DISK_GIB: u64 = 4;
/// What says the disks under `--work` are laid.
pub const LAID: &str = "laid.ok";
/// What stops a running VM cleanly.
pub const STOP: &str = "stop";
/// `downstream-media`'s words.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MediaOpts {
pub name: String,
pub upstream: String,
pub upstream_key: String,
pub via: String,
pub mirror: String,
pub ca: Option<PathBuf>,
pub tls_name: Option<String>,
/// `--authority`: the calls' `:authority` when it is not the TLS name.
pub authority: Option<String>,
pub bearer_file: PathBuf,
pub out: PathBuf,
}
/// `downstream-vm`'s words.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VmOpts {
pub iso: PathBuf,
pub work: PathBuf,
pub door: u16,
pub mem_mb: u32,
pub install_min: u64,
}
/// Pure: `downstream-media`'s words; a stray or missing word is refused by name.
pub fn parse_media(args: &[String]) -> Result<MediaOpts> {
let (
mut name,
mut upstream,
mut key,
mut via,
mut mirror,
mut ca,
mut tls,
mut bearer,
mut out,
) = (None, None, None, None, None, None, None, None, None);
let mut authority = None;
let mut it = args.iter();
while let Some(a) = it.next() {
let mut val = |what: &str| {
it.next()
.cloned()
.with_context(|| format!("downstream-media: {a} takes {what}"))
};
match a.as_str() {
"--name" => name = Some(val("a name")?),
"--upstream" => upstream = Some(val("the upstream's sync name")?),
"--upstream-key" => key = Some(val("the upstream's sync key (hex)")?),
"--via" => via = Some(val("front or wireguard")?),
"--mirror" => mirror = Some(val("a URL")?),
"--ca" => ca = Some(PathBuf::from(val("a PEM file")?)),
"--tls-name" => tls = Some(val("a DNS name")?),
"--authority" => authority = Some(val("a DNS name")?),
"--bearer-file" => bearer = Some(PathBuf::from(val("a file")?)),
"--out" => out = Some(PathBuf::from(val("the medium's path")?)),
other => bail!("downstream-media: `{other}`: cargo xtask downstream-media --name <n> --upstream <up> --upstream-key <hex> --via front|wireguard --mirror <url> [--ca <pem>] [--tls-name <name>] [--authority <name>] --bearer-file <f> --out <iso>"),
}
}
let need = |v: Option<String>, w: &str| {
v.with_context(|| format!("downstream-media: {w} is required"))
};
let o = MediaOpts {
name: need(name, "--name")?,
upstream: need(upstream, "--upstream")?,
upstream_key: need(key, "--upstream-key")?,
via: need(via, "--via")?,
mirror: need(mirror, "--mirror")?,
ca,
tls_name: tls,
authority,
bearer_file: bearer.context("downstream-media: --bearer-file is required")?,
out: out.context("downstream-media: --out is required")?,
};
match (o.via.as_str(), o.ca.is_some(), o.tls_name.is_some() || o.authority.is_some()) {
("front", true, _) => {}
("front", false, _) => bail!("downstream-media: a `--via front` downstream verifies its upstream's front: --ca <pem> is required"),
("wireguard", false, false) => {}
("wireguard", _, _) => bail!("downstream-media: a `--via wireguard` downstream dials the front's downstream interface in the clear inside its tunnel: no --ca, no --tls-name, no --authority"),
(other, _, _) => bail!("downstream-media: --via {other}: `front` or `wireguard`"),
}
Ok(o)
}
/// Pure: `downstream-vm`'s words.
pub fn parse_vm(args: &[String]) -> Result<VmOpts> {
let (mut iso, mut work, mut door, mut mem, mut install) = (None, None, None, 1024u32, 20u64);
let mut it = args.iter();
while let Some(a) = it.next() {
let mut val = |what: &str| {
it.next()
.cloned()
.with_context(|| format!("downstream-vm: {a} takes {what}"))
};
match a.as_str() {
"--iso" => iso = Some(PathBuf::from(val("a medium")?)),
"--work" => work = Some(PathBuf::from(val("a directory")?)),
"--door" => door = Some(val("a port")?.parse::<u16>().context("downstream-vm: --door is a port")?),
"--mem-mb" => mem = val("MiB")?.parse().context("downstream-vm: --mem-mb is MiB")?,
"--install-min" => install = val("minutes")?.parse().context("downstream-vm: --install-min is minutes")?,
other => bail!("downstream-vm: `{other}`: cargo xtask downstream-vm --iso <iso> --work <dir> --door <host-port> [--mem-mb N] [--install-min N]"),
}
}
let door = door.context("downstream-vm: --door <host-port> is required")?;
if door == 0 {
bail!("downstream-vm: --door 0: name the host port the door is forwarded to");
}
Ok(VmOpts {
iso: iso.context("downstream-vm: --iso is required")?,
work: work.context("downstream-vm: --work is required")?,
door,
mem_mb: mem,
install_min: install,
})
}
/// Pure: the declaration of `o` with the bearer `bearer`.
pub fn declaration(o: &MediaOpts, bearer: &str) -> Result<DownstreamRon> {
let via = match o.via.as_str() {
"front" => Via::Front {
server_name: o.tls_name.clone(),
authority: o.authority.clone(),
},
_ => Via::Wireguard,
};
DownstreamRon::on_a_box(
&o.name,
&o.upstream,
&o.upstream_key,
via,
&o.mirror,
bearer.trim(),
)
.map_err(|e| anyhow::anyhow!(e))
}
/// Pure: the forge's parameters for the staged files (the declaration; the CA when there is one).
pub fn forge_params(ron: &Path, ca: Option<&Path>, korp_root: &Path) -> Vec<String> {
let mut out = vec![
format!("holger_ron=@{}", ron.display()),
format!("plugin_trust_root=@{}", korp_root.display()),
];
if let Some(ca) = ca {
out.push(format!("downstream_ca=@{}", ca.display()));
}
out
}
pub fn run_media() -> Result<()> {
let args: Vec<String> = std::env::args().skip(2).collect();
media(&parse_media(&args)?, &crate::workspace_root())
}
fn media(o: &MediaOpts, root: &Path) -> Result<()> {
let bearer = std::fs::read_to_string(&o.bearer_file)
.with_context(|| format!("downstream-media: the bearer {}", o.bearer_file.display()))?;
let d = declaration(o, &bearer)?;
let ron = downstream_ron::render(&d);
let out = if o.out.is_absolute() {
o.out.clone()
} else {
std::env::current_dir()?.join(&o.out)
};
let dir = out
.parent()
.context("downstream-media: --out names no directory")?
.to_path_buf();
let stage = dir.join(format!(
"{}.stage",
out.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default()
));
std::fs::create_dir_all(&stage)?;
let staged_ron = stage.join("holger.ron");
std::fs::write(&staged_ron, &ron)?;
let korp_root = stage.join(crate::estate_media::KORP_ROOT_FILE);
std::fs::write(&korp_root, holger_recipe::estate_ron::KORP_ROOT_PEM)?;
let ca = match &o.ca {
Some(c) => {
let pem = std::fs::read_to_string(c)
.with_context(|| format!("downstream-media: --ca {}", c.display()))?;
if !pem.contains("-----BEGIN CERTIFICATE-----") {
bail!(
"downstream-media: --ca {} holds no PEM certificate",
c.display()
);
}
let to = stage.join(p::DOWNSTREAM_CA_STAGED);
std::fs::write(&to, pem)?;
Some(to)
}
None => None,
};
println!(
"downstream-media: `{}` of `{}` via {} — mirror {}, {} → {}",
d.name,
d.upstream,
d.via.word(),
d.mirror,
VARIANT,
out.display()
);
let suite = forge_door::declared_medium(
VARIANT,
ArtifactKind::Installer,
"cargo xtask downstream-media",
)?;
let ask = forge_door::Ask {
variant: Some(VARIANT.to_string()),
params: forge_params(&staged_ron, ca.as_deref(), &korp_root),
builds: vec!["kernel".to_string()],
..Default::default()
};
let t0 = std::time::Instant::now();
let iso = forge_door::run("downstream-media", root, &suite, VARIANT, &ask)?;
// Out of `.iso-out`, with its record and declaration beside it.
let (rec, app) = crate::estate_media::live_companions(&iso);
let move_to = |from: &Path, to: &Path| -> Result<()> {
std::fs::rename(from, to)
.or_else(|_| {
std::fs::copy(from, to)
.map(|_| ())
.and_then(|_| std::fs::remove_file(from))
})
.with_context(|| format!("move {} to {}", from.display(), to.display()))
};
move_to(&iso, &out)?;
if iso.with_extension("medium.json").is_file() {
move_to(
&iso.with_extension("medium.json"),
&out.with_extension("medium.json"),
)?;
}
for f in [
rec,
app,
iso.with_extension("live-release.bundle"),
iso.with_extension("live-release.sig"),
] {
let _ = std::fs::remove_file(f);
}
std::fs::write(out.with_extension("holger.ron"), &ron)?;
let _ = std::fs::remove_dir_all(&stage);
println!("downstream-media: composed in {} s", t0.elapsed().as_secs());
println!("MEDIUM {}", out.display());
Ok(())
}
pub fn run_vm() -> Result<()> {
let args: Vec<String> = std::env::args().skip(2).collect();
vm(&parse_vm(&args)?)
}
/// The machine of a downstream VM.
pub fn proof(o: &VmOpts, qemu: PathBuf) -> InstallThenServe {
let mut machine = Machine::seabios();
machine.mem_mb = o.mem_mb;
machine.smp = 2;
InstallThenServe {
product: "holger-downstream".into(),
qemu,
machine,
iso: o.iso.clone(),
attach: MediumAttach::Cdrom,
disks: vec![
Disk {
path: o.work.join("sys.img"),
bytes: BOOT_DISK_GIB << 30,
},
Disk {
path: o.work.join("data.img"),
bytes: DATA_DISK_GIB << 30,
},
],
workdir: o.work.clone(),
install_budget: Duration::from_secs(o.install_min * 60),
// Up until it is stopped: a week is "until stopped" for a gate.
serve_budget: Duration::from_secs(7 * 24 * 3600),
nics: vec![SlirpNic {
net: None,
hostfwds: vec![(o.door, HTTP_DOOR)],
}],
nic_model: "virtio-net-pci".into(),
refuse_serial: Vec::new(),
probe_every: Duration::from_secs(2),
}
}
/// Whether the door at `127.0.0.1:<port>` answers HTTP at all.
fn door_answers(port: u16) -> bool {
use std::io::Read as _;
let Ok(mut s) = std::net::TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_secs(2),
) else {
return false;
};
let _ = s.set_read_timeout(Some(Duration::from_secs(5)));
if s.write_all(b"GET /-/sync/key HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n")
.is_err()
{
return false;
}
let mut head = [0u8; 12];
s.read_exact(&mut head).is_ok() && head.starts_with(b"HTTP/1.")
}
fn vm(o: &VmOpts) -> Result<()> {
let qemu = kvmproof::which("qemu-system-x86_64")
.context("[downstream-vm-no-qemu] no qemu-system-x86_64 on PATH")?;
if !Path::new("/dev/kvm").exists() {
bail!("[downstream-vm-no-kvm] /dev/kvm is absent: a downstream VM runs on a KVM host");
}
std::fs::create_dir_all(&o.work)?;
let _ = std::fs::remove_file(o.work.join(STOP));
let proof = proof(o, qemu);
if !o.work.join(LAID).is_file() {
println!(
"downstream-vm: laying {} on {} ({} min budget)",
o.iso.display(),
o.work.display(),
o.install_min
);
let leg = proof.install();
println!(
" {} install {} ms {}",
if leg.pass { "PASS" } else { "FAIL" },
leg.ms,
leg.detail
);
if !leg.pass {
if !leg.serial_tail.is_empty() {
println!(
" serial tail:\n{}",
leg.serial_tail
.lines()
.map(|l| format!(" | {l}"))
.collect::<Vec<_>>()
.join("\n")
);
}
bail!("[downstream-vm-install] {}", leg.detail);
}
std::fs::write(o.work.join(LAID), format!("{}\n", o.iso.display()))?;
} else {
println!(
"downstream-vm: the disks under {} are laid: booting them again",
o.work.display()
);
}
let parent = std::os::unix::process::parent_id();
let mut up = false;
let t0 = std::time::Instant::now();
let leg = proof.serve(&mut |_serial| {
if !up && door_answers(o.door) {
up = true;
println!("UP 127.0.0.1:{} ({} s)", o.door, t0.elapsed().as_secs());
let _ = std::io::stdout().flush();
}
if o.work.join(STOP).exists() {
return Probe::Done(Ok("stopped by its stop file".into()));
}
if std::os::unix::process::parent_id() != parent {
return Probe::Done(Ok("its parent ended".into()));
}
Probe::Wait
});
println!(
"downstream-vm: {} ({})",
if up { "stopped" } else { "never served" },
leg.detail
);
if !up {
if !leg.serial_tail.is_empty() {
println!(
" serial tail:\n{}",
leg.serial_tail
.lines()
.map(|l| format!(" | {l}"))
.collect::<Vec<_>>()
.join("\n")
);
}
bail!(
"[downstream-vm-serve] the door 127.0.0.1:{} never answered: {}",
o.door,
leg.detail
);
}
Ok(())
}
// ───────────────────────── the one-way appliance (lane HOL-CDS-5) ─────────────────────────
/// The one-way medium the proof composes.
pub const ONEWAY_VARIANT: &str = p::ONEWAY_INSTALLER;
/// The proof's security domain (`domain:`), checked on every answer.
pub const PROOF_DOMAIN: &str = "kvm-lab";
/// The size of the proof's USB stick.
pub const STICK_MIB: u64 = 64;
/// `downstream-oneway-proof`'s words.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProofOpts {
/// The fixture (`cargo test -p holger --test one_way_downstream -- --ignored
/// write_the_one_way_appliance_fixture`): the bundle, `trust-policy.ron`,
/// `upstream-root.pem`, `upstream-signer.pem`, `fixture.json`.
pub fixture: PathBuf,
pub work: PathBuf,
pub door: u16,
pub mem_mb: u32,
pub install_min: u64,
/// The medium's network (`holger_net`); `None`: the medium's default `eth0:dhcp`.
pub net: Option<String>,
/// A medium composed before (skip the compose).
pub iso: Option<PathBuf>,
/// Compose the medium and stop (no KVM: it needs no turn on the KVM lock).
pub compose_only: bool,
}
/// Pure: `downstream-oneway-proof`'s words; a stray word is refused by name.
pub fn parse_proof(args: &[String]) -> Result<ProofOpts> {
let mut o = ProofOpts {
fixture: PathBuf::new(),
work: PathBuf::new(),
door: 0,
mem_mb: 1024,
install_min: 20,
net: None,
iso: None,
compose_only: false,
};
let mut it = args.iter();
while let Some(a) = it.next() {
let mut v = || {
it.next()
.cloned()
.with_context(|| format!("[downstream-oneway-proof-args] {a} takes a value"))
};
match a.as_str() {
"--fixture" => o.fixture = PathBuf::from(v()?),
"--work" => o.work = PathBuf::from(v()?),
"--door" => o.door = v()?.parse().context("[downstream-oneway-proof-args] --door is a port")?,
"--mem-mb" => o.mem_mb = v()?.parse().context("[downstream-oneway-proof-args] --mem-mb is a number")?,
"--install-min" => o.install_min = v()?.parse().context("[downstream-oneway-proof-args] --install-min is a number")?,
"--net" => o.net = Some(v()?),
"--iso" => o.iso = Some(PathBuf::from(v()?)),
"--compose-only" => o.compose_only = true,
other => bail!("[downstream-oneway-proof-args] `{other}` is not a word of downstream-oneway-proof (--fixture --work --door [--mem-mb] [--install-min] [--net] [--iso])"),
}
}
if o.fixture.as_os_str().is_empty()
|| o.work.as_os_str().is_empty()
|| (o.door == 0 && !o.compose_only)
{
bail!("[downstream-oneway-proof-args] --fixture, --work and --door are required");
}
Ok(o)
}
/// The fixture, read and checked.
#[derive(Debug, Clone)]
pub struct Fixture {
pub dir: PathBuf,
pub bundle: String,
pub vendor: String,
pub product: String,
pub sequence: u64,
pub project: String,
pub wheel: String,
pub wheel_sha256: String,
}
pub fn read_fixture(dir: &Path) -> Result<Fixture> {
let j: serde_json::Value =
serde_json::from_slice(&std::fs::read(dir.join("fixture.json")).with_context(|| {
format!(
"[downstream-oneway-proof-fixture] {}/fixture.json",
dir.display()
)
})?)?;
let s = |k: &str| {
j[k].as_str().map(str::to_string).with_context(|| {
format!("[downstream-oneway-proof-fixture] fixture.json names no `{k}`")
})
};
let f = Fixture {
dir: dir.to_path_buf(),
bundle: s("bundle")?,
vendor: s("vendor")?,
product: s("product")?,
sequence: j["sequence"]
.as_u64()
.context("[downstream-oneway-proof-fixture] fixture.json names no `sequence`")?,
project: s("project")?,
wheel: s("wheel")?,
wheel_sha256: s("wheel_sha256")?,
};
for need in [
f.bundle.as_str(),
"trust-policy.ron",
"upstream-root.pem",
"upstream-signer.pem",
] {
if !dir.join(need).is_file() {
bail!(
"[downstream-oneway-proof-fixture] {} has no {need}",
dir.display()
);
}
}
Ok(f)
}
/// Pure: the forge's parameters of the one-way medium (every staged file by its declared name).
pub fn oneway_forge_params(stage: &Path, net: bool) -> Vec<String> {
let mut out = vec![
format!("holger_ron=@{}", stage.join("holger.ron").display()),
format!(
"trust_policy=@{}",
stage.join(p::ONEWAY_TRUST_POLICY_STAGED).display()
),
format!(
"upstream_root=@{}",
stage.join(p::ONEWAY_ROOT_STAGED).display()
),
format!(
"upstream_signer=@{}",
stage.join(p::ONEWAY_SIGNER_STAGED).display()
),
format!(
"plugin_trust_root=@{}",
stage.join(crate::estate_media::KORP_ROOT_FILE).display()
),
];
if net {
out.push(format!(
"holger_net=@{}",
stage.join(p::ONEWAY_NET_STAGED).display()
));
}
out
}
/// Compose the one-way medium from the fixture's trust (what an admin provisions), into `out`.
fn oneway_media(
f: &Fixture,
net: Option<&str>,
bearer: &str,
out: &Path,
root: &Path,
) -> Result<()> {
let r = downstream_ron::OneWayRon::on_a_box("kvm-proof", PROOF_DOMAIN, bearer)
.map_err(|e| anyhow::anyhow!(e))?;
let ron = downstream_ron::render_one_way(&r);
let stage = out.with_extension("stage");
std::fs::create_dir_all(&stage)?;
std::fs::write(stage.join("holger.ron"), &ron)?;
std::fs::write(
stage.join(crate::estate_media::KORP_ROOT_FILE),
holger_recipe::estate_ron::KORP_ROOT_PEM,
)?;
for (from, to) in [
("trust-policy.ron", p::ONEWAY_TRUST_POLICY_STAGED),
("upstream-root.pem", p::ONEWAY_ROOT_STAGED),
("upstream-signer.pem", p::ONEWAY_SIGNER_STAGED),
] {
std::fs::copy(f.dir.join(from), stage.join(to))?;
}
if let Some(n) = net {
std::fs::write(stage.join(p::ONEWAY_NET_STAGED), n)?;
}
let suite = forge_door::declared_medium(
ONEWAY_VARIANT,
ArtifactKind::Installer,
"cargo xtask downstream-oneway-proof",
)?;
let ask = forge_door::Ask {
variant: Some(ONEWAY_VARIANT.to_string()),
params: oneway_forge_params(&stage, net.is_some()),
builds: vec!["kernel".to_string()],
..Default::default()
};
let t0 = std::time::Instant::now();
let iso = forge_door::run(
"downstream-oneway-proof",
root,
&suite,
ONEWAY_VARIANT,
&ask,
)?;
std::fs::rename(&iso, out).or_else(|_| {
std::fs::copy(&iso, out)
.map(|_| ())
.and_then(|_| std::fs::remove_file(&iso))
})?;
std::fs::write(out.with_extension("holger.ron"), &ron)?;
let _ = std::fs::remove_dir_all(&stage);
println!(
"downstream-oneway-proof: the one-way medium composed in {} s: {}",
t0.elapsed().as_secs(),
out.display()
);
Ok(())
}
/// The USB stick: a FAT32 image labelled `HOLGERINBOX` with the fixture's bundle at its top
/// level (korp-installer's FAT writer, no root and no mkfs needed; the appliance's kernel reads
/// FAT, its ESP is one).
fn make_stick(f: &Fixture, img: &Path) -> Result<()> {
let bundle = std::fs::read(f.dir.join(&f.bundle))?;
let mut image = korp_installer::esp::build_esp_image(
STICK_MIB << 20,
&[korp_installer::esp::EspFile::new(f.bundle.clone(), bundle)],
)
.context("[downstream-oneway-proof-stick] format the FAT stick")?;
// The boot sector's FAT32 volume label (offset 71, 11 bytes): the stick's name.
if !(image[66] == 0x29 && &image[82..87] == b"FAT32") {
bail!("[downstream-oneway-proof-stick] the stick is not FAT32");
}
image[71..82].copy_from_slice(b"HOLGERINBOX");
std::fs::write(img, image)?;
Ok(())
}
/// One HTTP/1.1 call to `127.0.0.1:<port>`: `(status, lower-cased headers, body)`.
pub fn http(
port: u16,
method: &str,
path: &str,
headers: &[(&str, &str)],
body: &[u8],
) -> Result<(u16, std::collections::BTreeMap<String, String>, Vec<u8>)> {
use std::io::Read as _;
let mut s = std::net::TcpStream::connect_timeout(
&std::net::SocketAddr::from(([127, 0, 0, 1], port)),
Duration::from_secs(3),
)?;
s.set_read_timeout(Some(Duration::from_secs(20)))?;
let mut head = format!("{method} {path} HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\nContent-Length: {}\r\n", body.len());
for (k, v) in headers {
head.push_str(&format!("{k}: {v}\r\n"));
}
head.push_str("\r\n");
s.write_all(head.as_bytes())?;
s.write_all(body)?;
let mut raw = Vec::new();
s.read_to_end(&mut raw)?;
let split = raw
.windows(4)
.position(|w| w == b"\r\n\r\n")
.context("no HTTP answer")?;
let text = String::from_utf8_lossy(&raw[..split]).to_string();
let mut lines = text.lines();
let status = lines
.next()
.and_then(|l| l.split_whitespace().nth(1))
.and_then(|c| c.parse().ok())
.context("no status")?;
let h: std::collections::BTreeMap<String, String> = lines
.filter_map(|l| l.split_once(':'))
.map(|(k, v)| (k.trim().to_ascii_lowercase(), v.trim().to_string()))
.collect();
let mut b = raw[split + 4..].to_vec();
if h.get("transfer-encoding")
.is_some_and(|v| v.eq_ignore_ascii_case("chunked"))
{
let mut out = Vec::new();
let mut rest = &b[..];
while let Some(nl) = rest.windows(2).position(|w| w == b"\r\n") {
let n =
usize::from_str_radix(String::from_utf8_lossy(&rest[..nl]).trim(), 16).unwrap_or(0);
if n == 0 || rest.len() < nl + 2 + n {
break;
}
out.extend_from_slice(&rest[nl + 2..nl + 2 + n]);
rest = &rest[(nl + 2 + n + 2).min(rest.len())..];
}
b = out;
}
Ok((status, h, b))
}
/// One QMP command to the VM's monitor socket.
fn qmp(sock: &Path, command: &serde_json::Value) -> Result<String> {
use std::io::{BufRead as _, BufReader};
let s = std::os::unix::net::UnixStream::connect(sock)
.with_context(|| format!("[downstream-oneway-proof-qmp] {}", sock.display()))?;
s.set_read_timeout(Some(Duration::from_secs(10)))?;
let mut r = BufReader::new(s.try_clone()?);
let mut line = String::new();
r.read_line(&mut line)?; // the greeting
let mut w = s;
w.write_all(b"{\"execute\":\"qmp_capabilities\"}\n")?;
line.clear();
r.read_line(&mut line)?;
w.write_all(format!("{command}\n").as_bytes())?;
let mut answer = String::new();
// Events may come first; the command's answer has `return` or `error`.
for _ in 0..20 {
answer.clear();
r.read_line(&mut answer)?;
if answer.contains("\"return\"") || answer.contains("\"error\"") {
break;
}
}
if answer.contains("\"error\"") {
bail!("[downstream-oneway-proof-qmp] {command}: {answer}");
}
Ok(answer)
}
pub fn run_oneway_proof() -> Result<()> {
let args: Vec<String> = std::env::args().skip(2).collect();
oneway_proof(&parse_proof(&args)?, &crate::workspace_root())
}
/// ★ **The one-way appliance, proven in KVM**: the medium composed with the fixture's trust,
/// laid once, booted with ONE user-mode NIC in `restrict=on` (the guest reaches nothing but the
/// door forwarded to it); the admin authorizes the first import over the door; a USB stick with
/// the signed bundle is plugged in (QMP hot-plug); the box copies it into its inbox, verifies and
/// lands it, and serves the wheel to pip with `X-Holger-Domain`; then the power button stops it
/// cleanly. Prints `PROOF-OK` and writes `<work>/proof.json`.
fn oneway_proof(o: &ProofOpts, root: &Path) -> Result<()> {
let f = read_fixture(&o.fixture)?;
std::fs::create_dir_all(&o.work)?;
let bearer = format!(
"proof-admin-{}",
blake3::hash(format!("{}{}", std::process::id(), now_ms()).as_bytes()).to_hex()
);
let iso = match &o.iso {
Some(i) => i.clone(),
None => {
let iso = o.work.join("oneway.iso");
oneway_media(&f, o.net.as_deref(), &bearer, &iso, root)?;
iso
}
};
if o.compose_only {
println!("MEDIUM {}", iso.display());
return Ok(());
}
let qemu = kvmproof::which("qemu-system-x86_64")
.context("[downstream-oneway-proof-no-qemu] no qemu-system-x86_64 on PATH")?;
if !Path::new("/dev/kvm").exists() {
bail!("[downstream-oneway-proof-no-kvm] /dev/kvm is absent");
}
// The bearer the medium was composed with (a kept medium's declaration says it).
let bearer = std::fs::read_to_string(iso.with_extension("holger.ron"))
.ok()
.and_then(|t| {
t.split("token: Some(\"")
.nth(1)
.and_then(|r| r.split('"').next())
.map(str::to_string)
})
.unwrap_or(bearer);
let vm = VmOpts {
iso: iso.clone(),
work: o.work.join("vm"),
door: o.door,
mem_mb: o.mem_mb,
install_min: o.install_min,
};
std::fs::create_dir_all(&vm.work)?;
let laying = proof(&vm, qemu.clone());
let t0 = std::time::Instant::now();
let leg = laying.install();
println!(
" {} install {} ms {}",
if leg.pass { "PASS" } else { "FAIL" },
leg.ms,
leg.detail
);
if !leg.pass {
println!(" serial tail:\n{}", leg.serial_tail);
bail!("[downstream-oneway-proof-install] {}", leg.detail);
}
let stick = o.work.join("stick.img");
make_stick(&f, &stick)?;
// The serve leg, by hand: the laid disks, ONE restricted user-mode NIC, the monitor. Boot 1
// hot-plugs the stick after the admin's grant (the site's real order: the box serves, the
// admin authorizes, the stick goes in). A guest that takes no hot-plugged disk is stopped
// cleanly and booted again with the stick in (the grant is kept on its data disk).
let sock = o.work.join("qmp.sock");
let page = format!("/production/simple/{}/", f.project);
let auth = format!("Bearer {bearer}");
let grant = format!(
"/-/bundle/first-import?vendor={}&product={}&sequence={}",
f.vendor, f.product, f.sequence
);
let mut steps: Vec<(String, bool, String)> = Vec::new();
let mut served_at = None;
let mut serial_all = String::new();
let mut clean_stops = 0u32;
for boot in 1..=2u8 {
let stick_at_boot = boot == 2;
let _ = std::fs::remove_file(&sock);
let mut args = Vec::new();
for d in &laying.disks {
args.push("-drive".to_string());
args.push(format!("file={},if=virtio,format=raw", d.path.display()));
}
if stick_at_boot {
args.push("-drive".to_string());
args.push(format!(
"file={},if=virtio,format=raw,readonly=on",
stick.display()
));
}
args.extend([
"-netdev".to_string(),
format!(
"user,id=n0,restrict=on,hostfwd=tcp:127.0.0.1:{}-:{HTTP_DOOR}",
o.door
),
"-device".to_string(),
"virtio-net-pci,netdev=n0".to_string(),
]);
args.extend([
"-qmp".to_string(),
format!("unix:{},server=on,wait=off", sock.display()),
"-boot".to_string(),
"c".to_string(),
]);
// 0 door, 1 grant (+ plug), 2 served?, 3 stopping, 4 stopped, 8 hot-plug gave nothing, 9 red
let mut stage = 0u8;
let mut plugged_at: Option<std::time::Instant> = None;
let mut said = String::new();
let dir = o.work.join(format!("serve-{boot}"));
let run = kvmproof::boot_hold_opts(
&qemu,
&laying.machine,
&args,
&dir,
Duration::from_secs(o.install_min * 60),
false,
|serial| {
said = serial.to_string();
match stage {
0 if door_answers(o.door) => {
steps.push((
format!("boot {boot}: door"),
true,
format!("UP 127.0.0.1:{} after {} s", o.door, t0.elapsed().as_secs()),
));
stage = if boot == 1 { 1 } else { 2 };
}
1 => {
let r = http(o.door, "POST", &grant, &[("Authorization", &auth)], b"");
let ok = matches!(&r, Ok((200, _, _)));
steps.push((
"the admin's first-import grant".into(),
ok,
format!(
"{:?}",
r.as_ref()
.map(|(c, _, b)| (c, String::from_utf8_lossy(b).to_string()))
),
));
if !ok {
stage = 9;
} else {
let plug = qmp(&sock, &serde_json::json!({"execute": "blockdev-add", "arguments": {"driver": "raw", "node-name": "stick", "read-only": true, "file": {"driver": "file", "filename": stick.display().to_string()}}}))
.and_then(|_| qmp(&sock, &serde_json::json!({"execute": "device_add", "arguments": {"driver": "virtio-blk-pci", "drive": "stick", "id": "stick0"}})));
steps.push(("the stick hot-plugged".into(), true, format!("{plug:?}")));
plugged_at = Some(std::time::Instant::now());
stage = 2;
}
}
2 => {
if let Ok((200, h, b)) = http(o.door, "GET", &page, &[], b"") {
if String::from_utf8_lossy(&b)
.contains(&format!("#sha256={}", f.wheel_sha256))
{
let domain = h.get("x-holger-domain").cloned().unwrap_or_default();
steps.push((
"pip index".into(),
true,
format!(
"{page} lists {} sha256 {} (boot {boot})",
f.wheel, f.wheel_sha256
),
));
steps.push((
"domain".into(),
domain == PROOF_DOMAIN,
format!("X-Holger-Domain: {domain}"),
));
let file = http(
o.door,
"GET",
&format!("/production/{}", f.wheel),
&[],
b"",
);
let bytes_ok = file
.as_ref()
.map(|(c, _, b)| *c == 200 && sha256_hex(b) == f.wheel_sha256)
.unwrap_or(false);
steps.push((
"pip download".into(),
bytes_ok,
format!("/production/{} byte for byte", f.wheel),
));
served_at = Some(t0.elapsed().as_secs());
let down =
qmp(&sock, &serde_json::json!({"execute": "system_powerdown"}));
steps.push((
format!("boot {boot}: power button"),
down.is_ok(),
format!("{down:?}"),
));
stage = 3;
}
}
if stage == 2
&& boot == 1
&& plugged_at.is_some_and(|t| t.elapsed() > Duration::from_secs(120))
&& !serial.contains("(HOLGERINBOX):")
{
steps.push(("hot-plug".into(), true, "the guest took no hot-plugged disk in 120 s: a clean stop, then boot 2 with the stick in".into()));
let down =
qmp(&sock, &serde_json::json!({"execute": "system_powerdown"}));
steps.push((
"boot 1: power button".into(),
down.is_ok(),
format!("{down:?}"),
));
stage = 8;
}
}
9 => return true,
_ => {}
}
false
},
);
let serial = if said.is_empty() {
run.serial.clone()
} else {
said
};
let stopped = run.exited.is_some() && serial.contains("stopped cleanly");
clean_stops += u32::from(stopped);
steps.push((
format!("boot {boot}: clean stop"),
stopped,
format!("exited {:?}", run.exited),
));
serial_all.push_str(&format!("\n===== boot {boot} =====\n{serial}"));
if stage != 8 {
break;
}
}
let serial = serial_all;
let has = |needle: &str| serial.contains(needle);
steps.push((
"no pairing".into(),
has("pid1: one-way, no pairing") && !has("pairing seed"),
"the init said it is one-way and paired nothing".into(),
));
steps.push((
"stick into the inbox".into(),
has("(HOLGERINBOX): 1 signed bundle(s) into the inbox"),
"the box copied the stick's bundle into bundle.inbox".into(),
));
steps.push((
"clean stops".into(),
clean_stops >= 1,
format!("{clean_stops} clean stop(s)"),
));
let pass = served_at.is_some() && steps.iter().all(|(_, ok, _)| *ok);
for (name, ok, detail) in &steps {
println!(" {} {name} {detail}", if *ok { "PASS" } else { "FAIL" });
}
let receipt = serde_json::json!({
"proof": "downstream-oneway-proof", "pass": pass, "medium": iso.display().to_string(),
"fixture": f.dir.display().to_string(), "domain": PROOF_DOMAIN, "served_after_s": served_at,
"steps": steps.iter().map(|(n, ok, d)| serde_json::json!({"step": n, "pass": ok, "detail": d})).collect::<Vec<_>>(),
"serial_tail": korp_installer::isoproof::serve::tail(&serial, 60),
});
std::fs::write(
o.work.join("proof.json"),
serde_json::to_vec_pretty(&receipt)?,
)?;
if !pass {
println!(
" serial tail:\n{}",
korp_installer::isoproof::serve::tail(&serial, 60)
);
bail!(
"[downstream-oneway-proof] RED: see {}",
o.work.join("proof.json").display()
);
}
println!("PROOF-OK {}", o.work.join("proof.json").display());
Ok(())
}
fn now_ms() -> u128 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis())
.unwrap_or(0)
}
fn sha256_hex(b: &[u8]) -> String {
use sha2::Digest as _;
sha2::Sha256::digest(b)
.iter()
.map(|x| format!("{x:02x}"))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &str) -> Vec<String> {
v.split_whitespace().map(str::to_string).collect()
}
const KEY: &str = "ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12ab12";
#[test]
fn downstream_media_takes_its_words_and_refuses_strays() {
let o = parse_media(&s(&format!("--name lab-1 --upstream test3.holger.rs --upstream-key {KEY} --via front --mirror https://10.0.2.2:40443 --ca /c.pem --tls-name repo.test3.holger.rs --bearer-file /b --out /o/lab-1.iso"))).unwrap();
assert_eq!(
(
o.name.as_str(),
o.via.as_str(),
o.tls_name.as_deref(),
o.out.as_path()
),
(
"lab-1",
"front",
Some("repo.test3.holger.rs"),
Path::new("/o/lab-1.iso")
)
);
// The certificate's name and the route's `:authority` apart (lane OX-WGDS3).
let apart = parse_media(&s(&format!("--name lab-1 --upstream test3.holger.rs --upstream-key {KEY} --via front --mirror https://10.0.2.2:40443 --ca /c.pem --tls-name test3.holger.rs --authority repo.test3.holger.rs --bearer-file /b --out /o/lab-1.iso"))).unwrap();
assert_eq!(
declaration(&apart, "gate-bearer-of-sufficient-length-0123456789")
.unwrap()
.via,
Via::Front {
server_name: Some("test3.holger.rs".into()),
authority: Some("repo.test3.holger.rs".into())
}
);
assert!(parse_media(&s(&format!("--name lab-2 --upstream u --upstream-key {KEY} --via wireguard --mirror http://10.46.0.1:50070 --authority x --bearer-file /b --out /o/i"))).is_err());
let d = declaration(&o, "gate-bearer-of-sufficient-length-0123456789\n").unwrap();
assert_eq!(
d.via,
Via::Front {
server_name: Some("repo.test3.holger.rs".into()),
authority: None
}
);
let w = parse_media(&s(&format!("--name lab-2 --upstream up --upstream-key {KEY} --via wireguard --mirror http://10.46.0.1:50070 --bearer-file /b --out /o/lab-2.iso"))).unwrap();
assert_eq!(
declaration(&w, &"x".repeat(40)).unwrap().via,
Via::Wireguard
);
for bad in [
format!("--name lab-1 --upstream up --upstream-key {KEY} --via front --mirror https://x --bearer-file /b --out /o"),
format!("--name lab-2 --upstream up --upstream-key {KEY} --via wireguard --mirror http://x --ca /c --bearer-file /b --out /o"),
format!("--name lab-2 --upstream up --upstream-key {KEY} --via tailscale --mirror http://x --bearer-file /b --out /o"),
format!("--name lab-2 --upstream up --upstream-key {KEY} --via wireguard --mirror http://x --out /o"),
"--wat".to_string(),
] {
assert!(parse_media(&s(&bad)).is_err(), "accepted: {bad}");
}
}
#[test]
fn downstream_vm_takes_its_words_and_lays_one_unrestricted_nic() {
let o = parse_vm(&s("--iso /m.iso --work /w --door 40811 --mem-mb 768")).unwrap();
assert_eq!((o.door, o.mem_mb, o.install_min), (40811, 768, 20));
assert!(parse_vm(&s("--iso /m.iso --work /w")).is_err(), "no door");
assert!(parse_vm(&s("--iso /m.iso --work /w --door 0")).is_err());
assert!(parse_vm(&s("--iso /m.iso --door 1 --wat")).is_err());
let p = proof(&o, PathBuf::from("/q"));
assert_eq!(p.nics.len(), 1, "ONE NIC");
assert_eq!(
(p.nics[0].net.as_deref(), &p.nics[0].hostfwds[..]),
(None, &[(40811, HTTP_DOOR)][..]),
"user-mode, unrestricted, the door forwarded"
);
assert!(
p.disks[0].bytes > p.disks[1].bytes,
"the boot disk is the larger"
);
}
#[test]
fn the_one_way_proof_takes_its_words_and_stages_every_trust_file_by_its_declared_param() {
let o = parse_proof(&s(
"--fixture /f --work /w --door 40900 --net eth0:10.0.2.15/24:10.0.2.2",
))
.unwrap();
assert_eq!(
(o.door, o.net.as_deref(), o.mem_mb),
(40900, Some("eth0:10.0.2.15/24:10.0.2.2"), 1024)
);
assert!(
parse_proof(&s("--fixture /f --work /w")).is_err(),
"no door"
);
assert!(parse_proof(&s("--fixture /f --work /w --door 1 --wat")).is_err());
let got = oneway_forge_params(Path::new("/s"), true);
let set = holger_recipe::declared();
let m = set
.installers
.iter()
.find(|i| i.name == ONEWAY_VARIANT)
.expect("the one-way medium");
for g in &got {
let name = g.split('=').next().unwrap();
assert!(
m.params.iter().any(|p| p.name == name),
"{ONEWAY_VARIANT} declares `{name}`"
);
}
for need in [
"holger_ron",
"trust_policy",
"upstream_root",
"upstream_signer",
] {
assert!(
got.iter().any(|g| g.starts_with(&format!("{need}="))),
"{need}: {got:?}"
);
}
assert!(!oneway_forge_params(Path::new("/s"), false)
.iter()
.any(|g| g.starts_with("holger_net=")));
}
#[test]
fn the_staged_files_ride_the_medium_by_its_declared_params() {
let got = forge_params(
Path::new("/s/holger.ron"),
Some(Path::new("/s/downstream-ca.pem")),
Path::new("/s/korp.pem"),
);
assert_eq!(
got,
[
"holger_ron=@/s/holger.ron",
"plugin_trust_root=@/s/korp.pem",
"downstream_ca=@/s/downstream-ca.pem"
]
);
let set = holger_recipe::declared();
let m = set
.installers
.iter()
.find(|i| i.name == VARIANT)
.expect("the medium");
for g in &got {
let name = g.split('=').next().unwrap();
assert!(
m.params.iter().any(|p| p.name == name),
"{VARIANT} declares `{name}`"
);
}
}
}