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/a_downstream_dials_its_invites_wireguard_endpoint.rs

//! ★ **A WireGuard downstream dials the endpoint its signed invite names** (lane
//! HOL-EGRESS-WG, after HOL-NCSC P1.5 put [`egress::check`] on every outgoing socket).
//!
//! A fetching downstream (`boundary: Fetch`) is pinned to its one upstream Holger, the
//! `mirror:` origin (`http://10.46.0.1:50070`, the upstream's door on the front's downstream
//! interface). That door is reached through the tunnel, and the tunnel's one UDP peer is the
//! front's WireGuard endpoint, named in the invite the upstream signed. So taking the invite
//! pins that endpoint too, exactly as signed, in the same store as the door:
//!
//! * the tunnel to the invite's endpoint is not refused (here, a test without
//!   `CAP_NET_ADMIN`, it stops at the device instead);
//! * a tunnel to any other `host:port`, and any other dial (another scheme or protocol to the
//!   same `host:port`, another port), is still refused (`holger-downstream-2`);
//! * an invite whose door is not the pinned upstream pins nothing.
//!
//! Its own process: the pin is process-wide and one-way.

use holger_server_lib::egress;
use holger_server_lib::sync::{tunnel, SyncPeer};
use holger_sync::invite::{DownstreamInvite, INVITE_SCHEMA};
use holger_sync::SyncKey;

const HUB_WG: &str = "CQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQk=";
const DOOR: &str = "http://10.46.0.1:50070";
const ENDPOINT: &str = "198.51.100.4:51830";

fn downstream_mode() {
    let dir = tempfile::tempdir().unwrap();
    let d = dir.path().display().to_string();
    let ron = format!(
        r#"(
    repositories: [
        (ron_name: "inbox", ron_repo_type: "raw", ron_upstreams: [], ron_in: None, ron_out: None, ron_data_dir: Some("{d}/inbox")),
    ],
    exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:0", ron_http_url: None) ],
    storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
    boundary: Fetch,
    mirror: Some((ron_sources: [ (ron_name: "cloud", ron_kind: Holger, ron_endpoint: "{DOOR}", ron_credential: Anonymous,
        ron_repositories: [ (ron_from: "bundles", ron_to: "inbox") ]) ])),
)"#
    );
    let cfg = dir.path().join("h.ron");
    std::fs::write(&cfg, ron).unwrap();
    let h = holger_server_lib::read_ron_config(&cfg).unwrap();
    h.enter_downstream_mode().unwrap();
    std::mem::forget(dir);
}

fn upstream(key: &SyncKey) -> SyncPeer {
    SyncPeer {
        name: "cloud".into(),
        key: key.public_hex(),
        url: Some(DOOR.into()),
        token_file: None,
        via: Default::default(),
        invite: Some("unused".into()),
    }
}

fn invite(own: &str, endpoint: &str, door: &str, now: i64) -> DownstreamInvite {
    DownstreamInvite {
        schema: INVITE_SCHEMA.into(),
        upstream: "cloud".into(),
        downstream: "lab-2".into(),
        downstream_key: own.into(),
        hub_key: HUB_WG.into(),
        endpoint: endpoint.into(),
        address: "10.46.0.2".into(),
        hub_address: "10.46.0.1".into(),
        door: door.into(),
        issued_at: now,
        expires_at: now + 600,
        psk: None,
    }
}

fn refused_as_downstream(what: &str, s: &str) {
    assert!(s.contains("[holger-downstream-2]"), "{what}: {s}");
}

#[test]
fn a_downstream_dials_its_invites_wireguard_endpoint_and_nothing_else() {
    downstream_mode();
    assert_eq!(egress::pinned(), Some(vec![DOOR.to_string()]));

    let up = SyncKey::from_seed([3; 32]);
    let peer = upstream(&up);
    let now = holger_sync::now_secs();

    // An invite whose door is NOT the pinned upstream (another lab's): taken, its endpoint
    // is not pinned, its tunnel is refused.
    let elsewhere = tempfile::tempdir().unwrap();
    let id = tunnel::identity(elsewhere.path()).unwrap();
    let stray = up
        .sign_invite(&invite(
            &id.public_b64,
            "198.51.100.9:51830",
            "http://10.46.0.9:50070",
            now,
        ))
        .unwrap()
        .to_bytes();
    let answer = tunnel::take(elsewhere.path(), "lab-2", &peer, &stray).unwrap();
    refused_as_downstream(
        "a door that is not the upstream",
        answer["tunnel"].as_str().unwrap(),
    );

    // The downstream takes its own invite: the tunnel to its endpoint is not refused.
    let data = tempfile::tempdir().unwrap();
    let id = tunnel::identity(data.path()).unwrap();
    let signed = up
        .sign_invite(&invite(&id.public_b64, ENDPOINT, DOOR, now))
        .unwrap()
        .to_bytes();
    let answer = tunnel::take(data.path(), "lab-2", &peer, &signed).unwrap();
    let state = answer["tunnel"].as_str().unwrap();
    assert!(
        !state.contains("holger-downstream-2") && !state.contains("refused"),
        "the invite's own endpoint is this downstream's line to its front: {state}"
    );
    // The kept invite, at the next start: the same.
    let kept = tunnel::enrol_with(
        data.path(),
        "lab-2",
        &id.public_b64,
        &peer,
        None,
        "kept",
        now,
    )
    .unwrap()
    .unwrap();
    if let Err(e) = tunnel::bring_up_invite(&peer, &kept, id.private) {
        assert!(!e.to_string().contains("holger-downstream-2"), "{e}");
    }

    // Every other peer is still refused: another host, another port.
    for other in ["198.51.100.5:51830", "198.51.100.4:51831"] {
        let not_signed = invite(&id.public_b64, other, DOOR, now);
        let e = tunnel::bring_up_invite(&peer, &not_signed, id.private)
            .map(|_| ())
            .unwrap_err();
        refused_as_downstream(other, &e.to_string());
    }
    // Another dial to the same host:port, by any scheme: refused.
    for other in [
        ENDPOINT,
        "http://198.51.100.4:51830",
        "https://198.51.100.4:51830",
        "https://pypi.org/simple/",
        "http://10.46.0.1:50071",
    ] {
        let e = egress::check("pull-through", other).unwrap_err();
        assert_eq!(e.code().code, "holger-downstream-2", "{other}: {e}");
    }
    // Its upstream's door is still its line out.
    egress::check("sync pull", DOOR).expect("the upstream's door");
    assert!(egress::is_offline(), "everything else is off");
}