fix(sandbox): exempt host gateway from SSRF block for rootless Podman#1279
fix(sandbox): exempt host gateway from SSRF block for rootless Podman#1279maxamillion wants to merge 3 commits into
Conversation
pasta assigns a link-local IP (169.254.x.x) as the host gateway in rootless Podman containers, which was always blocked by SSRF guards. Read the trusted gateway IP from /etc/hosts at proxy startup (injected by the driver via host.openshell.internal / host.containers.internal aliases) and exempt only that specific IP from the link-local block, while keeping 169.254.169.254 (cloud metadata) hard-blocked regardless.
Extract a public is_link_local_ip(IpAddr) helper in openshell-core::net covering IPv4 169.254.0.0/16, IPv6 fe80::/10, and IPv4-mapped variants. Refactor the inline IPv6 fe80::/10 bitmask in is_always_blocked_ip() and is_internal_ip() to use the new helper, eliminating two copies of the same pattern. Harden the trusted-gateway SSRF exemption in proxy.rs: both detect_trusted_host_gateway() and resolve_and_check_trusted_gateway() now reject always-blocked non-link-local IPs (loopback, unspecified) with a warning. The exemption was intended only for link-local addresses used by rootless Podman with pasta; a /etc/hosts entry mapping host.openshell.internal to 127.0.0.1 or 0.0.0.0 previously bypassed the loopback/unspecified SSRF invariant. Add 169.254. prefix to the generate_security_notes() host checks in mechanistic_mapper and policy server, closing a gap where link-local literal hosts were not flagged as internal/private in advisory notes.
…ion tests When detect_trusted_host_gateway() finds more than one distinct IP for host.openshell.internal, emit a structured warning so operators can diagnose unexpected /etc/hosts state. The first entry still wins (and any runtime mismatch is caught by resolve_and_check_trusted_gateway), but the ambiguity is now observable. Replace inline guard-logic tests with real calls to resolve_and_check_trusted_gateway() using IP literals, which bypass DNS and give deterministic results. This exercises the actual function wiring rather than mirroring the predicate logic manually. Add parse_hosts_file_for_host tests covering: single entry, same-IP deduplication across lines, multiple distinct IPs (documents first-wins ordering), multi-alias lines, missing alias, and comment handling.
60d765d to
37f2098
Compare
| // with pasta. Loopback (127.x), unspecified (0.0.0.0), and other | ||
| // always-blocked non-link-local addresses are never legitimate host | ||
| // gateway IPs and must not receive the exemption. | ||
| if is_always_blocked_ip(ip) && !is_link_local_ip(ip) { |
There was a problem hiding this comment.
P1: This turns the trusted-gateway path into a bypass for any non-loopback/non-unspecified gateway IP, not just the link-local Podman/pasta case described in the PR. On Docker/K8s-style setups, host.openshell.internal can resolve to private gateway IPs such as 172.17.0.1 or 192.168.x.x; taking this branch for those addresses skips the existing allowed_ips / resolve_and_reject_internal() SSRF controls.
Please only enable the exemption for a pinned link-local gateway, for example by rejecting non-link-local IPs here after the metadata check:
if !is_link_local_ip(ip) {
return None;
}That keeps the new rootless Podman behavior while preserving the existing private-IP policy path everywhere else.
|
|
||
| /// Returns `true` if `ip` is a known cloud instance metadata endpoint that | ||
| /// must never be exempted from SSRF blocking. | ||
| fn is_cloud_metadata_ip(ip: IpAddr) -> bool { |
There was a problem hiding this comment.
P1: is_link_local_ip() now treats IPv4-mapped IPv6 addresses as link-local, but the cloud metadata guard only compares the exact IPv4 169.254.169.254. That means ::ffff:169.254.169.254 is link-local for exemption purposes but is not recognized as metadata, which violates the “metadata is never exempted” invariant if it is pinned as the trusted gateway.
Please normalize IPv4-mapped IPv6 in this helper as well, e.g. compare mapped IPv6 addresses against the same metadata IPv4 value.
Summary
Rootless Podman with pasta assigns a link-local IP (
169.254.x.x) as the host gateway — previously always-blocked by SSRF guards — preventing sandbox code from reachinghost.openshell.internal. This PR fixes the SSRF exemption, hardens it against misuse, and adds a sharedis_link_local_ip()helper toopenshell-core.Related Issue
No tracking issue — discovered during rootless Podman networking investigation.
Changes
crates/openshell-core/src/net.rs: Add publicis_link_local_ip(IpAddr) -> boolcovering IPv4169.254.0.0/16, IPv6fe80::/10, and IPv4-mapped variants. Refactor the duplicate inline(v6.segments()[0] & 0xffc0) == 0xfe80bitmask out ofis_always_blocked_ip()andis_internal_ip()(was 3 copies, now 1).crates/openshell-sandbox/src/proxy.rs:/etc/hostsat proxy startup (before user code runs); exempt only that specific IP from the link-local SSRF block.detect_trusted_host_gateway()rejects cloud metadata IPs, always-blocked non-link-local IPs (loopback, unspecified), and emits a structured warning if/etc/hostshas multiple distinct IPs for the alias.resolve_and_check_trusted_gateway()adds defense-in-depth: rejects cloud metadata, mismatch against the pinned IP, always-blocked non-link-local addresses, and control-plane ports.HOST_GATEWAY_ALIASEScovershost.openshell.internal,host.containers.internal,host.docker.internal.CLOUD_METADATA_IPS(169.254.169.254) is never exempted.crates/openshell-sandbox/src/mechanistic_mapper.rsandcrates/openshell-server/src/grpc/policy.rs: Add"169.254."togenerate_security_notes()host prefix checks (advisory notes were missing link-local).Testing
mise run pre-commitpasses (all Rust checks: fmt, clippy, check, helm lint, markdown lint)is_link_local_ip()inopenshell-coreparse_hosts_file_for_hosttests covering: single entry, same-IP deduplication, multiple distinct IPs (documents first-wins ordering), multi-alias lines, missing alias, comment strippingresolve_and_check_trusted_gatewaytests using IP literals (deterministic, no DNS) covering: loopback rejection, unspecified rejection, mismatch rejection, cloud metadata rejectiondetect_trusted_host_gatewayguard predicate testsChecklist