Connect pipes over Iroh control
This commit is contained in:
parent
e294965833
commit
741bd202a8
8 changed files with 344 additions and 32 deletions
|
|
@ -268,6 +268,10 @@ pub async fn handle_request_async(
|
|||
message,
|
||||
node: Some(peer_node),
|
||||
} => pubsub_publish_to_peer(node, &peer_node, topic, message).await,
|
||||
ControlRequest::PipeConnect {
|
||||
target,
|
||||
node: Some(peer_node),
|
||||
} => pipe_connect_to_peer(node, &peer_node, target).await,
|
||||
other => handle_request(node, other),
|
||||
}
|
||||
}
|
||||
|
|
@ -545,7 +549,8 @@ async fn peer_ping(node: &LocalNode, peer_node: &str) -> Result<ControlResponse,
|
|||
| PeerControlResponse::SshCertSynced { .. }
|
||||
| PeerControlResponse::SshRevocationSynced { .. }
|
||||
| PeerControlResponse::KvSynced { .. }
|
||||
| PeerControlResponse::PubsubPublished { .. } => Err(NodeError::IrohPeer(
|
||||
| PeerControlResponse::PubsubPublished { .. }
|
||||
| PeerControlResponse::PipeConnected { .. } => Err(NodeError::IrohPeer(
|
||||
"peer returned wrong response type to ping request".to_owned(),
|
||||
)),
|
||||
}
|
||||
|
|
@ -654,7 +659,8 @@ async fn peer_auth_check(
|
|||
| PeerControlResponse::SshCertSynced { .. }
|
||||
| PeerControlResponse::SshRevocationSynced { .. }
|
||||
| PeerControlResponse::KvSynced { .. }
|
||||
| PeerControlResponse::PubsubPublished { .. } => Err(NodeError::IrohPeer(
|
||||
| PeerControlResponse::PubsubPublished { .. }
|
||||
| PeerControlResponse::PipeConnected { .. } => Err(NodeError::IrohPeer(
|
||||
"peer returned wrong response type to auth-check request".to_owned(),
|
||||
)),
|
||||
}
|
||||
|
|
@ -792,7 +798,8 @@ async fn cas_fetch_from_peer(
|
|||
| PeerControlResponse::SshCertSynced { .. }
|
||||
| PeerControlResponse::SshRevocationSynced { .. }
|
||||
| PeerControlResponse::KvSynced { .. }
|
||||
| PeerControlResponse::PubsubPublished { .. } => Err(NodeError::IrohPeer(
|
||||
| PeerControlResponse::PubsubPublished { .. }
|
||||
| PeerControlResponse::PipeConnected { .. } => Err(NodeError::IrohPeer(
|
||||
"peer returned wrong response type to CAS fetch".to_owned(),
|
||||
)),
|
||||
}
|
||||
|
|
@ -1068,6 +1075,54 @@ async fn pubsub_publish_to_peer(
|
|||
}
|
||||
}
|
||||
|
||||
async fn pipe_connect_to_peer(
|
||||
node: &LocalNode,
|
||||
peer_node: &str,
|
||||
target: String,
|
||||
) -> Result<ControlResponse, NodeError> {
|
||||
geth_pipe::validate_pipe_name(&target)?;
|
||||
let response = request_peer_control(node, peer_node, "pipe-connect", |peer_card, nonce| {
|
||||
PeerControlRequest::PipeConnect {
|
||||
peer_card,
|
||||
target: target.clone(),
|
||||
nonce,
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
match response {
|
||||
PeerControlResponse::PipeConnected {
|
||||
node_id,
|
||||
agent_id,
|
||||
endpoint_id,
|
||||
connection,
|
||||
allowed,
|
||||
reason,
|
||||
note,
|
||||
..
|
||||
} => {
|
||||
let connection = connection.unwrap_or_else(|| PipeConnection {
|
||||
target,
|
||||
connected_at: UnixMillis(0),
|
||||
local_listener_found: false,
|
||||
note: geth_pipe::pipe_roadmap().to_owned(),
|
||||
});
|
||||
Ok(ControlResponse::PipeRemoteConnected {
|
||||
peer_node_id: node_id,
|
||||
peer_agent_id: agent_id,
|
||||
endpoint_id,
|
||||
connection,
|
||||
allowed,
|
||||
reason,
|
||||
note,
|
||||
})
|
||||
}
|
||||
PeerControlResponse::Error { message } => Err(NodeError::IrohPeer(message)),
|
||||
_ => Err(NodeError::IrohPeer(
|
||||
"peer returned wrong response type to pipe connect".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn live_sync_cursor_key(peer_node: &str, stream: &str) -> String {
|
||||
format!("live-sync:{peer_node}:{stream}")
|
||||
}
|
||||
|
|
@ -1168,6 +1223,10 @@ async fn request_peer_control(
|
|||
| PeerControlResponse::PubsubPublished {
|
||||
nonce: response_nonce,
|
||||
..
|
||||
}
|
||||
| PeerControlResponse::PipeConnected {
|
||||
nonce: response_nonce,
|
||||
..
|
||||
} if response_nonce == &nonce => Ok(response),
|
||||
PeerControlResponse::Error { .. } => Ok(response),
|
||||
_ => Err(NodeError::IrohPeer(format!(
|
||||
|
|
@ -1606,6 +1665,55 @@ async fn handle_iroh_control_connection(
|
|||
note: "pubsub publish authenticated endpoint/card binding and required pubsub.publish on the remote topic resource; pubsub is lossy".to_owned(),
|
||||
}
|
||||
}
|
||||
PeerControlRequest::PipeConnect {
|
||||
peer_card,
|
||||
target,
|
||||
nonce,
|
||||
} => {
|
||||
geth_pipe::validate_pipe_name(&target)?;
|
||||
peer_card.validate_candidate()?;
|
||||
ensure_peer_card_matches_endpoint(&peer_card, &remote_endpoint_id)?;
|
||||
let discovered = DiscoveredPeer::candidate(
|
||||
peer_card.clone(),
|
||||
UnixMillis(geth_store::now_ms()),
|
||||
DiscoverySource::PeerExchange,
|
||||
)?;
|
||||
let store = Store::open(&node.paths.metadata_db())?;
|
||||
store.upsert_peer_card(&StoredPeerCard {
|
||||
peer_id: peer_card.node_id.to_string(),
|
||||
card_json: serde_json::to_string(&peer_card)?,
|
||||
updated_at_ms: discovered.discovered_at.0,
|
||||
})?;
|
||||
let resource = format!("resource:pipe:{target}");
|
||||
let capability = "pipe.connect".to_owned();
|
||||
let explanation = geth_auth::explain_auth_ops(
|
||||
&load_auth_ops_for_resource(&store, &resource)?,
|
||||
PrincipalId::new(peer_card.node_id.to_string()),
|
||||
ResourceId::new(resource),
|
||||
Capability::new(capability),
|
||||
);
|
||||
let connection = if explanation.allowed {
|
||||
Some(record_pipe_connection(
|
||||
&node,
|
||||
target,
|
||||
"remote pipe connect over protected Iroh control path; byte streams are not implemented yet".to_owned(),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
PeerControlResponse::PipeConnected {
|
||||
node_id: node.node_id.clone(),
|
||||
agent_id: node.agent_id.clone(),
|
||||
endpoint_id: node.iroh_status.endpoint_id.clone().unwrap_or_default(),
|
||||
remote_endpoint_id,
|
||||
connection,
|
||||
allowed: explanation.allowed,
|
||||
reason: explanation.reason,
|
||||
evaluated_ops: explanation.evaluated_ops,
|
||||
nonce,
|
||||
note: "pipe connect authenticated endpoint/card binding and required pipe.connect on the remote pipe resource; byte streams are not implemented yet".to_owned(),
|
||||
}
|
||||
}
|
||||
};
|
||||
send.write_all(geth_control::encode_peer_response(&response)?.as_bytes())
|
||||
.await
|
||||
|
|
@ -2647,26 +2755,18 @@ pub fn handle_request(
|
|||
runtime.listeners.insert(name, listener.clone());
|
||||
Ok(ControlResponse::PipeListening { listener })
|
||||
}
|
||||
ControlRequest::PipeConnect { target } => {
|
||||
ControlRequest::PipeConnect { target, node: None } => {
|
||||
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 {
|
||||
let connection = record_pipe_connection(
|
||||
node,
|
||||
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();
|
||||
}
|
||||
geth_pipe::local_pipe_runtime_note().to_owned(),
|
||||
)?;
|
||||
Ok(ControlResponse::PipeConnected { connection })
|
||||
}
|
||||
ControlRequest::PipeConnect { node: Some(_), .. } => {
|
||||
Err(NodeError::IrohEndpointUnavailable)
|
||||
}
|
||||
ControlRequest::ModuleStub { module, command } => {
|
||||
Ok(ControlResponse::NotImplemented { module, command })
|
||||
}
|
||||
|
|
@ -2782,6 +2882,30 @@ fn record_pubsub_message(
|
|||
Ok(message)
|
||||
}
|
||||
|
||||
fn record_pipe_connection(
|
||||
node: &LocalNode,
|
||||
target: String,
|
||||
note: String,
|
||||
) -> Result<PipeConnection, NodeError> {
|
||||
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,
|
||||
};
|
||||
runtime.connections.push_back(connection.clone());
|
||||
while runtime.connections.len() > PIPE_CONNECTION_RING_LIMIT {
|
||||
runtime.connections.pop_front();
|
||||
}
|
||||
Ok(connection)
|
||||
}
|
||||
|
||||
fn document_resource_from_stored(stored: &StoredDocumentResource) -> DocumentResource {
|
||||
DocumentResource {
|
||||
id: stored.document_id.clone().into(),
|
||||
|
|
@ -3302,6 +3426,13 @@ mod tests {
|
|||
},
|
||||
)
|
||||
.expect("right kv set");
|
||||
handle_request(
|
||||
&right,
|
||||
ControlRequest::PipeListen {
|
||||
name: "inbox".to_owned(),
|
||||
},
|
||||
)
|
||||
.expect("right pipe listen");
|
||||
|
||||
let ping = handle_request_async(
|
||||
&left,
|
||||
|
|
@ -3427,6 +3558,29 @@ mod tests {
|
|||
other => panic!("unexpected denied pubsub publish response: {other:?}"),
|
||||
}
|
||||
|
||||
let denied_pipe = handle_request_async(
|
||||
&left,
|
||||
ControlRequest::PipeConnect {
|
||||
target: "inbox".to_owned(),
|
||||
node: Some(right_card.node_id.to_string()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("denied remote pipe connect");
|
||||
match denied_pipe {
|
||||
ControlResponse::PipeRemoteConnected {
|
||||
allowed,
|
||||
reason,
|
||||
connection,
|
||||
..
|
||||
} => {
|
||||
assert!(!allowed);
|
||||
assert!(!connection.local_listener_found);
|
||||
assert!(reason.contains("no active direct or group grant"));
|
||||
}
|
||||
other => panic!("unexpected denied pipe connect response: {other:?}"),
|
||||
}
|
||||
|
||||
handle_request(
|
||||
&right,
|
||||
ControlRequest::AuthGrant {
|
||||
|
|
@ -3457,6 +3611,16 @@ mod tests {
|
|||
},
|
||||
)
|
||||
.expect("grant left pubsub publish");
|
||||
handle_request(
|
||||
&right,
|
||||
ControlRequest::AuthGrant {
|
||||
subject: left.node_id.clone(),
|
||||
resource: "resource:pipe:inbox".to_owned(),
|
||||
capability: "pipe.connect".to_owned(),
|
||||
grant_id: Some("grant:left-pipe-connect".to_owned()),
|
||||
},
|
||||
)
|
||||
.expect("grant left pipe connect");
|
||||
|
||||
let allowed = handle_request_async(
|
||||
&left,
|
||||
|
|
@ -3595,6 +3759,31 @@ mod tests {
|
|||
other => panic!("unexpected remote pubsub messages response: {other:?}"),
|
||||
}
|
||||
|
||||
let remote_pipe = handle_request_async(
|
||||
&left,
|
||||
ControlRequest::PipeConnect {
|
||||
target: "inbox".to_owned(),
|
||||
node: Some(right_card.node_id.to_string()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("allowed remote pipe connect");
|
||||
match remote_pipe {
|
||||
ControlResponse::PipeRemoteConnected {
|
||||
allowed,
|
||||
connection,
|
||||
reason,
|
||||
note,
|
||||
..
|
||||
} => {
|
||||
assert!(allowed);
|
||||
assert!(connection.local_listener_found);
|
||||
assert!(reason.contains("direct grant"));
|
||||
assert!(note.contains("byte streams are not implemented yet"));
|
||||
}
|
||||
other => panic!("unexpected allowed pipe connect response: {other:?}"),
|
||||
}
|
||||
|
||||
let denied_cert_sync = handle_request_async(
|
||||
&left,
|
||||
ControlRequest::SshCertSync {
|
||||
|
|
|
|||
Loading…
Reference in a new issue