Skip to content

fix(sandbox): resolve host-gateway aliases from proxy /etc/hosts - #2479

Open
zanetworker wants to merge 2 commits into
NVIDIA:mainfrom
zanetworker:2478-fix-ssrf-host-gateway-dns/az
Open

fix(sandbox): resolve host-gateway aliases from proxy /etc/hosts#2479
zanetworker wants to merge 2 commits into
NVIDIA:mainfrom
zanetworker:2478-fix-ssrf-host-gateway-dns/az

Conversation

@zanetworker

@zanetworker zanetworker commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix SSRF engine blocking host.openshell.internal in 0.0.91 by adding a DNS resolution fallback that reads the proxy's own /etc/hosts for host-gateway aliases.
  • The sandbox process runs in a separate mount namespace whose /etc/hosts does not contain the driver-injected entry, and the system DNS resolver cannot resolve proxy-internal names. The proxy's /etc/hosts (populated by the compute driver's --add-host flag) is the correct source of truth.

Related Issue

Fixes #2478

Changes

crates/openshell-supervisor-network/src/proxy.rs

  • Added resolve_from_proxy_hosts() (async) backed by a path-parameterized resolve_from_hosts_file() helper — reads the proxy's own /etc/hosts via tokio::fs (matches the sibling resolve_from_sandbox_hosts I/O pattern) using the existing parse_hosts_file_for_host parser. Scoped to Linux + test. Read failures log at debug! and return None (fall through to DNS) rather than failing silently.
  • Extended resolve_socket_addrs() with a new resolution step between sandbox /etc/hosts and system DNS: for host-gateway aliases only, try the proxy's /etc/hosts as a fallback.
  • Added 3 unit tests against a temp hosts file exercising the real file-reading path: alias resolves, missing alias → None, unreadable file → None.

Resolution order is now:

  1. IP literal
  2. Sandbox /etc/hosts (via /proc/{pid}/root/etc/hosts)
  3. Proxy /etc/hosts (host-gateway aliases only) ← new
  4. System DNS

Security properties preserved:

  • SSRF guards unchanged (link-local-only for trusted-gateway exemption)
  • Resolved IPs still flow through full SSRF validation (allowed_ips or exact_declared_endpoint_host path)
  • Cloud metadata, loopback, and control-plane ports still always blocked
  • Fallback only triggers for 3 hardcoded aliases in HOST_GATEWAY_ALIASES

Testing

  • 1011 unit tests pass (cargo test -p openshell-supervisor-network)
  • Clippy clean (cargo clippy --workspace --all-targets -- -D warnings)
  • Format clean (cargo fmt --all -- --check)
  • License headers pass
  • E2E sandbox_reaches_host_openshell_internal_via_host_gateway_aliasPASS (podman, macOS arm64, applehv, bridge network gateway_ip=10.89.0.1)
  • E2E sandbox_inference_local_routes_to_host_openshell_internalPASS
  • E2E inference_set_supports_no_verify_for_unreachable_endpointPASS
  • Manual verification: sandbox with allowed_ips policy reached host server; sandbox without policy correctly denied; non-alias hostname correctly denied

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)

The SSRF engine in 0.0.91 blocks host.openshell.internal because DNS
resolution fails for this synthetic hostname. The sandbox process runs
in a separate mount namespace whose /etc/hosts does not contain the
driver-injected entry, and the system DNS resolver cannot resolve
proxy-internal names.

Add resolve_from_proxy_hosts() as a fallback in the DNS resolution
chain for host-gateway aliases. When the sandbox's /etc/hosts and
system DNS both fail, the proxy reads its own /etc/hosts (the
container-level file populated by the compute driver's --add-host
flag) to resolve the hostname. The resolved IP then flows through
the normal SSRF validation pipeline (allowed_ips or
exact_declared_endpoint_host path) without weakening SSRF guards.

Resolution order is now:
1. IP literal
2. Sandbox /etc/hosts (via /proc/{pid}/root/etc/hosts)
3. Proxy /etc/hosts (host-gateway aliases only)
4. System DNS

Fixes NVIDIA#2478

Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 25, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

- Convert resolve_from_proxy_hosts to async with tokio::fs, matching
  the resolve_from_sandbox_hosts I/O pattern
- Extract path-parameterized resolve_from_hosts_file helper so the
  file-reading path is unit-testable with a temp hosts file
- Log read failures at debug level instead of silently falling
  through to DNS
- Drop redundant lowercase allocation at the alias guard call site
- Replace parser-only duplicate tests with temp-file tests covering
  alias resolution, missing alias, and unreadable file

Signed-off-by: Adel Zaalouk <azaalouk@redhat.com>
@krishicks

Copy link
Copy Markdown
Collaborator

/ok to test 91efad6

@krishicks krishicks self-assigned this Aug 4, 2026
Comment on lines +23 to +24
#[cfg(any(target_os = "linux", test))]
use std::path::Path;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the linter failures I think you can remove both these lines.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the import is needed — it's used by resolve_from_proxy_hosts (line 2846) and resolve_from_hosts_file (line 2856), both behind the same #[cfg(any(target_os = "linux", test))] guard.

The linter failure is the inverse: lines 9900 and 10036 used fully-qualified std::path::Path::new(...) while the import already exists, triggering unnecessary_qualification. Fixed by dropping the std::path:: prefix on those two call sites.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSRF engine blocks host.openshell.internal in 0.0.91 (regression from 0.0.86)

2 participants