OXYGENHUB
# 01320eefd505 spectra / tests / remote.rs
4.0 KiB · Rust · last changed spectra connect: trust a server through the owner's delegation, pin it, claim it (#21) 1 day ago source blame history raw
1
//! Setting a server up without the owner's secret key (spec §8.6,
2
//! spectra#22): `spectra init --owner KEYID`, nothing served but who it
3
//! is until the named owner claims it, and nobody else may.
4
5
mod common;
6
7
use common::{hermetic, run, text, Serving};
8
use spectra_core::daemon::WireEvent;
9
use spectra_core::keys::KeyPair;
10
use spectra_core::principals::{identity_payload, signed_event};
11
use spectra_mcp::client::{Client, Endpoint};
12
use spectra_mcp::tls::Pinned;
13
14
fn claim_body(pair: &KeyPair) -> Vec<u8> {
15
    let e = signed_event(pair, "human", "identity.owner", identity_payload(&pair.identity));
16
    serde_json::to_vec(&vec![WireEvent::from_new(&e)]).unwrap()
17
}
18
19
#[tokio::test]
20
async fn the_named_owner_claims_a_server_and_nobody_else_can() {
21
    let dir = tempfile::tempdir().unwrap();
22
    let d = dir.path();
23
    // The owner's key is on another machine: here there is only its KeyId.
24
    let owner = KeyPair::generate("Andy McCall", "");
25
    let o = run(d, &["init", "--owner", &owner.key.to_hex(), "--name", "earth"]);
26
    assert!(o.status.success(), "{}", text(&o));
27
    assert!(text(&o).contains("not yet claimed"), "{}", text(&o));
28
    assert!(!d.join("owner").exists(), "no owner home was read or made");
29
30
    let (srv, port) = Serving::start_tls(d);
31
    let probe = Pinned::new(None);
32
    let anon = Client::at(Endpoint::Tls { addr: format!("127.0.0.1:{port}"), config: probe.client_config().unwrap() }, None);
33
    let info = anon.send("GET", "/api/v1/instance", &[], vec![]).await.unwrap().json().unwrap();
34
    assert_eq!(info["name"], "earth");
35
    assert!(info["owner"].is_null(), "unclaimed");
36
    assert_eq!(info["named_owner"], owner.key.to_hex(), "says whose it waits for");
37
    let pin = probe.seen().unwrap().1;
38
    let url = format!("https://127.0.0.1:{port}");
39
40
    // Before the claim, even the owner's signed requests are refused, and say why.
41
    let as_owner = Client::at(Endpoint::tls(&url, pin).unwrap(), Some(owner.clone()));
42
    let r = as_owner.send("GET", "/api/v1/agents", &[], vec![]).await.unwrap();
43
    assert_eq!(r.status, 401);
44
    assert!(String::from_utf8_lossy(&r.body).contains("no owner yet"), "{}", String::from_utf8_lossy(&r.body));
45
46
    // Someone else cannot claim it.
47
    let mallory = KeyPair::generate("Mallory", "");
48
    let r = anon.send("POST", "/api/v1/claim", &[], claim_body(&mallory)).await.unwrap();
49
    assert_eq!(r.status, 403);
50
    assert!(String::from_utf8_lossy(&r.body).contains("another owner"));
51
52
    // Nor can a claim naming the owner but signed by another key.
53
    let mut forged = signed_event(&mallory, "human", "identity.owner", identity_payload(&owner.identity));
54
    forged.payload = identity_payload(&owner.identity);
55
    let r = anon.send("POST", "/api/v1/claim", &[], serde_json::to_vec(&vec![WireEvent::from_new(&forged)]).unwrap()).await.unwrap();
56
    assert_eq!(r.status, 403);
57
58
    // The owner can, once.
59
    let r = anon.send("POST", "/api/v1/claim", &[], claim_body(&owner)).await.unwrap();
60
    assert_eq!(r.status, 200, "{}", String::from_utf8_lossy(&r.body));
61
    let r = as_owner.send("GET", "/api/v1/agents", &[], vec![]).await.unwrap();
62
    assert_eq!(r.status, 200, "{}", String::from_utf8_lossy(&r.body));
63
    let r = anon.send("POST", "/api/v1/claim", &[], claim_body(&owner)).await.unwrap();
64
    assert_eq!(r.status, 403);
65
    assert!(String::from_utf8_lossy(&r.body).contains("already has an owner"));
66
    let info = anon.send("GET", "/api/v1/instance", &[], vec![]).await.unwrap().json().unwrap();
67
    assert_eq!(info["owner"], owner.key.to_hex());
68
    srv.stop();
69
70
    // An owner recorded after the instance verifies, and rebuilds.
71
    for args in [["admin", "verify"], ["admin", "rebuild"], ["admin", "check"]] {
72
        let o = hermetic(d).args(args).output().unwrap();
73
        assert!(o.status.success(), "{args:?}: {}", text(&o));
74
    }
75
}
76
77
#[test]
78
fn init_refuses_what_is_not_a_key_id() {
79
    let dir = tempfile::tempdir().unwrap();
80
    let o = run(dir.path(), &["init", "--owner", "andy"]);
81
    assert!(!o.status.success());
82
    assert!(text(&o).contains("not a KeyId"), "{}", text(&o));
83
}