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, ¬_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");
}