OXYGENHUB
# 77df591d5a2f spectra / tests / remote.rs
3.9 KiB · Rust · last changed Set a server up without the owner's key: spectra init --owner and the claim (#22) 2 days 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(&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
    let pin = probe.seen().unwrap().1;
37
    let url = format!("https://127.0.0.1:{port}");
38
39
    // Before the claim, even the owner's signed requests are refused, and say why.
40
    let as_owner = Client::at(Endpoint::tls(&url, pin).unwrap(), Some(owner.clone()));
41
    let r = as_owner.send("GET", "/api/v1/agents", &[], vec![]).await.unwrap();
42
    assert_eq!(r.status, 401);
43
    assert!(String::from_utf8_lossy(&r.body).contains("no owner yet"), "{}", String::from_utf8_lossy(&r.body));
44
45
    // Someone else cannot claim it.
46
    let mallory = KeyPair::generate("Mallory", "");
47
    let r = anon.send("POST", "/api/v1/claim", &[], claim_body(&mallory)).await.unwrap();
48
    assert_eq!(r.status, 403);
49
    assert!(String::from_utf8_lossy(&r.body).contains("another owner"));
50
51
    // Nor can a claim naming the owner but signed by another key.
52
    let mut forged = signed_event(&mallory, "human", "identity.owner", identity_payload(&owner.identity));
53
    forged.payload = identity_payload(&owner.identity);
54
    let r = anon.send("POST", "/api/v1/claim", &[], serde_json::to_vec(&WireEvent::from_new(&forged)).unwrap()).await.unwrap();
55
    assert_eq!(r.status, 403);
56
57
    // The owner can, once.
58
    let r = anon.send("POST", "/api/v1/claim", &[], claim_body(&owner)).await.unwrap();
59
    assert_eq!(r.status, 200, "{}", String::from_utf8_lossy(&r.body));
60
    let r = as_owner.send("GET", "/api/v1/agents", &[], vec![]).await.unwrap();
61
    assert_eq!(r.status, 200, "{}", String::from_utf8_lossy(&r.body));
62
    let r = anon.send("POST", "/api/v1/claim", &[], claim_body(&owner)).await.unwrap();
63
    assert_eq!(r.status, 403);
64
    assert!(String::from_utf8_lossy(&r.body).contains("already has an owner"));
65
    let info = anon.send("GET", "/api/v1/instance", &[], vec![]).await.unwrap().json().unwrap();
66
    assert_eq!(info["owner"], owner.key.to_hex());
67
    srv.stop();
68
69
    // An owner recorded after the instance verifies, and rebuilds.
70
    for args in [["admin", "verify"], ["admin", "rebuild"], ["admin", "check"]] {
71
        let o = hermetic(d).args(args).output().unwrap();
72
        assert!(o.status.success(), "{args:?}: {}", text(&o));
73
    }
74
}
75
76
#[test]
77
fn init_refuses_what_is_not_a_key_id() {
78
    let dir = tempfile::tempdir().unwrap();
79
    let o = run(dir.path(), &["init", "--owner", "andy"]);
80
    assert!(!o.status.success());
81
    assert!(text(&o).contains("not a KeyId"), "{}", text(&o));
82
}