Add manual signed peer card exchange

This commit is contained in:
Eric Wendland 2026-05-18 04:03:52 +02:00
commit 1ab24631cd
14 changed files with 360 additions and 25 deletions

View file

@ -12,6 +12,9 @@ use geth_control::{
};
use geth_crypto::AgentKey;
use geth_db::DbResource;
use geth_discovery::{
DiscoveredPeer, DiscoverySource, EndpointCandidate, PeerCard, discovery_is_untrusted_note,
};
use geth_document::{DocumentResource, DocumentState};
use geth_iroh::{EndpointStatus, GethIrohConfig, GethIrohEndpoint, GethRelayMode};
use geth_keychain::{KeychainOp, KeychainOpKind};
@ -27,7 +30,7 @@ use geth_ssh_identity::{
};
use geth_store::{
Store, StoredAuthOp, StoredDbResource, StoredDocumentResource, StoredFileConflict,
StoredFileRoot, StoredKeychainOp, StoredKvEntry, StoredKvStore, StoredResource,
StoredFileRoot, StoredKeychainOp, StoredKvEntry, StoredKvStore, StoredPeerCard, StoredResource,
StoredResourceSecret, StoredSshCertRequest, StoredSshCertificate, StoredSshRevocation,
};
use geth_types::{
@ -102,6 +105,10 @@ pub enum NodeError {
SshCertFlow(#[from] geth_ssh_identity::SshCertFlowError),
#[error("ssh identity error: {0}")]
SshIdentity(#[from] geth_ssh_identity::SshIdentityError),
#[error("discovery error: {0}")]
Discovery(#[from] geth_discovery::DiscoveryError),
#[error("cannot export peer card before the daemon has an Iroh EndpointID")]
IrohEndpointUnavailable,
}
#[derive(Clone, Debug)]
@ -241,6 +248,62 @@ pub fn handle_request(
node_id: node.node_id.clone(),
endpoint_id: node.iroh_status.endpoint_id.clone(),
})),
ControlRequest::PeerCardExport { out } => {
let endpoint_id = node
.iroh_status
.endpoint_id
.clone()
.ok_or(NodeError::IrohEndpointUnavailable)?;
let key = AgentKey::load(&node.paths.agent_key())?;
let card = PeerCard::signed(
NodeId::new(node.node_id.clone()),
&key,
vec![EndpointCandidate {
endpoint_id,
relay_url: None,
source: DiscoverySource::Manual,
}],
UnixMillis(geth_store::now_ms()),
)?;
if let Some(path) = &out {
std::fs::write(path, serde_json::to_string_pretty(&card)?)?;
}
Ok(ControlResponse::PeerCardExported {
card,
out,
note: discovery_is_untrusted_note().to_owned(),
})
}
ControlRequest::PeerCardImport { path } => {
let card_json = std::fs::read_to_string(&path)?;
let card: PeerCard = serde_json::from_str(&card_json)?;
card.validate_candidate()?;
let discovered = DiscoveredPeer::candidate(
card.clone(),
UnixMillis(geth_store::now_ms()),
DiscoverySource::Imported,
)?;
store.upsert_peer_card(&StoredPeerCard {
peer_id: card.node_id.to_string(),
card_json: serde_json::to_string(&card)?,
updated_at_ms: discovered.discovered_at.0,
})?;
Ok(ControlResponse::PeerCardImported {
peer: discovered,
note: discovery_is_untrusted_note().to_owned(),
})
}
ControlRequest::PeerCardList => {
let peers = store
.list_peer_cards()?
.into_iter()
.map(discovered_peer_from_stored)
.collect::<Result<Vec<_>, _>>()?;
Ok(ControlResponse::PeerCardList {
peers,
note: discovery_is_untrusted_note().to_owned(),
})
}
ControlRequest::ResourceList => Ok(ControlResponse::ResourceList {
resources: store
.list_resources()?
@ -1214,6 +1277,15 @@ fn file_root_from_stored(stored: &StoredFileRoot) -> FileRoot {
}
}
fn discovered_peer_from_stored(stored: StoredPeerCard) -> Result<DiscoveredPeer, NodeError> {
let card: PeerCard = serde_json::from_str(&stored.card_json)?;
Ok(DiscoveredPeer::candidate(
card,
UnixMillis(stored.updated_at_ms),
DiscoverySource::Imported,
)?)
}
fn file_conflict_from_stored(stored: StoredFileConflict) -> Result<FileConflict, NodeError> {
Ok(FileConflict {
id: stored.conflict_id,