diff --git a/crates/geth-node/src/lib.rs b/crates/geth-node/src/lib.rs index 1a8d4db..9eb5841 100644 --- a/crates/geth-node/src/lib.rs +++ b/crates/geth-node/src/lib.rs @@ -10695,6 +10695,196 @@ mod tests { use super::*; use tokio::io::AsyncReadExt; + #[derive(Debug, PartialEq, Eq)] + enum RemoteGuardKind { + Capability, + SignedLog, + OwnerReview, + CandidateOnly, + } + + #[derive(Debug)] + struct RemoteGuardAudit { + operation: &'static str, + mutates_or_opens_service: bool, + guard: RemoteGuardKind, + resource: Option<&'static str>, + capability: Option<&'static str>, + } + + fn remote_guard_audit_matrix() -> Vec { + vec![ + RemoteGuardAudit { + operation: "peer.ping", + mutates_or_opens_service: false, + guard: RemoteGuardKind::CandidateOnly, + resource: None, + capability: None, + }, + RemoteGuardAudit { + operation: "sync.status", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("per-stream"), + capability: Some("per-stream"), + }, + RemoteGuardAudit { + operation: "keychain.sync", + mutates_or_opens_service: true, + guard: RemoteGuardKind::SignedLog, + resource: Some("keychain"), + capability: None, + }, + RemoteGuardAudit { + operation: "auth.sync", + mutates_or_opens_service: true, + guard: RemoteGuardKind::SignedLog, + resource: Some("resource-auth"), + capability: None, + }, + RemoteGuardAudit { + operation: "node.enrollment.submit", + mutates_or_opens_service: true, + guard: RemoteGuardKind::OwnerReview, + resource: Some("node-enrollment"), + capability: None, + }, + RemoteGuardAudit { + operation: "cas.fetch", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:cas:local"), + capability: Some("cas.fetch"), + }, + RemoteGuardAudit { + operation: "cas.root.sync", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:cas-tree:"), + capability: Some("cas.fetch"), + }, + RemoteGuardAudit { + operation: "ssh.cert.sync", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:ssh:certs"), + capability: Some("ssh_cert.sync"), + }, + RemoteGuardAudit { + operation: "ssh.revocation.sync", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:ssh:revocations"), + capability: Some("ssh_revocation.sync"), + }, + RemoteGuardAudit { + operation: "kv.sync", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:kv:"), + capability: Some("kv.read"), + }, + RemoteGuardAudit { + operation: "document.sync", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:document:"), + capability: Some("document.read"), + }, + RemoteGuardAudit { + operation: "db.sync", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:db:"), + capability: Some("db.sync"), + }, + RemoteGuardAudit { + operation: "pubsub.publish", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pubsub:"), + capability: Some("pubsub.publish"), + }, + RemoteGuardAudit { + operation: "pubsub.subscribe", + mutates_or_opens_service: false, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pubsub:"), + capability: Some("pubsub.subscribe"), + }, + RemoteGuardAudit { + operation: "pipe.connect", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pipe:"), + capability: Some("pipe.connect"), + }, + RemoteGuardAudit { + operation: "pipe.listen", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pipe:"), + capability: Some("pipe.listen"), + }, + RemoteGuardAudit { + operation: "pipe.send", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pipe:"), + capability: Some("pipe.connect"), + }, + RemoteGuardAudit { + operation: "pipe.tcp.forward", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pipe-tcp:"), + capability: Some("pipe.forward"), + }, + RemoteGuardAudit { + operation: "pipe.unix.forward", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:pipe-unix:"), + capability: Some("pipe.forward"), + }, + RemoteGuardAudit { + operation: "ssh.proxy.connect", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:ssh-proxy:local"), + capability: Some("ssh_proxy.connect"), + }, + RemoteGuardAudit { + operation: "ssh.admin-shell", + mutates_or_opens_service: true, + guard: RemoteGuardKind::Capability, + resource: Some("resource:ssh-proxy:local"), + capability: Some("ssh_proxy.admin_shell"), + }, + ] + } + + #[test] + fn remote_authorization_audit_matrix_covers_mutating_and_service_operations() { + let matrix = remote_guard_audit_matrix(); + assert_eq!(matrix.len(), 21); + for item in matrix.iter().filter(|item| item.mutates_or_opens_service) { + assert_ne!( + item.guard, + RemoteGuardKind::CandidateOnly, + "{} must not rely on peer-card discovery alone", + item.operation + ); + if item.guard == RemoteGuardKind::Capability { + assert!( + item.resource.is_some() && item.capability.is_some(), + "{} capability guard must document resource and capability", + item.operation + ); + } + } + } + fn write_offline_iroh_config(paths: &GethPaths) { std::fs::write( paths.config_file(), diff --git a/docs/roadmap.md b/docs/roadmap.md index 2dfe3a9..fbef54c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -82,8 +82,12 @@ Implementation order: - `[~]` Remote authorization enforcement audit. Acceptance criteria: - - `[ ]` Every remote mutable operation has an explicit resource capability - check before mutating local state or opening a host service. + - `[x]` Every remote mutable operation has an explicit resource capability + check before mutating local state or opening a host service, or is + documented as a signed-log import / owner-reviewed enrollment exception. + - `[x]` A test-backed remote guard matrix documents the expected guard for + each remote operation and fails if mutating/service-opening operations rely + on discovery alone. - `[ ]` Tests cover denied and allowed paths for CAS, KV, DB, document, pubsub, pipe, SSH proxy/admin shell, SSH cert metadata, and revocations. - `[x]` Initial two-daemon denied-mutation coverage exists for remote pubsub