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

nordisk/holger

server/lib/tests/the_estate_declaration_runs_the_provenance_scanner.rs

//! **The declaration every Holger estate ships runs the provenance scanner** (lane
//! HOL-MVP-FORMATS, 2026-10-30: test2.holger.rs's Security page said "Provenance: off, Not
//! installed").
//!
//! The provenance scanner (`holger-provenance`: cosign keyless, DSSE and SLSA attestations,
//! PEP 740, npm attestations, verified offline) is built into every estate binary
//! (`supply-chain`), but the intake chain installs it only when the declaration's
//! `promotion.intake.scan` names a `provenance` block, and the estate declaration named none.
//! It now names one with `default: prefer`: every package's provenance material is verified
//! and recorded, and a package without any is not refused for that (the floor decides; a
//! source may still `require`). The server boots holger-recipe's `estate_ron::medium()` as the
//! box does, and its intake chain reports the provenance slot installed.

use std::io::Write;

use holger_recipe::estate_ron;
use holger_server_lib::scanner::ScannerKind;
use holger_server_lib::{read_ron_config, wire_holger, Holger};
use traits::test_support::hold_port;

const ESTATE: &str = "test3.holger.rs";
const CONSOLE_TOKEN: &str = "console-bearer-of-sufficient-length-0123456789";

struct Running {
    _data: tempfile::TempDir,
    _cfg: tempfile::NamedTempFile,
    holger: Holger,
}

fn start() -> Running {
    let mut last = String::new();
    for _ in 0..8 {
        let (grpc, http) = (hold_port(), hold_port());
        match try_start(grpc, http) {
            Ok(r) => return r,
            Err(e) => last = e,
        }
    }
    panic!("server never started; last error: {last}");
}

/// The intel database a box gets by import, installed by hand: no advisory for
/// the demo wheel, a fresh snapshot.
fn install_intel(dir: &std::path::Path) {
    use holger_intel::chrono::Utc;
    use holger_intel::{AdvisorySet, InstalledIntel, LocalIntelDb};
    let set = AdvisorySet {
        schema: holger_intel::set::SET_SCHEMA.into(),
        generated_at: Utc::now(),
        sources: vec![],
        advisories: vec![],
    };
    let db = LocalIntelDb::open(dir);
    let bytes = set.to_json_bytes().unwrap();
    db.stage_generation(1, &bytes).unwrap();
    db.activate(&InstalledIntel {
        schema: holger_intel::db::INSTALLED_SCHEMA.into(),
        bundle_id: "holger-intel-1".into(),
        sequence: 1,
        supplier: holger_intel::DEFAULT_SUPPLIER.into(),
        snapshot_at: Utc::now(),
        imported_at: Utc::now(),
        candidate_digest: "00".into(),
        release_signer_fingerprint: "00".into(),
        advisories_file: LocalIntelDb::generation_file(1),
        advisories_sha256: holger_server_lib::security::blob_digest_hex(&bytes),
        advisories: 0,
    })
    .unwrap();
}

fn try_start(grpc: u16, http: u16) -> Result<Running, String> {
    let data = tempfile::tempdir().unwrap();
    let d = data.path().display().to_string();
    // The medium's own file, its paths moved under the temporary directory.
    let medium = estate_ron::medium();
    let ron = medium
        .replace(
            &format!("\"{}/", holger_recipe::packaging::BOX_HOLGER_DATA),
            &format!("\"{d}/holger/"),
        )
        .replace(
            &format!("\"{}/", estate_ron::BOX_APPLIANCE_ROOT),
            &format!("\"{d}/"),
        )
        .replace(
            &format!("\"{}/", estate_ron::RELEASE_ROOT),
            &format!("\"{d}/"),
        )
        // A test build scans in-process and runs no clamd; the shipped declaration keeps the
        // defaults (`sandbox: required`, ClamAV required). The medium already carries the
        // `rules` key, so it is REPLACED, never added beside it (RON refuses a duplicate key).
        .replace("rules: (sandbox: required),", "rules: (sandbox: off),")
        .replace("required: true)))", "required: false)))")
        .replace("\"0.0.0.0:50051\"", &format!("\"127.0.0.1:{grpc}\""))
        .replace("\"0.0.0.0:8443\"", &format!("\"127.0.0.1:{http}\""));
    assert!(!ron.contains("\"/data"), "every path moved:\n{ron}");
    // What the installer feeds and the medium stages.
    std::fs::write(
        data.path().join(estate_ron::CONSOLE_TOKEN_FILE),
        CONSOLE_TOKEN,
    )
    .unwrap();
    std::fs::set_permissions(
        data.path().join(estate_ron::CONSOLE_TOKEN_FILE),
        <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o600),
    )
    .unwrap();
    std::fs::write(
        data.path().join(estate_ron::KORP_ROOT_STAGED),
        estate_ron::KORP_ROOT_PEM,
    )
    .unwrap();
    install_intel(&data.path().join("holger/intel"));
    let mut cfg = tempfile::NamedTempFile::new().unwrap();
    cfg.write_all(ron.as_bytes()).unwrap();
    let mut holger: Holger = read_ron_config(cfg.path()).map_err(|e| format!("config: {e:#}"))?;
    // The box: the estate's name from the join reply.
    holger.resolve_estate(Some(ESTATE));
    holger
        .instantiate_backends()
        .map_err(|e| format!("backends: {e:#}"))?;
    wire_holger(&mut holger).map_err(|e| format!("wire: {e:#}"))?;
    holger.start().map_err(|e| format!("start: {e:#}"))?;
    Ok(Running {
        _data: data,
        _cfg: cfg,
        holger,
    })
}

#[tokio::test(flavor = "multi_thread")]
async fn the_estate_declaration_installs_the_provenance_scanner() {
    let r = start();
    let chain = r
        .holger
        .intake()
        .expect("the estate declares the intake chain");
    let pipeline = chain.scanner().expect("the estate declares its scans");
    let installed = pipeline.scanners().installed(ScannerKind::Provenance);
    assert!(
        installed.iter().any(|e| e.contains("holger-provenance")),
        "the Security page's \"Provenance\" row is on, its engine named: {installed:?}"
    );
}