Close Phase 4 overlay and Unix forwarding gaps
This commit is contained in:
parent
0fdf5d0be7
commit
b0768999db
9 changed files with 210 additions and 13 deletions
|
|
@ -261,6 +261,7 @@ Current limits:
|
|||
- discovery can suggest peers, but never grants overlay access
|
||||
- overlay access must be resource-authorized with overlay.join/overlay.route
|
||||
- all overlay packets must be carried over Iroh, not SSH or another transport
|
||||
- release/platform notes live in docs/overlay-platforms.md
|
||||
|
||||
Bearer invite flow:
|
||||
geth resource create overlay home
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ pub enum NodeError {
|
|||
IrohPeer(String),
|
||||
#[error("overlay runtime error: {0}")]
|
||||
OverlayRuntime(String),
|
||||
#[error("unsupported platform: {0}")]
|
||||
UnsupportedPlatform(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -635,6 +637,11 @@ pub async fn run_unix_forward(
|
|||
target_path: PathBuf,
|
||||
bearer_secret: Option<String>,
|
||||
) -> Result<(), NodeError> {
|
||||
if !geth_pipe::unix_forward_supported() {
|
||||
return Err(NodeError::UnsupportedPlatform(
|
||||
geth_pipe::unix_forward_unsupported_message().to_owned(),
|
||||
));
|
||||
}
|
||||
let listen_path = geth_pipe::validate_unix_forward_path(&listen_path)?;
|
||||
geth_pipe::validate_unix_forward_path(&target_path)?;
|
||||
let listener = UnixListener::bind(&listen_path)?;
|
||||
|
|
@ -9034,10 +9041,14 @@ pub fn handle_request(
|
|||
})
|
||||
}
|
||||
ControlRequest::PipeSend { node: Some(_), .. } => Err(NodeError::IrohEndpointUnavailable),
|
||||
ControlRequest::PipeTcpForward { .. }
|
||||
| ControlRequest::PipeTcpStream { .. }
|
||||
| ControlRequest::PipeUnixForward { .. }
|
||||
| ControlRequest::PipeUnixStream { .. } => Err(NodeError::IrohEndpointUnavailable),
|
||||
ControlRequest::PipeTcpForward { .. } | ControlRequest::PipeTcpStream { .. } => {
|
||||
Err(NodeError::IrohEndpointUnavailable)
|
||||
}
|
||||
ControlRequest::PipeUnixForward { .. } | ControlRequest::PipeUnixStream { .. } => {
|
||||
Err(NodeError::UnsupportedPlatform(
|
||||
geth_pipe::unix_forward_unsupported_message().to_owned(),
|
||||
))
|
||||
}
|
||||
ControlRequest::PipeRecv { name, peek } => {
|
||||
geth_pipe::validate_pipe_name(&name)?;
|
||||
let messages = pipe_messages(node, &name, !peek)?;
|
||||
|
|
@ -10397,11 +10408,19 @@ fn overlay_peers(store: &Store, network: &str) -> Result<Vec<OverlayPeer>, NodeE
|
|||
let cidr = overlay_membership(store, network)?
|
||||
.map(|membership| membership.cidr)
|
||||
.unwrap_or_else(|| geth_overlay::DEFAULT_OVERLAY_CIDR.to_owned());
|
||||
let resource = geth_overlay::overlay_resource_id(network).to_string();
|
||||
store
|
||||
.list_peer_cards()?
|
||||
.into_iter()
|
||||
.map(|stored| {
|
||||
let card: PeerCard = serde_json::from_str(&stored.card_json)?;
|
||||
let peer = PrincipalId::new(card.node_id.to_string());
|
||||
let route_authorized = can_sync_resource(
|
||||
store,
|
||||
&peer,
|
||||
resource.as_str(),
|
||||
geth_overlay::CAPABILITY_ROUTE,
|
||||
)?;
|
||||
Ok(OverlayPeer {
|
||||
node_id: card.node_id.to_string(),
|
||||
endpoint_id: card
|
||||
|
|
@ -10412,7 +10431,11 @@ fn overlay_peers(store: &Store, network: &str) -> Result<Vec<OverlayPeer>, NodeE
|
|||
&cidr,
|
||||
card.node_id.as_str(),
|
||||
)?),
|
||||
state: "candidate-only".to_owned(),
|
||||
state: if route_authorized {
|
||||
"route-authorized-candidate".to_owned()
|
||||
} else {
|
||||
"candidate-only".to_owned()
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -13161,6 +13184,69 @@ mod tests {
|
|||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlay_peers_combine_trusted_route_metadata_with_untrusted_candidates() {
|
||||
let store = Store::open_memory().expect("open store");
|
||||
let key = AgentKey::generate();
|
||||
let card = PeerCard::signed(
|
||||
"node:right".into(),
|
||||
&key,
|
||||
vec![EndpointCandidate {
|
||||
endpoint_id: "endpoint:right".to_owned(),
|
||||
relay_url: None,
|
||||
direct_addresses: Vec::new(),
|
||||
source: DiscoverySource::Imported,
|
||||
}],
|
||||
UnixMillis(1),
|
||||
)
|
||||
.expect("peer card");
|
||||
store
|
||||
.upsert_peer_card(&StoredPeerCard {
|
||||
peer_id: card.node_id.to_string(),
|
||||
card_json: serde_json::to_string(&card).expect("card json"),
|
||||
updated_at_ms: 1,
|
||||
})
|
||||
.expect("store peer card");
|
||||
store
|
||||
.put_module_state(&StoredModuleState {
|
||||
module: geth_overlay::overlay_state_key("home"),
|
||||
state_json: serde_json::to_string(
|
||||
&geth_overlay::joined_overlay_membership(
|
||||
"home",
|
||||
Some("172.22.0.0/24"),
|
||||
"node:left",
|
||||
"secret",
|
||||
1,
|
||||
)
|
||||
.expect("membership"),
|
||||
)
|
||||
.expect("membership json"),
|
||||
updated_at_ms: 1,
|
||||
})
|
||||
.expect("store membership");
|
||||
|
||||
let peers = overlay_peers(&store, "home").expect("candidate peers");
|
||||
assert_eq!(peers.len(), 1);
|
||||
assert_eq!(peers[0].node_id, "node:right");
|
||||
assert_eq!(peers[0].state, "candidate-only");
|
||||
assert_eq!(
|
||||
peers[0].virtual_ip,
|
||||
Some(
|
||||
geth_overlay::deterministic_virtual_ip("172.22.0.0/24", "node:right")
|
||||
.expect("virtual ip")
|
||||
)
|
||||
);
|
||||
|
||||
grant_test_capability(
|
||||
&store,
|
||||
"node:right",
|
||||
"resource:overlay:home",
|
||||
geth_overlay::CAPABILITY_ROUTE,
|
||||
);
|
||||
let peers = overlay_peers(&store, "home").expect("authorized peers");
|
||||
assert_eq!(peers[0].state, "route-authorized-candidate");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lan_discovery_address_selection_uses_iroh_direct_addresses() {
|
||||
let key = AgentKey::generate();
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ pub fn interface_plan(
|
|||
.to_owned(),
|
||||
"activation is explicit and user-scoped".to_owned(),
|
||||
"all overlay packets remain carried over /geth/overlay/1".to_owned(),
|
||||
"release archives include docs/overlay-platforms.md with Linux TUN, macOS entitlement, and Windows Wintun guidance".to_owned(),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
|
@ -525,5 +526,17 @@ mod tests {
|
|||
.iter()
|
||||
.any(|note| note.contains("generated"))
|
||||
);
|
||||
assert!(
|
||||
interface
|
||||
.notes
|
||||
.iter()
|
||||
.any(|note| note.contains("overlay-platforms.md"))
|
||||
);
|
||||
let macos = interface_plan(&plan, Some("macos"), Some("172.22.0.10"))
|
||||
.expect("macOS interface plan");
|
||||
assert!(macos.commands.iter().any(|cmd| cmd.contains("utun")));
|
||||
let windows = interface_plan(&plan, Some("windows"), Some("172.22.0.10"))
|
||||
.expect("Windows interface plan");
|
||||
assert!(windows.commands.iter().any(|cmd| cmd.contains("Wintun")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,18 @@ pub enum PipeError {
|
|||
InvalidUnixSocketPath(String),
|
||||
}
|
||||
|
||||
pub const UNIX_FORWARD_UNSUPPORTED_MESSAGE: &str = "Unix socket forwarding is only supported on Unix platforms; use TCP forwarding on this platform";
|
||||
|
||||
#[must_use]
|
||||
pub const fn unix_forward_supported() -> bool {
|
||||
cfg!(unix)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn unix_forward_unsupported_message() -> &'static str {
|
||||
UNIX_FORWARD_UNSUPPORTED_MESSAGE
|
||||
}
|
||||
|
||||
pub fn validate_pipe_name(name: &str) -> Result<(), PipeError> {
|
||||
if name.is_empty()
|
||||
|| !name
|
||||
|
|
@ -124,6 +136,12 @@ mod tests {
|
|||
assert!(validate_tcp_forward_target_addr("192.0.2.10:22").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unix_forward_support_is_explicit_for_platforms() {
|
||||
assert_eq!(unix_forward_supported(), cfg!(unix));
|
||||
assert!(unix_forward_unsupported_message().contains("Unix socket forwarding"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unix_forward_paths_must_be_absolute_without_parent_components() {
|
||||
assert!(validate_unix_forward_path(Path::new("/tmp/geth.sock")).is_ok());
|
||||
|
|
|
|||
Loading…
Reference in a new issue