Skip to content

feat(broker): route fduty egress through the runner broker#35

Merged
ysyneu merged 5 commits into
mainfrom
feat/egress-broker
Jun 8, 2026
Merged

feat(broker): route fduty egress through the runner broker#35
ysyneu merged 5 commits into
mainfrom
feat/egress-broker

Conversation

@ysyneu

@ysyneu ysyneu commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Route fduty egress through the runner broker

When the runner provides a broker control fd (FLASHDUTY_CRED_FD), the CLI sends every API request over that inherited fd instead of dialing the upstream directly, with a sentinel app_key the broker overwrites. This keeps the real per-person key out of the sandbox/bash environment entirely.

What changes

  • New internal/cli/broker_dial_unix.go (//go:build unix): a *http.Client whose Transport.DialContext performs a per-dial SCM_RIGHTS handshake on the control fd — sends a 1-byte request, receives a dedicated SOCK_STREAM fd via SCM_RIGHTS, wraps it with net.FileConn. Per-dial (not once at startup) so keep-alive reconnects and pagination loops each get a fresh connection. A mutex serializes send+recv on the control fd so concurrent requests never cross fds. MaxIdleConnsPerHost=1 caps lingering dispatched conns (all dials target the same sentinel host).
  • internal/cli/broker_dial_other.go (//go:build !unix): compile-only stub.
  • internal/cli/root.go defaultNewClient: when FLASHDUTY_CRED_FD is set (and >= 3 — fds 0/1/2 are stdio and rejected), inject the broker client via flashduty.WithHTTPClient and use a sentinel app_key (go-flashduty rejects empty keys and always appends ?app_key=; the broker overwrites it). When both a configured app key and a control fd are present, broker mode wins. No change to the go-flashduty SDK (consumer logic stays in the CLI, per the 1:1 rule).

Verification

  • TestBrokerHTTPClient_DialAndRewrite + TestDefaultNewClient_BrokerMode pass natively (unix syscalls work on darwin); 8 concurrent requests each get their own dispatched connection and the upstream sees the real (rewritten) key.
  • TestDefaultNewClient_RejectsStdioFD: fds -1/0/1/2 are rejected. TestBrokerHTTPClient_RefusedReturnsError: a 0xFF broker refusal surfaces as an error (no hang). Both-keys-set takes the broker path.
  • Live: the modified fduty ran inside a BYOC runner and authenticated against the real pgy purely via the broker fd (no env key present).
  • Cross-compiles for linux, darwin, windows.

ysyneu added 5 commits June 8, 2026 00:04
- FLASHDUTY_CRED_FD must be >= 3: fds 0/1/2 are stdio and can never be
  the runner-injected control end, so reject them instead of handshaking
  on stdin/stdout.
- MaxIdleConnsPerHost=1 on the broker transport: all dials target the
  same sentinel host over the one control fd, so a single idle keep-alive
  conn suffices and dispatched conns don't linger.
- Tests: stdio/invalid fd rejection, the 0xFF broker-refusal path surfaces
  as an error (no hang), and both-keys-set still takes the broker path
  (the sentinel, not the configured app key, reaches the wire).
First broker PR, so the linter surfaces the original test helpers too.
Wrap the deferred syscall.Close / conn.Close calls so errcheck passes.
The CLI broker tests deadlocked on linux-amd64 CI (10m timeout) while
passing on macOS/Windows: a bare close() does not interrupt a recvmsg
blocked on that fd in another goroutine on Linux (it does on darwin/BSD).
fakeBroker and TestBrokerHTTPClient_RefusedReturnsError join their control
goroutine after teardown, so they hung waiting for a recvmsg that never
returned. Use syscall.Shutdown(SHUT_RDWR) to wake the blocked recvmsg
portably, then join, then close.

Verified: the cross-compiled linux/arm64 test binary runs clean (count=2)
in an ubuntu container; native darwin still passes.
@ysyneu ysyneu merged commit a239892 into main Jun 8, 2026
12 checks passed
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.

1 participant