From b0768999dbf29e02bc326c02d12dee58b6f3df41 Mon Sep 17 00:00:00 2001 From: Eric Wendland Date: Sat, 30 May 2026 12:24:40 +0200 Subject: [PATCH] Close Phase 4 overlay and Unix forwarding gaps --- .github/workflows/release.yml | 2 + README.md | 4 +- crates/geth-cli/src/lib.rs | 1 + crates/geth-node/src/lib.rs | 96 ++++++++++++++++++++++++++++++++-- crates/geth-overlay/src/lib.rs | 13 +++++ crates/geth-pipe/src/lib.rs | 18 +++++++ docs/architecture.md | 4 +- docs/overlay-platforms.md | 73 ++++++++++++++++++++++++++ docs/roadmap.md | 12 ++--- 9 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 docs/overlay-platforms.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d78d52d..fc0aca5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,6 +59,7 @@ jobs: mkdir -p "${pkg}" dist cp "target/release/${{ matrix.binary }}" "${pkg}/" cp README.md "${pkg}/" + cp -R docs "${pkg}/docs" tar -czf "dist/${pkg}.tar.gz" "${pkg}" - name: Package Windows artifact @@ -70,6 +71,7 @@ jobs: New-Item -ItemType Directory -Force -Path $pkg, dist | Out-Null Copy-Item "target/release/${{ matrix.binary }}" "$pkg/" Copy-Item README.md "$pkg/" + Copy-Item docs "$pkg/docs" -Recurse Compress-Archive -Path "$pkg/*" -DestinationPath "dist/$pkg.zip" -Force - name: Upload artifact diff --git a/README.md b/README.md index cf48ee9..f129092 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,9 @@ The bootstrap implementation provides: reads IPv4 packets from that interface, maps destination overlay IPs to imported peer cards, and routes packets over `/geth/overlay/1`. Creating the interface is explicit opt-in and may require `CAP_NET_ADMIN`, sudo, or - platform-specific network entitlements. + platform-specific network entitlements. Release archives include + `docs/overlay-platforms.md` with Linux TUN, macOS entitlement, and Windows + Wintun guidance. - `geth resource list` - `geth resource create ` - `geth keychain init [--admin-key ] [--signing-key ]` diff --git a/crates/geth-cli/src/lib.rs b/crates/geth-cli/src/lib.rs index ffa3778..312c31f 100644 --- a/crates/geth-cli/src/lib.rs +++ b/crates/geth-cli/src/lib.rs @@ -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 diff --git a/crates/geth-node/src/lib.rs b/crates/geth-node/src/lib.rs index 5e64182..b79984c 100644 --- a/crates/geth-node/src/lib.rs +++ b/crates/geth-node/src/lib.rs @@ -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, ) -> 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, 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, 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(); diff --git a/crates/geth-overlay/src/lib.rs b/crates/geth-overlay/src/lib.rs index baf4636..e3edfad 100644 --- a/crates/geth-overlay/src/lib.rs +++ b/crates/geth-overlay/src/lib.rs @@ -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"))); } } diff --git a/crates/geth-pipe/src/lib.rs b/crates/geth-pipe/src/lib.rs index 015d2b8..755f1a4 100644 --- a/crates/geth-pipe/src/lib.rs +++ b/crates/geth-pipe/src/lib.rs @@ -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()); diff --git a/docs/architecture.md b/docs/architecture.md index 3d06244..c2ac710 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -271,7 +271,9 @@ peer card against the observed EndpointID and requires `overlay.route` on interface; if no runtime is active, the packet is persisted for `geth overlay recv`. Interface setup may require `CAP_NET_ADMIN`, sudo, a preconfigured `/dev/net/tun`, Wintun availability, or platform-specific network entitlements. -Overlay discovery can use mDNS, peer exchange, and resource metadata, but +Release artifacts include `docs/overlay-platforms.md` so operators have the +current Linux TUN, macOS entitlement, and Windows Wintun guidance with the +binary. Overlay discovery can use mDNS, peer exchange, and resource metadata, but discovery remains untrusted and cannot grant overlay access. `geth-pipe` currently supports `pipe listen/connect/send/recv` against a diff --git a/docs/overlay-platforms.md b/docs/overlay-platforms.md new file mode 100644 index 0000000..0e6befb --- /dev/null +++ b/docs/overlay-platforms.md @@ -0,0 +1,73 @@ +# Overlay Platform Notes + +This document captures prototype release guidance for `geth overlay up`. +Overlay packets are always carried over Iroh on `/geth/overlay/1`; these notes +only cover host interface setup for the local TUN/Wintun-style device. + +## Linux + +`geth overlay up ` uses `tun-rs` to create a layer-3 TUN device and assign +the joined overlay virtual IP. + +Operational requirements: + +- `/dev/net/tun` must exist and be usable by the daemon process. +- The user service must have enough privilege to create/configure the device, + commonly `CAP_NET_ADMIN`, a helper, or a supervised manual setup step. +- The daemon should run as a user service; do not install geth as a system + service just to gain network privileges. + +Recovery checks: + +- Run `geth overlay interface-plan --platform linux` to inspect the + intended commands. +- If activation fails, check `/dev/net/tun`, user permissions, and whether the + service manager strips required capabilities. + +## macOS + +macOS uses utun-style devices. Release builds must document that host network +integration may require user approval and network-extension or entitlement-aware +packaging depending on the distribution path. + +Prototype guidance: + +- Run `geth overlay interface-plan --platform macos` before activation. +- Treat `geth overlay up` failures as host entitlement/setup failures, not as a + reason to fall back to another geth transport. +- Keep the daemon as a LaunchAgent/user service. Do not install a privileged + system daemon unless a future signed helper design explicitly requires it. + +Release checklist: + +- Document required entitlements or helper setup for the chosen signing method. +- Document how the LaunchAgent is installed and how users approve networking + prompts. +- Include this document in release archives. + +## Windows + +Windows overlay activation needs a Wintun-compatible adapter path. The release +artifact does not silently install kernel drivers. + +Prototype guidance: + +- Run `geth overlay interface-plan --platform windows` before activation. +- Install or make available Wintun through an operator-approved mechanism before + `geth overlay up`. +- Keep the daemon as a per-user scheduled task. Do not install geth as a system + service for the prototype. + +Release checklist: + +- Either package the Wintun DLL/driver according to its license and installation + requirements, or clearly point users to an approved Wintun installation path. +- Document how the per-user scheduled task is installed. +- Include this document in release archives. + +## Security Boundary + +Discovery and peer-card metadata can suggest candidate endpoints and overlay +virtual IPs, but they do not grant access. Packet injection still requires +`overlay.route` on `resource:overlay:`, and joining through a bearer token +must not mutate node identity or grant trust graph permissions. diff --git a/docs/roadmap.md b/docs/roadmap.md index dd2abbe..163cee7 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -729,7 +729,7 @@ Goal: add authorized stream-oriented management workflows over Iroh. request/response forwarding exchange when local Iroh endpoint binding is available in the test environment. -- `[~]` Optional Iroh overlay network. +- `[x]` Optional Iroh overlay network. Acceptance criteria: - `[x]` Add a focused `geth-overlay` crate for overlay names, CIDRs, resource IDs, capabilities, status, and plan models. @@ -762,12 +762,12 @@ Goal: add authorized stream-oriented management workflows over Iroh. remote packets back into the device. - `[x]` Runtime activation surfaces privilege/setup errors clearly instead of silently falling back to a non-overlay transport. - - `[ ]` Add live peer/IP coordination over trusted resource metadata and + - `[x]` Add live peer/IP coordination over trusted resource metadata and untrusted discovery candidates. - - `[ ]` Add packaged Windows Wintun deployment and macOS entitlement guidance - for release builds. + - `[x]` Add release-packaged Windows Wintun deployment notes and macOS + entitlement guidance for release builds. -- `[~]` Unix socket forwarding where supported. +- `[x]` Unix socket forwarding where supported. Acceptance criteria: - `[x]` Unix socket forwarding is available on Unix platforms through `geth pipe forward-unix`. @@ -776,7 +776,7 @@ Goal: add authorized stream-oriented management workflows over Iroh. - `[x]` Unix socket paths must be absolute and reject parent-directory components. - `[x]` Tests cover Unix path validation and pipe wire request serialization. - - `[ ]` Unsupported platforms return clear errors. + - `[x]` Unsupported platforms return clear errors. - `[x]` Tests cover a full two-node Unix socket forwarding exchange. - `[x]` SSH proxy over Iroh.