Conversation
|
All contributors have signed the DCO ✍️ ✅ |
|
Thank you for your interest in contributing to OpenShell, @Joffref. This project uses a vouch system for first-time contributors. Before submitting a pull request, you need to be vouched by a maintainer. To get vouched:
See CONTRIBUTING.md for details. |
|
This is a good start but I'd like to see some security hardening and better testing:
|
|
I have read the DCO document and I hereby sign the DCO. |
|
recheck |
…rnel Point the upstream IPv6 policy DNS references at NVIDIA/OpenShell#3702 and issue #3716 instead of the fork branch, and note that the PR also fixes the NAT64 SSRF gap. Add a control sandbox section to KERNEL.md: why it uses the tun + iptables variant, and how to check TUN and ip6tables NAT since the guest exposes no kernel config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mediated policy DNS always answered AAAA queries with NOERROR/NODATA and
resolved A queries upstream as A only. On IPv6-only hosts behind
NAT64/DNS64 the trusted resolver returns no A records, so every
policy-allowed name failed with policy_dns_upstream_no_data and native
TCP egress was unusable.
Add a driver-owned supervisor flag, --policy-dns-ipv6-egress
{auto,enabled,disabled}. The default auto mode enables AAAA answers only
when the supervisor network namespace has an IPv6 default route and no
IPv4 default route, so dual-stack and IPv4-only hosts keep the current
A-record fallback. AAAA answers use the existing epoch-scoped synthetic
IPv6 pool and are pinned and dialed through the same resolved-endpoint
store and destination validation as IPv4 answers.
Signed-off-by: Joffref <mjoffre@blaxel.ai>
…ings A DNS64 answer like 64:ff9b::a00:5 is really 10.0.0.5, but the SSRF checks treated it as a public IPv6 address. Addresses inside a NAT64 prefix are now checked as the IPv4 address they embed, in policy DNS and in the CONNECT path. The well-known prefix is always known; other prefixes come from the new nat64_prefixes driver setting, or from ipv4only.arpa discovery when the supervisor starts. policy_dns_ipv6_egress and nat64_prefixes can now be set in the Docker, Podman, Kubernetes and VM driver configs. Before this, only auto was reachable in practice. The supervisor logs its IPv6 egress decision (requested mode, result, default routes, route state) and the NAT64 prefixes it uses as OCSF config events. Tests: route detection fixtures per backend, a start_mediated test with a fake mediation source and DNS64 upstream, driver arg tests, and an e2e check of the decision event. Signed-off-by: Joffref <mjoffre@blaxel.ai>
…S runtime The listener-based runtime still started with IPv6 answers off, so an explicit mode only reached the mediated runtime. Resolve the mode once and use it for both. Signed-off-by: Joffref <mjoffre@blaxel.ai>
…swers allowed_ips entries now match a NAT64 address through the IPv4 address it embeds, so 10.0.0.0/8 covers 64:ff9b::a00:5 the same way it covers 10.0.0.5. Loopback, link-local and metadata stay blocked first. The mediated IPv6 test now also checks that these are denied: the real upstream IPv6, an unallocated synthetic address, the right address on the wrong port, an open without a binary identity, and the old mapping after a policy reload. A second test covers an unreachable trusted resolver. The store gets the IPv6 version of the wrong port / stale generation / expiry test. Signed-off-by: Joffref <mjoffre@blaxel.ai>
94841e8 to
f7f1f99
Compare
johntmyers
left a comment
There was a problem hiding this comment.
gator-agent
PR Review Status
Thanks @johntmyers; I checked the NAT64/IPv4 SSRF parity concern against the new registry and found one ordering defect that can select the wrong embedded IPv4 address when prefixes overlap. The linked maintainer-authored issue makes the cross-cutting change project-valid, and the Fern documentation covers the new operator settings.
Action required: @Joffref, make NAT64 classification choose the most-specific matching prefix and add the overlapping-prefix regression coverage described inline.
Blocking findings:
GATOR-f7f1f994-01: overlapping NAT64 prefixes are interpreted in registration order rather than by the route's most-specific prefix.
Carried findings:
- None
Non-blocking suggestions:
GATOR-f7f1f994-02: extend E2E coverage beyond startup diagnostics to exercise sandbox AAAA resolution and the native IPv6 TCP relay path; this does not block the current revision.
Gator metadata
- Validation: Project-valid through maintainer-authored issue #3716, which defines the IPv6 policy-DNS contract and acceptance criteria.
- Docs: Fern configuration and network-policy documentation updated; navigation changes are not needed for edits to existing pages.
- Checks: DCO and available current-head checks pass, but code review has a blocking finding and required branch gates are not yet complete.
- E2E:
test:e2eis required for the network-policy and sandbox-runtime behavior; dispatch is deferred until blocking review feedback is resolved. - Head SHA:
f7f1f9943d1a932d738def0750f912ba69886104 - Base SHA:
c9da461a588f04b4ae1cc9f46aff98e30ca1bf44 - Merge base SHA:
c9da461a588f04b4ae1cc9f46aff98e30ca1bf44 - Patch ID:
acb665354181ae083ca011ac010062fdd27207f9 - Gator payload:
9 - Review mode:
initial - Previous reviewed SHA: none
- Review budget exhausted: no
- Maintainer decision required: no
- Next state:
gator:in-review
embedded_ipv4 returned the first registered prefix that contained the address. With a configured 2001:db8::/32 and a later-discovered 2001:db8:8c52:7003::/96, 2001:db8:8c52:7003::7f00:1 was read through the /32 as 140.82.112.3 and passed the SSRF checks, while the /96 route translates it to 127.0.0.1. Pick the longest matching prefix, the well-known prefix included, the same way the route that carries the packet does. The tests register the broad prefix first and check loopback, metadata and private destinations under the nested one. They use RFC 9637 documentation space because the registry is process-wide and a 2001:db8::/32 would change how other tests classify their 2001:db8 addresses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Joffref <mjoffre@blaxel.ai>
|
Fixed in 1134d60: embedded_ipv4 now picks the longest matching prefix (well-known prefix included). The regression tests register the broad /32 first, then the nested /96, and check that loopback and metadata stay always-blocked and private addresses stay internal under the /96. I used RFC 9637 3fff:: space instead of 2001:db8::/32: the registry is process-wide, so a 2001:db8::/32 would change how other tests running in parallel classify their 2001:db8 addresses. The E2E suggestion (GATOR-02) is left for a follow-up. |
|
Label |
|
/ok to test 1134d60 |
Re-check After Author UpdateThanks @Joffref. I re-evaluated latest head What I checked: the author-only delta now selects the longest matching NAT64 prefix, including the well-known prefix, and its regression coverage registers the broad prefix first before checking loopback, metadata, private, and allowed-network classification through the nested prefix. The independent follow-up review found no new blocking issues, and the prior Gator thread is resolved. Disposition: resolved. Remaining items:
Gator metadata
|
…test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Joffref <mjoffre@blaxel.ai>
PR Review StatusThe follow-up review found no blocking issues in the current-head delta: the test-only duration rewrite is behaviorally equivalent, and the previously resolved NAT64 prefix finding remains resolved. Blocking findings:
Carried findings:
Gator metadata
|
|
/ok to test 9ab8076 |
Summary
On IPv6-only hosts (NAT64/DNS64), policy DNS never resolved an allowed name because AAAA always got an empty answer. This adds a
policy_dns_ipv6_egresssetting (autoby default, which only enables AAAA on IPv6-only supervisor namespaces), and makes the SSRF checks treat a NAT64 address as the IPv4 address it embeds, so IPv6 answers get the same protections as IPv4 ones.Related Issue
Closes #3716
Changes
NAT64 classification (
openshell-core::net::nat64): RFC 6052 extraction for every prefix length, RFC 7050 discovery fromipv4only.arpa, and an append-only registry of network prefixes. The SSRF checks use it:is_internal_ipandis_always_blocked_ip;allowed_ipsmatching, so10.0.0.0/8covers64:ff9b::a00:5like it covers10.0.0.5.64:ff9b::/96is always known. The rest of64:ff9b:1::/48counts as internal.Driver settings:
policy_dns_ipv6_egress(auto/enabled/disabled) andnat64_prefixesin the Docker, Podman, Kubernetes and VM driver configs. The drivers validate them at startup and pass them on the supervisor argv (--policy-dns-ipv6-egress,--nat64-prefix).Supervisor startup: configured NAT64 prefixes are registered, and discovery runs, before any egress path starts. The IPv6 mode is resolved once from
/proc/net/routeand/proc/net/ipv6_routeand applied to both policy DNS runtimes.OCSF: one config event for the IPv6 decision (requested mode, result, IPv4/IPv6 default route,
route_state:ipv6_only,dual_stack,ipv4_only,no_default_routeorroute_table_unavailable). A second one for the NAT64 prefixes in use.Docs:
docs/how-it-works/gateways/configuration.mdx,docs/how-it-works/policies/overview.mdx,architecture/sandbox.md.Testing
Unit tests pass for the touched crates:
openshell-core,openshell-supervisor-network,openshell-supervisor, and the Docker, Podman, Kubernetes, VM and gateway crates. The Linux-only runtime path builds forx86_64-unknown-linux-gnu.New tests:
NAT64: RFC 6052 examples at all six lengths, discovery, and the SSRF predicates and
allowed_ipsmatching with NAT64 addresses (well-known, configured, local-use, CIDRs).Route detection: fixtures for the supervisor namespace of each backend:
Each is covered IPv4-only, dual-stack and IPv6-only where it applies, plus unreadable tables and down/reject routes.
mediated_policy_dns_ipv6_end_to_end:PolicyDnsRuntime::start_mediatedwith a fakeNetworkMediationSourceand a fake DNS64 upstream. It covers:allowed_ips, wildcard, metadata, configured prefix).Resolver failure: a second mediated test with an unreachable trusted resolver.
Store: IPv6 wrong port, stale generation and expiry.
Drivers: each driver renders the supervisor args from its config.
E2E:
e2e/rust/tests/policy_dns_ipv6_egress.rschecks the decision event on each driver lane.OPENSHELL_E2E_EXPECT_ROUTE_STATEpins the expected state on lanes with a known network. It compiles, but I haven't run it on a driver lane.Not covered yet: the sandbox-side path from an IPv6 socket to the relay, and an IPv6-only CI lane (both tracked in feat(network): support policy DNS IPv6 egress across the sandbox–supervisor boundary #3716).
mise run pre-commitpasses. mise isn't installed here, so I ran the same checks by hand:cargo fmt --all --check, the SPDX header check, and clippy on the touched crates.Unit tests added/updated
E2E tests added/updated (if applicable)
Checklist