Add local pipe registry commands
This commit is contained in:
parent
677945073b
commit
c991825127
13 changed files with 235 additions and 14 deletions
|
|
@ -131,6 +131,9 @@ Roadmap items should be actionable and checkable:
|
||||||
- Pubsub supports local daemon-lifetime publish/subscribe snapshots through a
|
- Pubsub supports local daemon-lifetime publish/subscribe snapshots through a
|
||||||
bounded in-memory ring buffer. Iroh-gossip replication, private topics, and
|
bounded in-memory ring buffer. Iroh-gossip replication, private topics, and
|
||||||
pubsub capability enforcement are still roadmap work.
|
pubsub capability enforcement are still roadmap work.
|
||||||
|
- Pipe listen/connect supports a local daemon-lifetime registry only. Iroh byte
|
||||||
|
streams, TCP/Unix forwarding, and pipe capability enforcement are still
|
||||||
|
roadmap work.
|
||||||
- Resource secret epoch metadata can be created, rotated, and listed locally.
|
- Resource secret epoch metadata can be created, rotated, and listed locally.
|
||||||
Bearer access metadata can be created/listed/revoked as resource-scoped auth
|
Bearer access metadata can be created/listed/revoked as resource-scoped auth
|
||||||
ops and must not allow trust graph mutation capabilities. Payload encryption,
|
ops and must not allow trust graph mutation capabilities. Payload encryption,
|
||||||
|
|
|
||||||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -1120,6 +1120,7 @@ dependencies = [
|
||||||
"geth-document",
|
"geth-document",
|
||||||
"geth-keychain",
|
"geth-keychain",
|
||||||
"geth-kv",
|
"geth-kv",
|
||||||
|
"geth-pipe",
|
||||||
"geth-pubsub",
|
"geth-pubsub",
|
||||||
"geth-resource",
|
"geth-resource",
|
||||||
"geth-secrets",
|
"geth-secrets",
|
||||||
|
|
@ -1223,6 +1224,7 @@ dependencies = [
|
||||||
"geth-iroh",
|
"geth-iroh",
|
||||||
"geth-keychain",
|
"geth-keychain",
|
||||||
"geth-kv",
|
"geth-kv",
|
||||||
|
"geth-pipe",
|
||||||
"geth-pubsub",
|
"geth-pubsub",
|
||||||
"geth-resource",
|
"geth-resource",
|
||||||
"geth-secrets",
|
"geth-secrets",
|
||||||
|
|
@ -1241,6 +1243,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"geth-types",
|
"geth-types",
|
||||||
"serde",
|
"serde",
|
||||||
|
"thiserror 2.0.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,9 @@ The bootstrap implementation provides:
|
||||||
- `geth ssh revocation add <kind> <target>`
|
- `geth ssh revocation add <kind> <target>`
|
||||||
- `geth ssh revocation list`
|
- `geth ssh revocation list`
|
||||||
- `geth ssh revocation export --out <path> [--format jsonl|openssh-krl-spec]`
|
- `geth ssh revocation export --out <path> [--format jsonl|openssh-krl-spec]`
|
||||||
|
- local pipe registry commands: `geth pipe listen/connect`
|
||||||
|
|
||||||
Other command groups exist as explicit stubs: `pipe` and `ssh`.
|
Other command groups exist as explicit stubs: `ssh`.
|
||||||
|
|
||||||
## Resource Modules
|
## Resource Modules
|
||||||
|
|
||||||
|
|
@ -110,7 +111,8 @@ Everything meaningful is modeled as a resource. Planned resource kinds are:
|
||||||
|
|
||||||
- `db`: SQLite/cr-sqlite synchronization
|
- `db`: SQLite/cr-sqlite synchronization
|
||||||
- `kv`: Iroh Documents backed key-value stores
|
- `kv`: Iroh Documents backed key-value stores
|
||||||
- `pipe`: dumbpipe-like byte streams over Iroh
|
- `pipe`: dumbpipe-like byte streams over Iroh; the bootstrap has a local
|
||||||
|
daemon registry only
|
||||||
- `document`: Automerge documents over Iroh streams
|
- `document`: Automerge documents over Iroh streams
|
||||||
- `pubsub`: lossy notifications, not authoritative storage; the bootstrap
|
- `pubsub`: lossy notifications, not authoritative storage; the bootstrap
|
||||||
keeps only an in-memory daemon-lifetime ring buffer
|
keeps only an in-memory daemon-lifetime ring buffer
|
||||||
|
|
|
||||||
|
|
@ -462,9 +462,9 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
|
||||||
PubsubCommand::Pub { topic, message } => ControlRequest::PubsubPub { topic, message },
|
PubsubCommand::Pub { topic, message } => ControlRequest::PubsubPub { topic, message },
|
||||||
PubsubCommand::Sub { topic } => ControlRequest::PubsubSub { topic },
|
PubsubCommand::Sub { topic } => ControlRequest::PubsubSub { topic },
|
||||||
},
|
},
|
||||||
Command::Pipe { command } => ControlRequest::ModuleStub {
|
Command::Pipe { command } => match command {
|
||||||
module: "pipe".to_owned(),
|
PipeCommand::Listen { name } => ControlRequest::PipeListen { name },
|
||||||
command: format!("{command:?}"),
|
PipeCommand::Connect { target } => ControlRequest::PipeConnect { target },
|
||||||
},
|
},
|
||||||
Command::Db { command } => match command {
|
Command::Db { command } => match command {
|
||||||
DbCommand::Add { name, path } => ControlRequest::DbAdd { name, path },
|
DbCommand::Add { name, path } => ControlRequest::DbAdd { name, path },
|
||||||
|
|
@ -947,6 +947,18 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
|
||||||
}
|
}
|
||||||
println!("note: {note}");
|
println!("note: {note}");
|
||||||
}
|
}
|
||||||
|
ControlResponse::PipeListening { listener } => {
|
||||||
|
println!("listening pipe: {}", listener.name);
|
||||||
|
println!("id: {}", listener.id);
|
||||||
|
println!("listened_at_ms: {}", listener.listened_at.0);
|
||||||
|
println!("note: {}", listener.note);
|
||||||
|
}
|
||||||
|
ControlResponse::PipeConnected { connection } => {
|
||||||
|
println!("pipe target: {}", connection.target);
|
||||||
|
println!("local_listener_found: {}", connection.local_listener_found);
|
||||||
|
println!("connected_at_ms: {}", connection.connected_at.0);
|
||||||
|
println!("note: {}", connection.note);
|
||||||
|
}
|
||||||
ControlResponse::NotImplemented { module, command } => {
|
ControlResponse::NotImplemented { module, command } => {
|
||||||
println!("{module} {command}: not implemented yet");
|
println!("{module} {command}: not implemented yet");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ geth-db = { path = "../geth-db" }
|
||||||
geth-document = { path = "../geth-document" }
|
geth-document = { path = "../geth-document" }
|
||||||
geth-keychain = { path = "../geth-keychain" }
|
geth-keychain = { path = "../geth-keychain" }
|
||||||
geth-kv = { path = "../geth-kv" }
|
geth-kv = { path = "../geth-kv" }
|
||||||
|
geth-pipe = { path = "../geth-pipe" }
|
||||||
geth-pubsub = { path = "../geth-pubsub" }
|
geth-pubsub = { path = "../geth-pubsub" }
|
||||||
geth-resource = { path = "../geth-resource" }
|
geth-resource = { path = "../geth-resource" }
|
||||||
geth-secrets = { path = "../geth-secrets" }
|
geth-secrets = { path = "../geth-secrets" }
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use geth_db::DbResource;
|
||||||
use geth_document::{DocumentResource, DocumentState};
|
use geth_document::{DocumentResource, DocumentState};
|
||||||
use geth_keychain::KeychainOp;
|
use geth_keychain::KeychainOp;
|
||||||
use geth_kv::{KvEntry, KvResource};
|
use geth_kv::{KvEntry, KvResource};
|
||||||
|
use geth_pipe::{PipeConnection, PipeListener};
|
||||||
use geth_pubsub::PubsubMessage;
|
use geth_pubsub::PubsubMessage;
|
||||||
use geth_resource::ResourceDescriptor;
|
use geth_resource::ResourceDescriptor;
|
||||||
use geth_secrets::{BearerAccess, ResourceMasterSecret};
|
use geth_secrets::{BearerAccess, ResourceMasterSecret};
|
||||||
|
|
@ -152,6 +153,12 @@ pub enum ControlRequest {
|
||||||
PubsubSub {
|
PubsubSub {
|
||||||
topic: String,
|
topic: String,
|
||||||
},
|
},
|
||||||
|
PipeListen {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
PipeConnect {
|
||||||
|
target: String,
|
||||||
|
},
|
||||||
ModuleStub {
|
ModuleStub {
|
||||||
module: String,
|
module: String,
|
||||||
command: String,
|
command: String,
|
||||||
|
|
@ -284,6 +291,12 @@ pub enum ControlResponse {
|
||||||
messages: Vec<PubsubMessage>,
|
messages: Vec<PubsubMessage>,
|
||||||
note: String,
|
note: String,
|
||||||
},
|
},
|
||||||
|
PipeListening {
|
||||||
|
listener: PipeListener,
|
||||||
|
},
|
||||||
|
PipeConnected {
|
||||||
|
connection: PipeConnection,
|
||||||
|
},
|
||||||
NotImplemented {
|
NotImplemented {
|
||||||
module: String,
|
module: String,
|
||||||
command: String,
|
command: String,
|
||||||
|
|
@ -406,5 +419,13 @@ mod tests {
|
||||||
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
request
|
request
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let request = ControlRequest::PipeListen {
|
||||||
|
name: "inbox".to_owned(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
|
||||||
|
request
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ geth-document = { path = "../geth-document" }
|
||||||
geth-iroh = { path = "../geth-iroh" }
|
geth-iroh = { path = "../geth-iroh" }
|
||||||
geth-keychain = { path = "../geth-keychain" }
|
geth-keychain = { path = "../geth-keychain" }
|
||||||
geth-kv = { path = "../geth-kv" }
|
geth-kv = { path = "../geth-kv" }
|
||||||
|
geth-pipe = { path = "../geth-pipe" }
|
||||||
geth-pubsub = { path = "../geth-pubsub" }
|
geth-pubsub = { path = "../geth-pubsub" }
|
||||||
geth-resource = { path = "../geth-resource" }
|
geth-resource = { path = "../geth-resource" }
|
||||||
geth-secrets = { path = "../geth-secrets" }
|
geth-secrets = { path = "../geth-secrets" }
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use geth_document::{DocumentResource, DocumentState};
|
||||||
use geth_iroh::{EndpointStatus, GethIrohConfig, GethIrohEndpoint, GethRelayMode};
|
use geth_iroh::{EndpointStatus, GethIrohConfig, GethIrohEndpoint, GethRelayMode};
|
||||||
use geth_keychain::{KeychainOp, KeychainOpKind};
|
use geth_keychain::{KeychainOp, KeychainOpKind};
|
||||||
use geth_kv::{KvEntry, KvResource};
|
use geth_kv::{KvEntry, KvResource};
|
||||||
|
use geth_pipe::{PipeConnection, PipeListener};
|
||||||
use geth_pubsub::PubsubMessage;
|
use geth_pubsub::PubsubMessage;
|
||||||
use geth_resource::ResourceDescriptor;
|
use geth_resource::ResourceDescriptor;
|
||||||
use geth_secrets::{BearerAccess, ResourceMasterSecret};
|
use geth_secrets::{BearerAccess, ResourceMasterSecret};
|
||||||
|
|
@ -30,7 +31,7 @@ use geth_types::{
|
||||||
AuthOpId, Capability, KeyId, NodeId, PrincipalId, ResourceId, ResourceKind, ResourceName,
|
AuthOpId, Capability, KeyId, NodeId, PrincipalId, ResourceId, ResourceKind, ResourceName,
|
||||||
SshCertId, SshCertRequestId, UnixMillis,
|
SshCertId, SshCertRequestId, UnixMillis,
|
||||||
};
|
};
|
||||||
use std::collections::VecDeque;
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
|
|
@ -80,6 +81,8 @@ pub enum NodeError {
|
||||||
Secrets(#[from] geth_secrets::SecretsError),
|
Secrets(#[from] geth_secrets::SecretsError),
|
||||||
#[error("pubsub error: {0}")]
|
#[error("pubsub error: {0}")]
|
||||||
Pubsub(#[from] geth_pubsub::PubsubError),
|
Pubsub(#[from] geth_pubsub::PubsubError),
|
||||||
|
#[error("pipe error: {0}")]
|
||||||
|
Pipe(#[from] geth_pipe::PipeError),
|
||||||
#[error("runtime state lock poisoned")]
|
#[error("runtime state lock poisoned")]
|
||||||
RuntimeLockPoisoned,
|
RuntimeLockPoisoned,
|
||||||
#[error("invalid ssh certificate kind: {0}")]
|
#[error("invalid ssh certificate kind: {0}")]
|
||||||
|
|
@ -110,6 +113,7 @@ pub struct LocalNode {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct NodeRuntime {
|
struct NodeRuntime {
|
||||||
pubsub: Mutex<PubsubRuntime>,
|
pubsub: Mutex<PubsubRuntime>,
|
||||||
|
pipes: Mutex<PipeRuntime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -117,7 +121,14 @@ struct PubsubRuntime {
|
||||||
messages: VecDeque<PubsubMessage>,
|
messages: VecDeque<PubsubMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct PipeRuntime {
|
||||||
|
listeners: BTreeMap<String, PipeListener>,
|
||||||
|
connections: VecDeque<PipeConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
const PUBSUB_RING_LIMIT: usize = 256;
|
const PUBSUB_RING_LIMIT: usize = 256;
|
||||||
|
const PIPE_CONNECTION_RING_LIMIT: usize = 256;
|
||||||
|
|
||||||
pub fn init_node(paths: &GethPaths) -> Result<LocalNode, NodeError> {
|
pub fn init_node(paths: &GethPaths) -> Result<LocalNode, NodeError> {
|
||||||
paths.ensure_base_dirs()?;
|
paths.ensure_base_dirs()?;
|
||||||
|
|
@ -143,6 +154,7 @@ pub fn init_node(paths: &GethPaths) -> Result<LocalNode, NodeError> {
|
||||||
iroh_status: EndpointStatus::scaffolded(),
|
iroh_status: EndpointStatus::scaffolded(),
|
||||||
runtime: Arc::new(NodeRuntime {
|
runtime: Arc::new(NodeRuntime {
|
||||||
pubsub: Mutex::new(PubsubRuntime::default()),
|
pubsub: Mutex::new(PubsubRuntime::default()),
|
||||||
|
pipes: Mutex::new(PipeRuntime::default()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -890,6 +902,42 @@ pub fn handle_request(
|
||||||
note: geth_pubsub::pubsub_storage_warning().to_owned(),
|
note: geth_pubsub::pubsub_storage_warning().to_owned(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
ControlRequest::PipeListen { name } => {
|
||||||
|
geth_pipe::validate_pipe_name(&name)?;
|
||||||
|
let listener = PipeListener {
|
||||||
|
id: format!("pipe:{name}").into(),
|
||||||
|
name: name.clone(),
|
||||||
|
listened_at: UnixMillis(geth_store::now_ms()),
|
||||||
|
note: geth_pipe::local_pipe_runtime_note().to_owned(),
|
||||||
|
};
|
||||||
|
let mut runtime = node
|
||||||
|
.runtime
|
||||||
|
.pipes
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| NodeError::RuntimeLockPoisoned)?;
|
||||||
|
runtime.listeners.insert(name, listener.clone());
|
||||||
|
Ok(ControlResponse::PipeListening { listener })
|
||||||
|
}
|
||||||
|
ControlRequest::PipeConnect { target } => {
|
||||||
|
geth_pipe::validate_pipe_name(&target)?;
|
||||||
|
let mut runtime = node
|
||||||
|
.runtime
|
||||||
|
.pipes
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| NodeError::RuntimeLockPoisoned)?;
|
||||||
|
let local_listener_found = runtime.listeners.contains_key(&target);
|
||||||
|
let connection = PipeConnection {
|
||||||
|
target,
|
||||||
|
connected_at: UnixMillis(geth_store::now_ms()),
|
||||||
|
local_listener_found,
|
||||||
|
note: geth_pipe::local_pipe_runtime_note().to_owned(),
|
||||||
|
};
|
||||||
|
runtime.connections.push_back(connection.clone());
|
||||||
|
while runtime.connections.len() > PIPE_CONNECTION_RING_LIMIT {
|
||||||
|
runtime.connections.pop_front();
|
||||||
|
}
|
||||||
|
Ok(ControlResponse::PipeConnected { connection })
|
||||||
|
}
|
||||||
ControlRequest::ModuleStub { module, command } => {
|
ControlRequest::ModuleStub { module, command } => {
|
||||||
Ok(ControlResponse::NotImplemented { module, command })
|
Ok(ControlResponse::NotImplemented { module, command })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,5 @@ license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
geth-types = { path = "../geth-types" }
|
geth-types = { path = "../geth-types" }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use geth_types::{PipeId, ResourceId};
|
use geth_types::{PipeId, ResourceId, UnixMillis};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|
@ -8,7 +8,60 @@ pub struct PipeResource {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PipeListener {
|
||||||
|
pub id: PipeId,
|
||||||
|
pub name: String,
|
||||||
|
pub listened_at: UnixMillis,
|
||||||
|
pub note: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PipeConnection {
|
||||||
|
pub target: String,
|
||||||
|
pub connected_at: UnixMillis,
|
||||||
|
pub local_listener_found: bool,
|
||||||
|
pub note: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum PipeError {
|
||||||
|
#[error("invalid pipe name or target: {0}")]
|
||||||
|
InvalidName(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_pipe_name(name: &str) -> Result<(), PipeError> {
|
||||||
|
if name.is_empty()
|
||||||
|
|| !name
|
||||||
|
.bytes()
|
||||||
|
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':'))
|
||||||
|
{
|
||||||
|
return Err(PipeError::InvalidName(name.to_owned()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn pipe_roadmap() -> &'static str {
|
pub fn pipe_roadmap() -> &'static str {
|
||||||
"future pipes are authorized Iroh bidirectional streams for stdin/stdout and forwarding"
|
"future pipes are authorized Iroh bidirectional streams for stdin/stdout and forwarding"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn local_pipe_runtime_note() -> &'static str {
|
||||||
|
"local daemon registry only; byte streams and Iroh transport are not implemented yet"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_name_validation_rejects_paths_and_empty_names() {
|
||||||
|
assert!(validate_pipe_name("inbox").is_ok());
|
||||||
|
assert!(validate_pipe_name("node:laptop").is_ok());
|
||||||
|
assert!(validate_pipe_name("").is_err());
|
||||||
|
assert!(validate_pipe_name("../inbox").is_err());
|
||||||
|
assert!(validate_pipe_name("inbox/main").is_err());
|
||||||
|
assert!(validate_pipe_name("inbox main").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -873,6 +873,74 @@ fn pubsub_pub_sub_uses_lossy_in_memory_runtime() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_listen_connect_uses_local_runtime_registry() {
|
||||||
|
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::PipeListen {
|
||||||
|
name: "inbox".to_owned(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("listen pipe");
|
||||||
|
match response {
|
||||||
|
geth_control::ControlResponse::PipeListening { listener } => {
|
||||||
|
assert_eq!(listener.id.to_string(), "pipe:inbox");
|
||||||
|
assert_eq!(listener.name, "inbox");
|
||||||
|
assert!(listener.note.contains("local daemon registry"));
|
||||||
|
}
|
||||||
|
other => panic!("unexpected response: {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = geth_node::handle_request(
|
||||||
|
&node,
|
||||||
|
geth_control::ControlRequest::PipeConnect {
|
||||||
|
target: "inbox".to_owned(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("connect pipe");
|
||||||
|
match response {
|
||||||
|
geth_control::ControlResponse::PipeConnected { connection } => {
|
||||||
|
assert_eq!(connection.target, "inbox");
|
||||||
|
assert!(connection.local_listener_found);
|
||||||
|
assert!(
|
||||||
|
connection
|
||||||
|
.note
|
||||||
|
.contains("Iroh transport are not implemented")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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::PipeConnect {
|
||||||
|
target: "inbox".to_owned(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("connect after reopen");
|
||||||
|
match response {
|
||||||
|
geth_control::ControlResponse::PipeConnected { connection } => {
|
||||||
|
assert!(!connection.local_listener_found);
|
||||||
|
}
|
||||||
|
other => panic!("unexpected response: {other:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
geth_node::handle_request(
|
||||||
|
&node,
|
||||||
|
geth_control::ControlRequest::PipeListen {
|
||||||
|
name: "../bad".to_owned(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ssh_cert_request_approval_and_revocation_export_use_local_state() {
|
fn ssh_cert_request_approval_and_revocation_export_use_local_state() {
|
||||||
let home = tempfile::tempdir().expect("tempdir");
|
let home = tempfile::tempdir().expect("tempdir");
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,12 @@ are lost when the daemon stops. This is deliberate: pubsub is a lossy wakeup and
|
||||||
presence channel, not authoritative storage. Iroh-gossip replication, private
|
presence channel, not authoritative storage. Iroh-gossip replication, private
|
||||||
topics, and capability enforcement are future work.
|
topics, and capability enforcement are future work.
|
||||||
|
|
||||||
`geth-pipe` and `geth-ssh-proxy` currently define types, command shape, and
|
`geth-pipe` currently supports `pipe listen/connect` against a local
|
||||||
roadmap stubs.
|
daemon-lifetime registry. This is a control-plane scaffold for names and
|
||||||
|
connection attempts only; it does not carry bytes, forward sockets, or use Iroh
|
||||||
|
streams yet.
|
||||||
|
|
||||||
|
`geth-ssh-proxy` currently defines types, command shape, and roadmap stubs.
|
||||||
|
|
||||||
`geth-ssh-identity` defines SSH trust namespaces plus certificate request,
|
`geth-ssh-identity` defines SSH trust namespaces plus certificate request,
|
||||||
approval, certificate import, and revocation-list data models. The bootstrap
|
approval, certificate import, and revocation-list data models. The bootstrap
|
||||||
|
|
|
||||||
|
|
@ -251,11 +251,15 @@ authorization and durable-state boundaries clear.
|
||||||
|
|
||||||
Goal: add authorized stream-oriented management workflows over Iroh.
|
Goal: add authorized stream-oriented management workflows over Iroh.
|
||||||
|
|
||||||
- `[ ]` Dumbpipe-style Iroh streams.
|
- `[~]` Dumbpipe-style Iroh streams.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
- `geth pipe listen/connect` can connect two local test nodes.
|
- `[x]` `geth pipe listen/connect` works against a local daemon-lifetime
|
||||||
- Pipe access requires `pipe.listen` or `pipe.connect`.
|
registry.
|
||||||
- Streams close cleanly and propagate errors.
|
- `[x]` Tests cover local listener registration, local connect matching, and
|
||||||
|
daemon restart behavior.
|
||||||
|
- `[ ]` `geth pipe listen/connect` can connect two local test nodes over Iroh.
|
||||||
|
- `[ ]` Pipe access requires `pipe.listen` or `pipe.connect`.
|
||||||
|
- `[ ]` Streams close cleanly and propagate errors.
|
||||||
|
|
||||||
- `[ ]` TCP forwarding.
|
- `[ ]` TCP forwarding.
|
||||||
Acceptance criteria:
|
Acceptance criteria:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue