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

nordisk/monetize

crates/monetize-admin/tests/door.rs

//! **The operator's door, over the wire**: mutual TLS under the box's
//! installer CA; the operator's certificate puts the solo feed (products,
//! vendors, prices, secrets — what the installer fed, changed after the
//! install), a box's own certificate (of the same CA) is refused by name, a
//! feed monetize would not start on is refused whole and nothing is written,
//! and what is read back is key NAMES, never a value.

use std::sync::Arc;
use std::time::Duration;

use nordisk_pairing::mtls::{self, InstallerCa, Issued};
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};

async fn put(addr: std::net::SocketAddr, ca: &str, me: &Issued, body: &str) -> (u16, String) {
    send(addr, ca, me, "PUT", "/feed", body).await
}

/// How long one request at the door may take (RA1-11, as tests/door_security.rs).
const DEADLINE: Duration = Duration::from_secs(30);

async fn send(
    addr: std::net::SocketAddr,
    ca: &str,
    me: &Issued,
    method: &str,
    path: &str,
    body: &str,
) -> (u16, String) {
    tokio::time::timeout(DEADLINE, send_once(addr, ca, me, method, path, body))
        .await
        .unwrap_or_else(|_| panic!("{method} {path}: no answer within {DEADLINE:?}"))
}

async fn send_once(
    addr: std::net::SocketAddr,
    ca: &str,
    me: &Issued,
    method: &str,
    path: &str,
    body: &str,
) -> (u16, String) {
    let cfg = mtls::client_config(ca, me).unwrap();
    let tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
    let mut tls = tokio_rustls::TlsConnector::from(cfg)
        .connect(mtls::vm_name(), tcp)
        .await
        .unwrap();
    let req = format!("{method} {path} HTTP/1.1\r\nHost: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}", mtls::VM_SERVER_NAME, body.len());
    tls.write_all(req.as_bytes()).await.unwrap();
    // write_all can leave the last TLS records in rustls's buffer (RA1-11): flush them.
    tls.flush().await.unwrap();
    let mut out = Vec::new();
    let _ = tls.read_to_end(&mut out).await;
    let t = String::from_utf8_lossy(&out).to_string();
    (
        t.split_whitespace()
            .nth(1)
            .and_then(|s| s.parse().ok())
            .unwrap_or(0),
        t.split_once("\r\n\r\n")
            .map(|(_, b)| b.to_string())
            .unwrap_or_default(),
    )
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_operator_puts_the_feed_and_nobody_else_does() {
    use base64::Engine as _;
    let ca = InstallerCa::mint("monetize-admin", Duration::from_secs(3600)).unwrap();
    let box_identity = ca.issue_bootstrap(Duration::from_secs(3600)).unwrap();
    let operator = ca.issue_installer(Duration::from_secs(3600)).unwrap();
    let (csr, key) = mtls::box_request().unwrap();
    let stranger = Issued {
        cert_pem: ca
            .sign_box(&csr, "admin", Duration::from_secs(3600))
            .unwrap(),
        key_pem: key,
    };

    let dir = tempfile::tempdir().unwrap();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let id = box_identity.clone();
    tokio::spawn(monetize_admin::door::serve(
        listener,
        ca.cert_pem.clone(),
        Arc::new(move || id.clone()),
        dir.path().to_path_buf(),
    ));

    let wasm = base64::engine::general_purpose::STANDARD.encode(b"\0asm\x01\0\0\0");
    let feed = |vendor: &str| {
        serde_json::json!({
            "plugin.holger.wasm": wasm,
            "plugin.holger.toml": format!("{vendor}instance = \"holger-rs\"\nguest_digest = \"ab\"\ngrants = []\n"),
            "plugin.holger.prices": "[SE]\nrepo_month = 4900\n",
            "vendor.stripe.holger.wasm": wasm,
            "vendor.stripe.holger.toml": "enabled = true\nguest_digest = \"cd\"\n",
            "secret.MONETIZE_STRIPE_HOLGER_SECRET_KEY": "sk_test_never_shown",
        })
        .to_string()
    };
    let good = feed("vendor = \"stripe/holger\"\n");

    let (st, body) = put(addr, &ca.cert_pem, &stranger, &good).await;
    assert_eq!(st, 403, "{body}");
    assert!(body.contains("[not-the-operator]"), "{body}");
    assert!(
        !dir.path()
            .join(monetize_admin::solo::DOOR_FEED_REL)
            .exists(),
        "nothing written for a stranger"
    );

    let (st, body) = put(addr, &ca.cert_pem, &operator, &feed("")).await;
    assert_eq!(st, 422, "{body}");
    assert!(
        body.contains("vendor"),
        "a priced product that names no vendor: {body}"
    );
    assert!(
        !dir.path()
            .join(monetize_admin::solo::DOOR_FEED_REL)
            .exists(),
        "a refused feed writes nothing"
    );
    let (st, body) = put(addr, &ca.cert_pem, &operator, "[1, 2]").await;
    assert_eq!(st, 400, "{body}");
    assert!(body.contains("[feed-json]"), "{body}");

    // Break-glass recovery: the operator only; it leaves the console's marker.
    let (st, _) = send(addr, &ca.cert_pem, &stranger, "POST", "/passkeys/reset", "").await;
    assert_eq!(st, 403);
    assert!(!dir.path().join(monetize_admin::solo::RESET_REL).exists());
    let (st, body) = send(addr, &ca.cert_pem, &operator, "POST", "/passkeys/reset", "").await;
    assert_eq!(st, 202, "{body}");
    assert!(dir.path().join(monetize_admin::solo::RESET_REL).exists());

    let (st, body) = put(addr, &ca.cert_pem, &operator, &good).await;
    assert_eq!(st, 200, "{body}");
    assert!(body.contains("holger"), "{body}");
    assert!(
        !body.contains("sk_test_never_shown"),
        "a value is never said: {body}"
    );
    let kept = monetize_admin::solo::read_door_feed(dir.path());
    assert_eq!(
        kept.get("secret.MONETIZE_STRIPE_HOLGER_SECRET_KEY")
            .map(String::as_str),
        Some("sk_test_never_shown")
    );
    use std::os::unix::fs::PermissionsExt as _;
    assert_eq!(
        std::fs::metadata(dir.path().join(monetize_admin::solo::DOOR_FEED_REL))
            .unwrap()
            .permissions()
            .mode()
            & 0o777,
        0o600
    );
    let (st, body) = send(addr, &ca.cert_pem, &operator, "GET", "/feed", "").await;
    assert_eq!(st, 200, "{body}");
    assert!(
        body.contains("secret.MONETIZE_STRIPE_HOLGER_SECRET_KEY")
            && body.contains("plugin.holger.toml"),
        "{body}"
    );
    assert!(
        !body.contains("sk_test_never_shown") && !body.contains("4900"),
        "key names only: {body}"
    );
}

/// G7: **an enrolment token is the operator's to mint** on the ONE monetize's door
/// (`POST /attach/token`); the estate redeems it at the edge's `POST /attach/enrol`.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_operator_mints_an_enrolment_token_and_nobody_else_does() {
    let ca = InstallerCa::mint("monetize-admin", Duration::from_secs(3600)).unwrap();
    let box_identity = ca.issue_bootstrap(Duration::from_secs(3600)).unwrap();
    let operator = ca.issue_installer(Duration::from_secs(3600)).unwrap();
    let (csr, key) = mtls::box_request().unwrap();
    let stranger = Issued {
        cert_pem: ca
            .sign_box(&csr, "admin", Duration::from_secs(3600))
            .unwrap(),
        key_pem: key,
    };
    let dir = tempfile::tempdir().unwrap();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let id = box_identity.clone();
    tokio::spawn(monetize_admin::door::serve(
        listener,
        ca.cert_pem.clone(),
        Arc::new(move || id.clone()),
        dir.path().to_path_buf(),
    ));
    let ask = r#"{"estate":"acme","product":"gunnar"}"#;
    let tokens = dir.path().join(monetize_front::attach::TOKENS_REL);

    let (st, body) = send(addr, &ca.cert_pem, &stranger, "POST", "/attach/token", ask).await;
    assert_eq!(st, 403, "{body}");
    assert!(!tokens.exists(), "nothing minted for a stranger");
    let (st, body) = send(addr, &ca.cert_pem, &operator, "POST", "/attach/token", ask).await;
    assert_eq!(st, 200, "{body}");
    let v: serde_json::Value = serde_json::from_str(&body).unwrap();
    assert!(v["token"].as_str().is_some_and(|t| !t.is_empty()), "{body}");
    assert!(tokens.exists());
}

/// **A product the binary LINKS is fed with no module** (lane L26, 2026-10-19): the kit
/// (private-monetize-ops `[products.<id>] linked = true`) sends `plugin.<id>.toml` and
/// `.prices` and no `.wasm`, because the build serves that product with its own native
/// plugin. On a build that links `holger`, the door takes it (200) and the block names no
/// guest; a product the build does NOT link is still refused by name
/// (`[plugin-no-module]`), and so is holger on a build that links no holger — the server
/// would not start on a block with neither a module nor a plugin.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_linked_product_is_fed_without_a_module_and_an_unlinked_one_is_refused() {
    let ca = InstallerCa::mint("monetize-admin", Duration::from_secs(3600)).unwrap();
    let box_identity = ca.issue_bootstrap(Duration::from_secs(3600)).unwrap();
    let operator = ca.issue_installer(Duration::from_secs(3600)).unwrap();
    let start = |linked: Option<Vec<&'static str>>| {
        let (ca_pem, id) = (ca.cert_pem.clone(), box_identity.clone());
        async move {
            let dir = tempfile::tempdir().unwrap();
            let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
            let addr = listener.local_addr().unwrap();
            let identity: Arc<dyn Fn() -> Issued + Send + Sync> = Arc::new(move || id.clone());
            match linked {
                Some(l) => tokio::spawn(monetize_admin::door::serve_linked(
                    listener,
                    ca_pem,
                    identity,
                    dir.path().to_path_buf(),
                    l,
                )),
                None => tokio::spawn(monetize_admin::door::serve(
                    listener,
                    ca_pem,
                    identity,
                    dir.path().to_path_buf(),
                )),
            };
            (dir, addr)
        }
    };
    let feed = |id: &str| {
        serde_json::json!({
            format!("plugin.{id}.toml"): format!("free = true\ninstance = \"{id}-rs\"\ngrants = []\n"),
        })
        .to_string()
    };

    // The holger-linked build (the vetra set): taken, and the block names no guest.
    let (dir, addr) = start(Some(vec!["holger"])).await;
    let (st, body) = put(addr, &ca.cert_pem, &operator, &feed("holger")).await;
    assert_eq!(st, 200, "a linked product needs no module: {body}");
    assert!(body.contains("holger"), "{body}");
    assert!(monetize_admin::solo::read_door_feed(dir.path()).contains_key("plugin.holger.toml"));
    // A product that build does NOT link, with no module: refused by name, nothing written.
    let (dir, addr) = start(Some(vec!["holger"])).await;
    let (st, body) = put(addr, &ca.cert_pem, &operator, &feed("kassa")).await;
    assert_eq!(st, 422, "{body}");
    assert!(
        body.contains("[plugin-no-module]") && body.contains("plugin.kassa.wasm"),
        "{body}"
    );
    assert!(
        !dir.path()
            .join(monetize_admin::solo::DOOR_FEED_REL)
            .exists(),
        "a refused feed writes nothing"
    );

    // This binary's own set: it links no holger, so holger with no module is refused.
    let (_dir, addr) = start(None).await;
    let linked = monetize_admin::linked().product_names();
    let (st, body) = put(addr, &ca.cert_pem, &operator, &feed("holger")).await;
    if linked.contains(&"holger") {
        assert_eq!(st, 200, "{body}");
    } else {
        assert_eq!(st, 422, "this build links {linked:?}: {body}");
        assert!(body.contains("[plugin-no-module]"), "{body}");
    }
}

fn sha256_hex(b: &[u8]) -> String {
    ring::digest::digest(&ring::digest::SHA256, b)
        .as_ref()
        .iter()
        .map(|x| format!("{x:02x}"))
        .collect()
}

/// ★ **R34-5: the door answers a feed APPLIED only once the box applied it** (rehearsal
/// 2026-10-25: the door said "taken" 2-3 s before the box applied the feed live, and an
/// order sent at once used the old vendor settings). PID 1 says which door feed it took
/// (`solo/door-feed.taken`: the feed file's sha256, or `<sha256> NOT taken: <why>`), the
/// server which config it applied (`<config>.applied`: the config's sha256); the door waits
/// for both, and says a refusal as one. (A stand-in plays PID 1 and the server here.)
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn the_door_answers_applied_only_after_the_box_applied_the_feed() {
    use base64::Engine as _;
    let ca = InstallerCa::mint("monetize-admin", Duration::from_secs(3600)).unwrap();
    let box_identity = ca.issue_bootstrap(Duration::from_secs(3600)).unwrap();
    let operator = ca.issue_installer(Duration::from_secs(3600)).unwrap();
    let dir = tempfile::tempdir().unwrap();
    let root = dir.path().to_path_buf();
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let id = box_identity.clone();
    tokio::spawn(monetize_admin::door::serve(
        listener,
        ca.cert_pem.clone(),
        Arc::new(move || id.clone()),
        root.clone(),
    ));
    let wasm = base64::engine::general_purpose::STANDARD.encode(b"\0asm\x01\0\0\0");
    let feed = |price: u32| {
        serde_json::json!({
            "plugin.holger.wasm": wasm,
            "plugin.holger.toml": "vendor = \"stripe/holger\"\ninstance = \"holger-rs\"\nguest_digest = \"ab\"\ngrants = [\"grpc\"]\n",
            "plugin.holger.prices": format!("[SE]\nrepo_month = {price}\n"),
            "vendor.stripe.holger.wasm": wasm,
            "vendor.stripe.holger.toml": "enabled = true\nguest_digest = \"cd\"\n",
        })
        .to_string()
    };
    // PID 1 runs and has taken an older feed; the server applied the config it wrote.
    let taken = root.join("solo/door-feed.taken");
    let config = root.join(monetize_front::intake::CONFIG_REL);
    let applied = std::path::PathBuf::from(format!("{}.applied", config.display()));
    std::fs::create_dir_all(config.parent().unwrap()).unwrap();
    std::fs::create_dir_all(taken.parent().unwrap()).unwrap();
    std::fs::write(&taken, "0".repeat(64)).unwrap();
    std::fs::write(&config, "# old\n").unwrap();
    std::fs::write(&applied, sha256_hex(b"# old\n")).unwrap();
    // The stand-in: a second after the door feed moves it is taken, a second later applied.
    let stand_in = |refuse: bool| {
        let (root, taken, config, applied) =
            (root.clone(), taken.clone(), config.clone(), applied.clone());
        tokio::spawn(async move {
            let door_feed = root.join(monetize_admin::solo::DOOR_FEED_REL);
            let was = std::fs::read(&door_feed).ok();
            while std::fs::read(&door_feed).ok() == was {
                tokio::time::sleep(Duration::from_millis(50)).await;
            }
            let d = sha256_hex(&std::fs::read(&door_feed).unwrap());
            tokio::time::sleep(Duration::from_secs(1)).await;
            if refuse {
                std::fs::write(
                    &taken,
                    format!("{d} NOT taken: REFUSED [stand-in] the box would not start on it"),
                )
                .unwrap();
                return std::time::Instant::now();
            }
            let text = format!("# config of {d}\n");
            std::fs::write(&config, &text).unwrap();
            std::fs::write(&taken, &d).unwrap();
            tokio::time::sleep(Duration::from_secs(1)).await;
            std::fs::write(&applied, sha256_hex(text.as_bytes())).unwrap();
            std::time::Instant::now()
        })
    };
    let box_side = stand_in(false);
    let (st, body) = put(addr, &ca.cert_pem, &operator, &feed(4900)).await;
    let answered = std::time::Instant::now();
    let applied_at = box_side.await.unwrap();
    assert_eq!(st, 200, "{body}");
    assert!(
        answered >= applied_at,
        "the door answered before the box applied the feed: {body}"
    );
    assert!(body.starts_with("applied:"), "{body}");
    // A feed PID 1 refuses is said as refused, never as taken.
    let box_side = stand_in(true);
    let (st, body) = put(addr, &ca.cert_pem, &operator, &feed(5900)).await;
    box_side.await.unwrap();
    assert_eq!(st, 422, "{body}");
    assert!(
        body.contains("NOT taken") && body.contains("[stand-in]"),
        "{body}"
    );
}