Skip to content

egress: write the host's lane verdict into every NIC-bearing guest - #171

Merged
CMGS merged 1 commit into
mainfrom
feat/egress-lane-file
Sep 14, 2026
Merged

CMGS merged 1 commit into
mainfrom
feat/egress-lane-file

Conversation

@CMGS

@CMGS CMGS commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Stacked on #170 (base feat/egress-rule-ports); merge order #169 → #170 → this → the review PR that follows. One commit of its own. Closes #167.

The desktop image's guest-proxy-setup decided from NIC presence, so on a guarded bridge lane the GNOME session and osworld-server got neither a route nor the relay; and the boolean marker #165 introduced only said "locked", which left a NIC-bearing guest no way to tell "the host has not decided yet" from "the host says direct".

Change

  • sandboxd/engine/lane.go replaces nicmark.go: Lane (relay | direct) and Engine.MarkLane write /etc/silkd-lane through the existing fs_write path.
  • sandboxd/pool: laneOf(key) is empty on the none lane (no NIC to route through), relay when the lane is locked (bridge), else direct (CNI); markLane writes it at the same three sites the marker used (golden build after the readiness probe, cold provision, the late reconcile lock). The .nic golden sidecar stores the word, so a golden built under another verdict — including one built by the marker version — is rebuilt, not adopted; none-lane goldens keep matching. Engine.MarkLane replaces MarkNICLocked; fake engines follow.
  • silkd/src/net.rs: routes_directly() is true only for a NIC whose lane file does not read relay; an absent or unreadable file routes directly, so an old sandboxd that writes nothing leaves the guest as it was. Unit test covers absent / direct / relay.
  • os-image/desktop/24.04/Dockerfile: guest-proxy-setup still configures the relay at once when there is no NIC; with a NIC it waits for /etc/silkd-lane, follows relay, and exits on direct. The unit already ran After=silkd.service and Before=desktop-session.service osworld-server.service, so the session and osworld-server start after the verdict.
  • e2e/cmd/desktopsmoke gains -net none|egress and -probe: on the egress lane the checkpoint/branch leg (refused on the lane) gives way to a /execute leg that requires http_proxy in the session's environment and a 200 from the allowed origin through the relay; the /execute parsing moved into one execute helper used by both legs.
  • Docs: docs/silkd.md, docs/egress.md (lane paragraph), docs/desktop.md (lane bullet, unit bullet); desktop pools may use the egress lane again.

Decisions

  • A two-word file instead of the boolean marker plus a timeout: "no file after N s" as the direct signal would freeze a wrong verdict into a golden and every clone of it when the host probe is slow; with direct written explicitly, a timeout fires only for an old sandboxd.
  • The none lane gets no file: both consumers already treat no NIC as relay, and a file there would be a third source of the same fact.
  • The reconcile late-lock case (a live claim whose node flipped CNI → bridge) is written but the running desktop session is not re-configured: a re-claim heals it, deploy.md makes draining claims part of upgrading, and silkd's execs follow the file at once.
  • The marker is renamed, not kept alongside: egress: tell the guest when its NIC is locked so the bridge lane gets the proxy variables #165 shipped in no release tag.

Codex's review of this branch raised one MAJOR: a 30 s guest wait is shorter than the host's own worst case (90 s probe plus a 2-minute write timeout), so a late relay could land after the desktop had configured itself and been snapshotted. That is resolved in the review PR stacked on this one, the way the previous batch's round fixes rode #168 on #165: the wait is 300 s (longer than the host's success window, so a guest still waiting belongs to a boot the host already failed), the timeout exits non-zero so a mis-timed build is visible, the NIC test matches silkd's device-backed probe, and desktopsmoke asserts the unit is active. Merge this PR together with the review PR.

Hot path

None on the claim path: the write happens at golden build, cold provision and the late reconcile lock. silkd's per-exec cost on a NIC-bearing guest is one small read (the marker was one stat; measured below noise: 750 vs 745 µs exec RTT); the review PR latches a relay verdict so the bridge lane pays it once. Nothing on the none lane.

Tests and evidence

  • TestLaneVerdictReachesGuestOnColdBoots (locked / unlocked egress lane and none lane, golden and cold), TestGoldenNICSidecarGatesAdoption; silkd lane_file_decides_relay.
  • Gates: make go-lint 10 × 0 issues.; asl -forwarder=false clean on both GOOS; go test engine/pool; go vet ./... e2e; silkd cargo fmt --check, clippy -D warnings, 111 tests on macOS and 111 in a rust:1.98.0 linux/arm64 container.
  • Hardware, cocoon-test1, guest images carrying this silkd (rt-lane4, desktop-lane4 = desktop:24.04 + this silkd + this guest-proxy-setup): scripts/egress-e2e.sh NET=none PASS and NET=egress BRIDGE=sbxbr0 PASS (direct egress blocked by the lock, allowed origin reached with and without -x with the injected credential, denied host 403); scripts/sandboxd-e2e.sh BRIDGE=sbxbr0 PASS including restart re-adoption; desktopsmoke -net egress PASS on a 2xlarge egress-lane desktop pool with a policy (screenshot, AT-SPI tree, PyAutoGUI click, then: the session's environment carries the relay and the allowed origin answers 200 through it, audited as a GET on port 80); desktopsmoke -net none PASS with the checkpoint/branch leg.

@CMGS
CMGS force-pushed the feat/egress-rule-ports branch from 5e163a3 to e8176f8 Compare September 14, 2026 06:54
@CMGS
CMGS changed the base branch from feat/egress-rule-ports to main September 14, 2026 06:55
The desktop image's guest-proxy-setup decided from NIC presence, so on a
guarded bridge lane the GNOME session and osworld-server got neither a
route nor the relay, and silkd's marker only said "locked", which left a
guest with a NIC no way to tell "the host has not decided yet" from "the
host says direct". sandboxd now writes /etc/silkd-lane with the verdict,
relay on a locked bridge lane and direct on a CNI lane, at the same three
sites the marker used (golden build, cold provision, late reconcile
lock); the golden sidecar records the word so a golden built under
another verdict is rebuilt. silkd routes directly only when the file does
not read relay, one 8-byte read per exec on a NIC-bearing guest, nothing
on the none lane. guest-proxy-setup waits up to 30 s for the file on a
NIC-bearing guest, follows relay, and treats direct or a silent host as
routing. desktopsmoke gains -net egress with a fetch through the
session's proxy environment in place of the checkpoint leg, which the
lane refuses. The old marker shipped in no release, so it is renamed
rather than kept.

Closes #167
@CMGS
CMGS force-pushed the feat/egress-lane-file branch from 11e2fcb to fe3416d Compare September 14, 2026 06:56
@CMGS
CMGS merged commit a5a868e into main Sep 14, 2026
2 checks passed
@CMGS
CMGS deleted the feat/egress-lane-file branch September 14, 2026 06:59
CMGS added a commit that referenced this pull request Sep 14, 2026
…te (#173)

Batch-end round on the state with #169, #170 and #171 merged, scoped by the hygiene ledger (0 files due after) plus an ownership pass over all of silkd. Fixed with tests: hostPort defaulted the port it could not parse and serveConnect dialed the raw authority, so a path-form CONNECT with Host: example.com:+22 was judged as 443, allowed by a ports rule and dialed to port 22; every malformed authority (out-of-range port, two ports, stray or unclosed brackets, empty host) is now a deny with a no-port audit line, the dial and the intercept authority come from the parsed pair, and serveSocks refuses port 0. The SOCKS5 door becomes the pool policy's call (the tenant's rules already gate every tunnel), ServesSocks trusts Validate, the desktop unit's wait outlives the host's probe and write timeouts and fails visibly on timeout, silkd latches a relay verdict and serves base_env from a static slice, deploy docs gain the daemon/image lockstep and the strict-config rollback. Round net prod +34 / test +88, comment lines +5 / -8; every gate green on both platforms, hardware suites and the desktop lane PASS on cocoon-test1; Codex converged after five rounds.
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.

desktop: guest-proxy-setup should follow the lane the host decides, not NIC presence

1 participant