Prevent unsigned Fleet collectors from claiming federation provenance

This commit is contained in:
archipelago
2026-10-07 20:35:50 -04:00
parent c7bf4db085
commit 9268930c10
3 changed files with 290 additions and 12 deletions
+4 -4
View File
@@ -4,27 +4,27 @@
//! Trust is bilateral — both sides must agree. Federated nodes periodically
//! sync container status, health metrics, and availability.
pub(crate) mod handshake_delivery;
mod invites;
pub mod pending;
pub(crate) mod handshake_delivery;
mod storage;
mod sync;
mod types;
// Re-export all public items so `crate::federation::*` continues to work.
pub use invites::{accept_invite, create_invite, parse_invite};
pub(crate) use invites::restrict_discovery_invite;
pub use invites::{accept_invite, create_invite, parse_invite};
// Crate-internal: used by the periodic federation auto-sync to re-assert
// membership to peers that don't list us back (asymmetry self-heal).
pub(crate) use invites::notify_join;
// Crate-internal: peer-joined resolves the granted trust level by matching
// the acceptor's invite_token against our stored outgoing invites.
pub(crate) use storage::load_invites;
pub(crate) use storage::{load_unique_payment_peer, load_unique_payment_peer_by_did};
#[allow(unused_imports)]
pub use storage::{
add_node, fips_npub_for_onion, load_nodes, load_removed_dids, record_peer_transport,
record_sync_result, remove_node, save_nodes, set_trust_level, update_node,
};
pub(crate) use storage::{load_invites, load_node_identities};
pub(crate) use storage::{load_unique_payment_peer, load_unique_payment_peer_by_did};
pub use sync::{build_local_state, deploy_to_peer, sync_with_peer, sync_with_peer_by_did};
pub use types::{AppStatus, FederatedNode, NodeStateSnapshot, TrustLevel, TrustSource};
@@ -71,6 +71,23 @@ pub async fn load_nodes(data_dir: &Path) -> Result<Vec<FederatedNode>> {
load_nodes_inner(data_dir).await
}
/// Every persisted relationship identity, before display-only onion deduplication.
/// Callers excluding unsigned claims must not lose an observer/untrusted DID
/// merely because another record currently shares its route.
pub(crate) async fn load_node_identities(
data_dir: &Path,
) -> Result<std::collections::HashSet<String>> {
let _guard = FEDERATION_STORE_LOCK.lock().await;
let content = match fs::read(data_dir.join(FEDERATION_DIR).join(NODES_FILE)).await {
Ok(content) => content,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Default::default()),
Err(error) => return Err(error).context("Could not read federation identities"),
};
let file: NodesFile = serde_json::from_slice(&content)
.context("Invalid federation identities; unsigned claims cannot replace them")?;
Ok(file.nodes.into_iter().map(|node| node.did).collect())
}
/// Resolve payment identity from persisted records before display deduplication
/// can merge fields from different identities sharing an address.
pub(crate) async fn load_unique_payment_peer(