874 lines
28 KiB
Rust
874 lines
28 KiB
Rust
use std::process::{Child, Command};
|
|
use std::time::{Duration, Instant};
|
|
|
|
fn unix_sockets_available(home: &std::path::Path) -> bool {
|
|
let probe = home.join("probe.sock");
|
|
match std::os::unix::net::UnixListener::bind(&probe) {
|
|
Ok(listener) => {
|
|
drop(listener);
|
|
let _ = std::fs::remove_file(probe);
|
|
true
|
|
}
|
|
Err(error) => {
|
|
eprintln!("skipping daemon socket test; Unix sockets unavailable: {error}");
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
fn geth_bin() -> &'static str {
|
|
env!("CARGO_BIN_EXE_geth")
|
|
}
|
|
|
|
fn run_geth(home: &std::path::Path, args: &[&str]) -> std::process::Output {
|
|
Command::new(geth_bin())
|
|
.env("GETH_HOME", home)
|
|
.args(args)
|
|
.output()
|
|
.expect("run geth")
|
|
}
|
|
|
|
fn spawn_daemon(home: &std::path::Path) -> Child {
|
|
Command::new(geth_bin())
|
|
.env("GETH_HOME", home)
|
|
.args(["daemon", "run"])
|
|
.spawn()
|
|
.expect("spawn daemon")
|
|
}
|
|
|
|
fn wait_for_socket(path: &std::path::Path) {
|
|
let started = Instant::now();
|
|
while started.elapsed() < Duration::from_secs(5) {
|
|
if path.exists() {
|
|
return;
|
|
}
|
|
std::thread::sleep(Duration::from_millis(50));
|
|
}
|
|
panic!("socket did not appear: {}", path.display());
|
|
}
|
|
|
|
#[test]
|
|
fn geth_init_in_temp_home() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let output = run_geth(home.path(), &["init"]);
|
|
assert!(
|
|
output.status.success(),
|
|
"stderr: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
assert!(home.path().join("geth.sqlite").exists());
|
|
assert!(home.path().join("identity/agent.ed25519").exists());
|
|
assert!(home.path().join("config.toml").exists());
|
|
assert!(
|
|
std::fs::read_to_string(home.path().join("config.toml"))
|
|
.expect("read config")
|
|
.contains("relay_mode = \"default\"")
|
|
);
|
|
assert!(
|
|
std::fs::read_to_string(home.path().join("config.toml"))
|
|
.expect("read config")
|
|
.contains("local_discovery = true")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn geth_status_against_running_daemon() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
if !unix_sockets_available(home.path()) {
|
|
return;
|
|
}
|
|
assert!(run_geth(home.path(), &["init"]).status.success());
|
|
std::fs::write(
|
|
home.path().join("config.toml"),
|
|
"[iroh]\nrelay_mode = \"disabled\"\nlocal_discovery = false\n",
|
|
)
|
|
.expect("write config");
|
|
let mut daemon = spawn_daemon(home.path());
|
|
wait_for_socket(&home.path().join("run/geth.sock"));
|
|
|
|
let output = run_geth(home.path(), &["status"]);
|
|
let _ = daemon.kill();
|
|
let _ = daemon.wait();
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"stderr: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(stdout.contains("geth daemon: running"));
|
|
assert!(stdout.contains("agent:"));
|
|
assert!(stdout.contains("endpoint:"));
|
|
assert!(stdout.contains("iroh relay: disabled"));
|
|
assert!(stdout.contains("iroh discovery: local-network disabled"));
|
|
}
|
|
|
|
#[test]
|
|
fn initialized_node_can_roundtrip_cas_blob() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
assert_eq!(node.paths.home(), home.path());
|
|
|
|
let cas = geth_cas::LocalCas::new(paths.cas_dir());
|
|
let output_path = home.path().join("output.txt");
|
|
let added = cas.add_bytes(b"hello geth integration").expect("add blob");
|
|
assert!(cas.has(&added.hash).expect("has blob"));
|
|
cas.get_to_path(&added.hash, &output_path)
|
|
.expect("get blob");
|
|
assert_eq!(
|
|
std::fs::read(output_path).expect("read output"),
|
|
b"hello geth integration"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn cas_pin_unpin_updates_local_pin_metadata() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
let blob_path = home.path().join("blob.txt");
|
|
std::fs::write(&blob_path, b"pinned bytes").expect("write blob");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasAdd { path: blob_path },
|
|
)
|
|
.expect("add cas blob");
|
|
let hash = match response {
|
|
geth_control::ControlResponse::CasAdded { hash, .. } => hash,
|
|
other => panic!("unexpected response: {other:?}"),
|
|
};
|
|
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasPin { hash: hash.clone() },
|
|
)
|
|
.expect("pin blob");
|
|
let response = geth_node::handle_request(&node, geth_control::ControlRequest::CasList)
|
|
.expect("list blobs");
|
|
match response {
|
|
geth_control::ControlResponse::CasList { blobs } => {
|
|
assert_eq!(blobs.len(), 1);
|
|
assert!(blobs[0].pinned);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
geth_node::handle_request(&node, geth_control::ControlRequest::CasUnpin { hash })
|
|
.expect("unpin blob");
|
|
let response = geth_node::handle_request(&node, geth_control::ControlRequest::CasList)
|
|
.expect("list blobs");
|
|
match response {
|
|
geth_control::ControlResponse::CasList { blobs } => {
|
|
assert_eq!(blobs.len(), 1);
|
|
assert!(!blobs[0].pinned);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cas_cleanup_removes_unpinned_and_retains_pinned_blobs() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
let pinned_path = home.path().join("pinned.txt");
|
|
let unpinned_path = home.path().join("unpinned.txt");
|
|
std::fs::write(&pinned_path, b"keep me").expect("write pinned");
|
|
std::fs::write(&unpinned_path, b"remove me").expect("write unpinned");
|
|
|
|
let pinned_hash = match geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasAdd { path: pinned_path },
|
|
)
|
|
.expect("add pinned")
|
|
{
|
|
geth_control::ControlResponse::CasAdded { hash, .. } => hash,
|
|
other => panic!("unexpected response: {other:?}"),
|
|
};
|
|
let unpinned_hash = match geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasAdd {
|
|
path: unpinned_path,
|
|
},
|
|
)
|
|
.expect("add unpinned")
|
|
{
|
|
geth_control::ControlResponse::CasAdded { hash, .. } => hash,
|
|
other => panic!("unexpected response: {other:?}"),
|
|
};
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasPin {
|
|
hash: pinned_hash.clone(),
|
|
},
|
|
)
|
|
.expect("pin blob");
|
|
|
|
let dry_run = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasCleanup { dry_run: true },
|
|
)
|
|
.expect("dry-run cleanup");
|
|
match dry_run {
|
|
geth_control::ControlResponse::CasCleanup {
|
|
removed,
|
|
retained_pinned,
|
|
dry_run,
|
|
} => {
|
|
assert!(dry_run);
|
|
assert_eq!(removed, vec![unpinned_hash.clone()]);
|
|
assert_eq!(retained_pinned, vec![pinned_hash.clone()]);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
assert!(
|
|
geth_cas::LocalCas::new(paths.cas_dir())
|
|
.has(&unpinned_hash)
|
|
.expect("dry-run keeps unpinned")
|
|
);
|
|
|
|
let cleanup = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::CasCleanup { dry_run: false },
|
|
)
|
|
.expect("cleanup");
|
|
match cleanup {
|
|
geth_control::ControlResponse::CasCleanup {
|
|
removed,
|
|
retained_pinned,
|
|
dry_run,
|
|
} => {
|
|
assert!(!dry_run);
|
|
assert_eq!(removed, vec![unpinned_hash.clone()]);
|
|
assert_eq!(retained_pinned, vec![pinned_hash.clone()]);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let cas = geth_cas::LocalCas::new(paths.cas_dir());
|
|
assert!(cas.has(&pinned_hash).expect("pinned retained"));
|
|
assert!(!cas.has(&unpinned_hash).expect("unpinned removed"));
|
|
}
|
|
|
|
#[test]
|
|
fn auth_explain_distinguishes_discovered_peer_candidates() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
let store = geth_store::Store::open(&paths.metadata_db()).expect("open store");
|
|
store
|
|
.upsert_peer_card(&geth_store::StoredPeerCard {
|
|
peer_id: "node:discovered".to_owned(),
|
|
card_json: r#"{"node_id":"node:discovered"}"#.to_owned(),
|
|
updated_at_ms: 1,
|
|
})
|
|
.expect("insert peer card");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::AuthExplain {
|
|
subject: "node:discovered".to_owned(),
|
|
resource: "resource:cas:local".to_owned(),
|
|
capability: "cas.fetch".to_owned(),
|
|
},
|
|
)
|
|
.expect("auth explain");
|
|
match response {
|
|
geth_control::ControlResponse::AuthExplain(explanation) => {
|
|
assert!(!explanation.allowed);
|
|
assert!(explanation.reason.contains("discovered peer candidate"));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn auth_grant_revoke_and_explain_use_local_auth_log() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::AuthGrant {
|
|
subject: "node:laptop".to_owned(),
|
|
resource: "resource:cas:local".to_owned(),
|
|
capability: "cas.fetch".to_owned(),
|
|
grant_id: Some("grant:test-fetch".to_owned()),
|
|
},
|
|
)
|
|
.expect("grant capability");
|
|
match response {
|
|
geth_control::ControlResponse::AuthOpRecorded { op } => {
|
|
assert_eq!(op.resource.to_string(), "resource:cas:local");
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::AuthExplain {
|
|
subject: "node:laptop".to_owned(),
|
|
resource: "resource:cas:local".to_owned(),
|
|
capability: "cas.fetch".to_owned(),
|
|
},
|
|
)
|
|
.expect("explain grant");
|
|
match response {
|
|
geth_control::ControlResponse::AuthExplain(explanation) => {
|
|
assert!(explanation.allowed);
|
|
assert_eq!(explanation.evaluated_ops, 1);
|
|
assert!(explanation.reason.contains("grant:test-fetch"));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::AuthRevoke {
|
|
resource: "resource:cas:local".to_owned(),
|
|
grant_id: "grant:test-fetch".to_owned(),
|
|
},
|
|
)
|
|
.expect("revoke grant");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::AuthExplain {
|
|
subject: "node:laptop".to_owned(),
|
|
resource: "resource:cas:local".to_owned(),
|
|
capability: "cas.fetch".to_owned(),
|
|
},
|
|
)
|
|
.expect("explain revoke");
|
|
match response {
|
|
geth_control::ControlResponse::AuthExplain(explanation) => {
|
|
assert!(!explanation.allowed);
|
|
assert_eq!(explanation.evaluated_ops, 2);
|
|
assert!(explanation.reason.contains("no active"));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn keychain_init_and_status_use_local_keychain_log() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
let admin_key_path = home.path().join("admin.pub");
|
|
std::fs::write(&admin_key_path, "ssh-ed25519 AAAAADMIN eric@geth\n").expect("write admin key");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KeychainInit {
|
|
admin_key_path: Some(admin_key_path),
|
|
},
|
|
)
|
|
.expect("init keychain");
|
|
match response {
|
|
geth_control::ControlResponse::KeychainInitialized { ops } => {
|
|
assert_eq!(ops.len(), 2);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(&node, geth_control::ControlRequest::KeychainStatus)
|
|
.expect("keychain status");
|
|
match response {
|
|
geth_control::ControlResponse::KeychainStatus(status) => {
|
|
assert!(status.initialized);
|
|
assert_eq!(status.admin_keys, 1);
|
|
assert_eq!(status.users, 0);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn db_add_and_status_register_local_db_metadata() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
let db_path = home.path().join("notes.sqlite");
|
|
let conn = rusqlite::Connection::open(&db_path).expect("open sqlite");
|
|
conn.execute(
|
|
"CREATE TABLE notes(id INTEGER PRIMARY KEY, body TEXT NOT NULL)",
|
|
[],
|
|
)
|
|
.expect("create notes table");
|
|
drop(conn);
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DbAdd {
|
|
name: "notes".to_owned(),
|
|
path: db_path.clone(),
|
|
},
|
|
)
|
|
.expect("add db");
|
|
match response {
|
|
geth_control::ControlResponse::DbAdded { db } => {
|
|
assert_eq!(db.name, "notes");
|
|
assert_eq!(db.sync_status, "local-only");
|
|
assert!(db.path_exists);
|
|
assert!(db.size_bytes.unwrap_or_default() > 0);
|
|
assert!(db.schema_metadata.contains("tables=1"));
|
|
assert!(db.schema_metadata.contains("schema_hash="));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DbStatus {
|
|
name: "notes".to_owned(),
|
|
},
|
|
)
|
|
.expect("db status");
|
|
match response {
|
|
geth_control::ControlResponse::DbStatus { db } => {
|
|
assert_eq!(db.name, "notes");
|
|
assert!(db.path.ends_with("notes.sqlite"));
|
|
assert!(db.schema_metadata.contains("tables=1"));
|
|
assert!(db.schema_metadata.contains("schema_hash="));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DbAdd {
|
|
name: "../bad".to_owned(),
|
|
path: db_path,
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DbAdd {
|
|
name: "missing".to_owned(),
|
|
path: home.path().join("missing.sqlite"),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn kv_create_set_get_use_local_store() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KvCreate {
|
|
name: "prefs".to_owned(),
|
|
},
|
|
)
|
|
.expect("create kv");
|
|
match response {
|
|
geth_control::ControlResponse::KvCreated { kv } => {
|
|
assert_eq!(kv.name, "prefs");
|
|
assert_eq!(kv.sync_status, "local-only");
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KvSet {
|
|
name: "prefs".to_owned(),
|
|
key: "apps/foo/theme".to_owned(),
|
|
value: "dark".to_owned(),
|
|
},
|
|
)
|
|
.expect("set kv");
|
|
match response {
|
|
geth_control::ControlResponse::KvSet { entry } => {
|
|
assert_eq!(entry.store.to_string(), "kv:prefs");
|
|
assert_eq!(entry.key, "apps/foo/theme");
|
|
assert_eq!(entry.value, "dark");
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KvGet {
|
|
name: "prefs".to_owned(),
|
|
key: "apps/foo/theme".to_owned(),
|
|
},
|
|
)
|
|
.expect("get kv");
|
|
match response {
|
|
geth_control::ControlResponse::KvGet { entry } => {
|
|
assert_eq!(entry.expect("entry").value, "dark");
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KvGet {
|
|
name: "prefs".to_owned(),
|
|
key: "apps/foo/missing".to_owned(),
|
|
},
|
|
)
|
|
.expect("get missing kv");
|
|
match response {
|
|
geth_control::ControlResponse::KvGet { entry } => {
|
|
assert!(entry.is_none());
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KvCreate {
|
|
name: "../bad".to_owned(),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::KvSet {
|
|
name: "missing".to_owned(),
|
|
key: "apps/foo/theme".to_owned(),
|
|
value: "dark".to_owned(),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn document_create_and_status_use_local_store() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DocumentCreate {
|
|
name: "notes".to_owned(),
|
|
},
|
|
)
|
|
.expect("create document");
|
|
match response {
|
|
geth_control::ControlResponse::DocumentCreated { document } => {
|
|
assert_eq!(document.name, "notes");
|
|
assert_eq!(document.sync_status, "local-only");
|
|
assert_eq!(document.state_bytes, 2);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DocumentStatus {
|
|
name: "notes".to_owned(),
|
|
},
|
|
)
|
|
.expect("document status");
|
|
match response {
|
|
geth_control::ControlResponse::DocumentStatus { document } => {
|
|
assert_eq!(document.id.to_string(), "document:notes");
|
|
assert_eq!(document.resource.to_string(), "resource:document:notes");
|
|
assert_eq!(document.state_bytes, 2);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DocumentCreate {
|
|
name: "../bad".to_owned(),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::DocumentStatus {
|
|
name: "missing".to_owned(),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn secret_create_rotate_and_status_track_resource_epochs() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
|
|
let first = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SecretCreate {
|
|
resource: "resource:cas:local".to_owned(),
|
|
},
|
|
)
|
|
.expect("create secret");
|
|
match first {
|
|
geth_control::ControlResponse::SecretCreated { secret } => {
|
|
assert_eq!(secret.resource.to_string(), "resource:cas:local");
|
|
assert_eq!(secret.epoch, 1);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let second = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SecretRotate {
|
|
resource: "resource:cas:local".to_owned(),
|
|
},
|
|
)
|
|
.expect("rotate secret");
|
|
match second {
|
|
geth_control::ControlResponse::SecretCreated { secret } => {
|
|
assert_eq!(secret.resource.to_string(), "resource:cas:local");
|
|
assert_eq!(secret.epoch, 2);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let status = geth_node::handle_request(&node, geth_control::ControlRequest::SecretStatus)
|
|
.expect("secret status");
|
|
match status {
|
|
geth_control::ControlResponse::SecretStatus { secrets } => {
|
|
assert_eq!(secrets.len(), 2);
|
|
assert_eq!(secrets[0].epoch, 1);
|
|
assert_eq!(secrets[1].epoch, 2);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SecretCreate {
|
|
resource: "resource:missing".to_owned(),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bearer_access_create_list_revoke_uses_resource_scoped_auth_ops() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SecretBearerCreate {
|
|
resource: "resource:cas:local".to_owned(),
|
|
capabilities: vec!["cas.fetch".to_owned(), "cas.pin".to_owned()],
|
|
expires_at_ms: Some(1234),
|
|
},
|
|
)
|
|
.expect("create bearer");
|
|
let secret = match response {
|
|
geth_control::ControlResponse::SecretBearerCreated { access } => {
|
|
assert_eq!(access.resource.to_string(), "resource:cas:local");
|
|
assert_eq!(access.capabilities.len(), 2);
|
|
assert_eq!(access.expires_at.map(|expires_at| expires_at.0), Some(1234));
|
|
assert!(!access.may_delegate);
|
|
access.secret.to_string()
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
};
|
|
|
|
let response = geth_node::handle_request(&node, geth_control::ControlRequest::SecretBearerList)
|
|
.expect("list bearer");
|
|
match response {
|
|
geth_control::ControlResponse::SecretBearerList { access } => {
|
|
assert_eq!(access.len(), 1);
|
|
assert_eq!(access[0].secret.to_string(), secret);
|
|
assert!(!access[0].may_delegate);
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SecretBearerRevoke {
|
|
resource: "resource:cas:local".to_owned(),
|
|
secret: secret.clone(),
|
|
},
|
|
)
|
|
.expect("revoke bearer");
|
|
let response = geth_node::handle_request(&node, geth_control::ControlRequest::SecretBearerList)
|
|
.expect("list revoked bearer");
|
|
match response {
|
|
geth_control::ControlResponse::SecretBearerList { access } => {
|
|
assert!(access.is_empty());
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SecretBearerCreate {
|
|
resource: "resource:cas:local".to_owned(),
|
|
capabilities: vec!["auth.delegate".to_owned()],
|
|
expires_at_ms: None,
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn pubsub_pub_sub_uses_lossy_in_memory_runtime() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::PubsubPub {
|
|
topic: "presence/laptop".to_owned(),
|
|
message: "online".to_owned(),
|
|
},
|
|
)
|
|
.expect("publish");
|
|
match response {
|
|
geth_control::ControlResponse::PubsubPublished { message } => {
|
|
assert_eq!(message.topic.to_string(), "presence/laptop");
|
|
assert_eq!(message.message, "online");
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::PubsubSub {
|
|
topic: "presence/laptop".to_owned(),
|
|
},
|
|
)
|
|
.expect("subscribe snapshot");
|
|
match response {
|
|
geth_control::ControlResponse::PubsubMessages {
|
|
topic,
|
|
messages,
|
|
note,
|
|
} => {
|
|
assert_eq!(topic, "presence/laptop");
|
|
assert_eq!(messages.len(), 1);
|
|
assert_eq!(messages[0].message, "online");
|
|
assert!(note.contains("not authoritative storage"));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let reopened = geth_node::open_node(&paths).expect("reopen node");
|
|
let response = geth_node::handle_request(
|
|
&reopened,
|
|
geth_control::ControlRequest::PubsubSub {
|
|
topic: "presence/laptop".to_owned(),
|
|
},
|
|
)
|
|
.expect("subscribe reopened snapshot");
|
|
match response {
|
|
geth_control::ControlResponse::PubsubMessages { messages, .. } => {
|
|
assert!(messages.is_empty());
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
assert!(
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::PubsubPub {
|
|
topic: "presence/laptop".to_owned(),
|
|
message: String::new(),
|
|
},
|
|
)
|
|
.is_err()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn ssh_cert_request_approval_and_revocation_export_use_local_state() {
|
|
let home = tempfile::tempdir().expect("tempdir");
|
|
let paths = geth_config::GethPaths::from_home(home.path());
|
|
let node = geth_node::init_node(&paths).expect("init node");
|
|
let public_key_path = home.path().join("id_ed25519.pub");
|
|
std::fs::write(&public_key_path, "ssh-ed25519 AAAATEST eric@geth\n").expect("write pubkey");
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SshCertRequest {
|
|
public_key_path: public_key_path.clone(),
|
|
cert_kind: "user".to_owned(),
|
|
principals: vec!["eric".to_owned()],
|
|
requested_validity: Some("+52w".to_owned()),
|
|
renewal_of: None,
|
|
reason: Some("renewal".to_owned()),
|
|
},
|
|
)
|
|
.expect("request cert");
|
|
let request_id = match response {
|
|
geth_control::ControlResponse::SshCertRequested { request } => request.id.to_string(),
|
|
other => panic!("unexpected response: {other:?}"),
|
|
};
|
|
|
|
let response = geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SshCertApprove {
|
|
request_id: request_id.clone(),
|
|
ca_key_path: home.path().join("ca_sk"),
|
|
valid_for: Some("+4w".to_owned()),
|
|
serial: Some(42),
|
|
out: None,
|
|
},
|
|
)
|
|
.expect("approve cert");
|
|
match response {
|
|
geth_control::ControlResponse::SshCertApproved { approval } => {
|
|
assert_eq!(approval.request_id.to_string(), request_id);
|
|
assert!(approval.signing_command.contains(&"ssh-keygen".to_owned()));
|
|
assert!(approval.signing_command.contains(&"42".to_owned()));
|
|
}
|
|
other => panic!("unexpected response: {other:?}"),
|
|
}
|
|
|
|
let export_path = home.path().join("revocations.jsonl");
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SshRevocationAdd {
|
|
kind: "public-key".to_owned(),
|
|
target: "ssh:blake3:test".to_owned(),
|
|
reason: Some("lost key".to_owned()),
|
|
},
|
|
)
|
|
.expect("add revocation");
|
|
geth_node::handle_request(
|
|
&node,
|
|
geth_control::ControlRequest::SshRevocationExport {
|
|
out: export_path.clone(),
|
|
},
|
|
)
|
|
.expect("export revocations");
|
|
assert!(
|
|
std::fs::read_to_string(export_path)
|
|
.expect("read revocations")
|
|
.contains("lost key")
|
|
);
|
|
}
|