Repository navigation
T116 — Carry dns-steered TLS to the proxy on port 443 - #2860
norrietaylor merged 13 commits into
Conversation
A connection whose first byte opens a TLS handshake record takes its authority from the ClientHello's SNI at the port the box connected to, and enters the same admission, termination, substitution and egress re-check as a CONNECT. No SNI, or one outside the admitted set, is closed and audited off_module. The span carries the entry form.
A dns-steered box opens TLS to the proxy's address on port 443. The lane predicate admits TCP there beside the listener's port, for a box that declared a credentialed upstream. UDP 443 at the address stays dropped.
The proxy now takes a dns-steered box's direct TLS on port 443, so the interim DnsWithoutDirectTls refusal goes. BEP-017 still refuses dns and both on a host with no box-zone resolver.
The boxes crate no longer has the DnsWithoutDirectTls refusal. Drop its match arm in min box spec, and expect dns to be admitted like both on an own_ip box on the VM host.
An idle pool slot holds one listener at PROXY_PORT and one at 443, and closes the other when it takes a connection. The share counts a box's connections at both ports as one lane, and the delivery header carries the port the box dialed, so the proxy routes direct TLS by its SNI.
A credentialed lane now admits TCP 443 at the proxy's address. The gate and relay tests that probe a refused port there use 444 instead.
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
|
/revise Two lanes fail on 06b7cbd. Both come from this change.
Run |
The kani_proofs module names PROXY_DIRECT_TLS_PORT in the lane harness but did not import it, so the Kani build of boxes failed with E0425.
BEP-080 opens TCP 443 at the proxy's address to a laned box, so the NET-134 case's silent-drop probe at 443 now connects. Probe 444, a port the lane never opens, and say in the errors that the lane opens the proxy's listener port and TCP 443 only. The proxy-source case's failure diagnostic also cross-probed 443 as an unlistened port; move it to 444 for the same reason.
|
Approved: every review thread resolved at @norrietaylor resolved the last thread, which releases the approval this review had withheld. Nothing to do. |
There was a problem hiding this comment.
Review at 9a133e4 against T116 (#2805) and BEP-080. Arch ruling on this head: CONDITIONS (code conforms, C1 and C2 below).
No blocking defect in the code. I checked these:
serve_innerpeeks one byte, routes0x16todirect_tls, and has a limit on every wait. The SNI is admitted before anything is dialed. No SNI and an outside SNI are both auditedoff_module. Admitted flows shareopen_upstreamandrelay_flowwith CONNECT.bep_hostscreens 443 like 8118, the delivery carries the dialed port, andsettle_twinshas no leak or double count.proxy_lane_admitsadds TCP 443 only. The Kani model matches.DnsWithoutDirectTlsis fully gone.- The out-of-layer files carry only the removal and the 443→444 test probes.
Arch conditions
- C1: in the PR body, change
## Closes #2787 tooto## Refs #2787 (T115). The live run ofbep_dns_steering_through_the_box_zone_resolverhas not happened, and GitHub honours the keyword in a heading. - C2: in
scripts/session-e2e.shproof_bep_dns_steering_through_the_box_zone_resolver, remove the dead interim-refusal handling. That is thegrep -q 'does not yet take a direct TLS connection'branch and itsreturn 3(~13985-13988), plus the twobz_status = 3known_gap branches (~14093, ~14160). Also rewrite the header at 13911-13915, which says KNOWN GAP until #2805.
Low
crates/bep/src/proxy.rs:472-481: the CONNECT path now gets twoHEAD_TIMEOUTwindows (first-byte read, thenread_head), so a slow head holds a slot ~60 s, not 30 s. Use one deadline for both.proxy.rs:713-723: a no-SNI refusal is audited with authority":443".min box auditcannot show that no SNI was sent. The test pins":443".crates/boxes/src/core/egress.rs:130PROXY_DIRECT_TLS_PORTandcrates/switch/src/bep_host.rs:182PROXY_TLS_PORTare not tied. Addconst _: () = assert!(...)beside the 8118 assert atcrates/minvmd/src/net/egress_gate.rs:6377.- The plan's observability line asks for the entry form in the per-request audit record. BEP-039 fixes that record's fields, and the PR puts
entryon the span and the debug line, which is correct. The plan line will be amended. No code change is needed here.
There was a problem hiding this comment.
arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling
The change conforms to Gatehouse §6.10. The proxy takes the authority from the SNI, attribution stays on the switch delivery, and only a box with a declared lane can reach port 443. One gap remains. Line 851 says that the listener ports are the union of the admitted authorities' ports, but this change fixes the port at 443. A dns-steered box with a store authority on another port is steered to the proxy and then dropped. Do C1: file the issue, include the spec amendment for BEP-080, and name the issue in the PR body. The live VM-host proof stays open under #2787.
Ruled at 9a133e4 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
The proxy now takes a direct TLS connection, so admission no longer refuses dns steering with a credentialed upstream. Drop the KNOWN GAP handling of that refusal from the dns-steering case: any admission refusal there is now a hard failure.
The first-byte peek and the head read each took a fresh HEAD_TIMEOUT window, so a box could hold a connection open for twice the limit. Take one deadline when the connection is accepted and use it for both reads, so HEAD_TIMEOUT is the total time to send the first head.
A ClientHello with no server name was audited with the authority ":443", which reads as a blank host in `min box audit`. Record it as `(no SNI):443` instead. No DNS name holds a parenthesis or a space, so no SNI a box sends can produce the same authority. The record keeps its BEP-039 fields.
boxes::core::egress::PROXY_DIRECT_TLS_PORT and switch::bep_host::PROXY_TLS_PORT both hold 443 with nothing tying them. Assert they agree beside the listener-port assert, as minvmd is the one crate that sees both.
There was a problem hiding this comment.
arch-review: PASS_WITH_CONDITIONS · 1 condition · the full ruling
The compare closes the gaps it touches. One deadline now bounds the head read. A refusal with no SNI is recorded as (no SNI):443. The e2e case fails hard on any refusal. The proxy, relay and listener conform to Gatehouse §6.10 lines 819, 823, 825 and 833. BEP-080 is not defined in spec 24, and BEP-029 does not cover direct TLS. Make the spec edit in C1. The live VM proof stays open on #2787.
Ruled at d5b3b0a against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
There was a problem hiding this comment.
minimal-review — approval withheld
Verdict COMMENT — APPROVE withheld: 4 unresolved threads — minimal-foundry[bot] crates/bep/src/proxy.rs, minimal-arch[bot] crates/switch/src/bep_host.rs:185, minimal-arch[bot] crates/boxes/src/core/egress.rs:130, minimal-arch[bot] crates/bep/src/proxy.rs:692. No findings.
Panel: 3 of 3 personas ran (correctness, security, supply-chain).
There was a problem hiding this comment.
minimal-review — approved
Verdict APPROVE — every review thread is resolved; @norrietaylor closed the last one. The review of d5b3b0a55e3a withheld this approval on the threads open then. Posted as a comment: the App cannot approve its own pull request; the check and the label carry it.
There was a problem hiding this comment.
Re-reviewed 9a133e4..d5b3b0a against review 5756401304 and the arch conditions. One HEAD_TIMEOUT deadline covers the first byte and the head, with a paused-clock test. A no-SNI refusal is recorded as (no SNI):443 (audit::NO_SNI). The two 443 constants are tied at compile time. The e2e known-gap branches are removed. Residuals #2867, #2857 and #2787 are listed. BEP-080 is defined on main by #2851. All lanes are green.
T116 — Carry dns-steered TLS to the proxy on port 443
slice: S16
kind: task
covers: BEP-080
size: L
est: 320 code, 380 tests
executor: agent
difficulty: needs care
model: standard
diagnostics:
min box audit <box>shows a request that arrived as direct TLS on port 443 with the SNI authority it was routed to, and a refused one markedoff_module; adns-steered GitHub grant box on a VM host is created, where before it was refused with exit 3 (#2805)observability: the proxy's existing per-request audit record and its debug line carry the entry form,
connectordirect_tls, beside the authoritylayers:
- proxy:
- crates/bep/src/proxy.rs: a connection whose first bytes are a TLS ClientHello, not a CONNECT or an absolute-form request, takes its authority from the SNI and enters the same termination, substitution and egress re-check a CONNECT to that authority enters; no SNI, or an SNI outside the module host sets and the registered store authorities, is refused
off_moduletest: cargo nextest run -p bep direct_tls_on_443_routes_by_sni
test: cargo nextest run -p bep direct_tls_without_admitted_sni_is_refused_off_module
- listener:
- crates/switch/src/bep_host.rs: the proxy's address takes TCP connections on port 443 beside
PROXY_PORT, delivered to the proxy with the same box attribution, and the pool counts both ports as one lanetest: cargo nextest run -p switch proxy_address_delivers_port_443_with_box_attribution
- relay:
- crates/boxes/src/core/egress.rs:
proxy_lane_admitsadmits TCP 443 at the proxy's address for a box that declares a credentialed upstream, beside the listener's port, and nothing else; UDP 443 there stays rejectedtest: cargo nextest run -p boxes proxy_lane_admits_tls_443_and_listener_port_only
- activation:
- crates/boxes/src/core/bep.rs: the interim refusal
DnsWithoutDirectTlsand its test go, sodnsis admitted wherever the host factbox_zone_resolveris truetest: cargo nextest run -p boxes dns_steering_is_admitted_with_the_resolver_and_no_interim_refusal
produces: none
consumes: none
proof: On a VM host, a box with
steering = "dns"and no proxy variables opens TLS to github.com, the proxy routes it by its SNI and substitutes the sealed value, and a ClientHello naming another host is refused and auditedoff_module.Background, from the issue as first filed
Under
dnssteering the box-zone gate answers a credentialed name, such as github.com, with the Box Egress Proxy's address, andHTTPS_PROXYis not set (BEP-012). The client then opens TCP 443 to that address. Today nothing on that path can carry the connection:proxy_lane_admits,crates/boxes/src/core/egress.rs). TCP 443 there is dropped asUncredentialedProxyDestination.crates/switch/src/bep_host.rs,PROXY_PORT). It accepts only CONNECT or absolute-formhttp://requests (crates/bep/src/proxy.rs), and never reads a bare TLS ClientHello.So
dnssteering stays refused with exit 3 (BEP-017) even on the VM host. Under the owner's decision of 2026-10-31 on #2797, T113 admits onlyboththere.To admit
dns, the work is:dnsinhost_resolves_box_namesonce both are in place.dnssteering with no proxy variables in the box.Epic #1501. Spec:
docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md(BEP-016, BEP-017, BEP-062, BEP-078).Refs #2787 (T115)
When this lands, run
./scripts/session-e2e.sh bep_dns_steering_through_the_box_zone_resolver(added by #2829) on a macOS VM host with the live proxy. Thestoreandfail_closedlegs must print PASS, andgitmust print PASS where a GitHub sign-in andE2E_BEP_PRIVATE_REPOare set. T115 (#2787) stays open until that run passes, and is then closed by hand from a comment that links the run.Proof
In the tests on this branch, a box connection on port 443 that opens with a TLS ClientHello for api.github.com, with no CONNECT and no proxy variables, completes TLS under the interception root. The proxy dials the upstream that the SNI names, and the upstream gets the real credential in place of the sealed value. A ClientHello for example.com, or one with no SNI, is closed with nothing dialed, and the audit log that
min box auditreads records itoff_module. The switch delivers port 443 at the proxy address with the box's own id, and the relay admits TCP 443 there for a credentialed lane. Adns-steered GitHub grant box on a VM host (own_ip) is now admitted where before it was refused with exit 3. No run on a real VM host took place in this session, so the end-to-end proof with github.com is still owed tojust e2e(proof_bep_dns_steering_through_the_box_zone_resolver).Notes from the agent: Files touched outside the layers, each one a direct result of the task:
BepRefusal::DnsWithoutDirectTlsmatch arm. Without this change the crate does not compile after the variant goes. Also updated one stale T116 — Carry dns-steered TLS to the proxy on port 443 #2805 comment.dns_steering_is_admitted_where_the_switch_resolves_boxesasserted the interim refusal text. It now expectsdnsto be admitted likeboth. Also updated the stale T116 — Carry dns-steered TLS to the proxy on port 443 #2805 doc comment onhost_resolves_box_names.credentialed_box_dropped_at_proxy_address_other_port) and crates/minimald/src/net/switch.rs (compiled_egress_carries_the_credentialed_lane) used TCP 443 at the proxy address as the refused 'other port' for a laned box. They now probe 444. Both tests pass, and so do the minvmd and minimald egress, bep and proxy filters (196 and 177 tests).Inside the layers:
kani_proofsrestates the lane, so it now admits 443 too. I did not run Kani (lane ci-kani). `sw…[truncated]Tests
cargo nextest run -p bep direct_tls_on_443_routes_by_snicargo nextest run -p bep direct_tls_without_admitted_sni_is_refused_off_modulecargo nextest run -p switch proxy_address_delivers_port_443_with_box_attributioncargo nextest run -p boxes proxy_lane_admits_tls_443_and_listener_port_onlycargo nextest run -p boxes dns_steering_is_admitted_with_the_resolver_and_no_interim_refusalVerify
min task run agent-verifyexited 101 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.Out of scope
These files are outside the task's
layers:, so this PR is a draft:crates/minimal/src/cmd/box_spec.rscrates/minimal/src/cmd/boxes/create.rscrates/minimald/src/net/switch.rscrates/minvmd/src/net/egress_gate.rsCloses #2805
Residuals