Replies: 1 comment
|
Verified the finding and the proposal against master (99f6f02) — the spoofing claim is real, and I want to add the precise mechanism, the impact mapping, and two hardening suggestions. 1. The finding verifies at source (and it's the precondition inversion, not a fence bug)
The precise failure mode is a precondition inversion of the DNS-rebinding defense. The fence is sound under its original assumption: the socket is reachable only by local processes, and a browser fills Host from the URL it believes it is talking to, so a rebound page cannot forge it. A Host-preserving proxy breaks both legs: it extends socket reachability to the tailnet, and it restores client control of Host. The loopback branch then degenerates into a remotely-typeable password. Not a bug in the fence's own terms — but the deployment pattern (loopback bind + Host-preserving proxy) is precisely where the fence's guarantee evaporates, and nothing else stands behind it. 2. The impact is not theoretical — the 15 methods are the whole configuration planeThe pin protects: 3. The proposal: right shape, two hardening suggestionsThe design is sound: proxy-attested identity as an explicit auth supplement, opt-in, empty default bit-identical to today, exact-match allowlist, and the hard precondition. Two suggestions: A. Make the identity grant an AND with the existing Host fence, not an OR replacement. The identity header is authentication; the Host fence is the rebinding/CSRF defense — different axes, both should hold. A privileged request should pass when (existing Host fence passes with the deployment's declared B. The hard precondition needs a second leg: header-origin hygiene. The allowlist trusts the header name, and the guarantee rests entirely on "the proxy overwrites client-supplied values" — true for tailscale serve (verified) but not for nginx/Caddy by default, which neither emit Also note the bind schema in master: 4. Cross-links and the overall picture
I'd be glad to see both as PRs when the channel opens — the security model (fence = rebinding defense, proxy = identity authority, both must hold) is the right end state, and the empirical verification (header overwrite + Host passthrough) is exactly the evidence these threads need. |
Uh oh!
There was an error while loading. Please reload this page.
The situation.
PRIVILEGED_METHODSmust pass the trust fence with an empty list, pinning them to loopback Hosts "until a real authentication layer exists." Behind a reverse proxy that preservesHost(tailscale serve does, verified), that means the entire configuration plane 403s for remote users: Settings (permissions, plugins), credentials, model discovery, agent-preset management. In our deployment the UI sections render empty and the operator falls back to an SSH tunnel — the exact workflow the proxy was supposed to replace.A finding relevant to the current fence. While testing we found that
tailscale servepasses a client-suppliedHostheader verbatim to the backend. Consequence: any tailnet member who can reach the serve port can sendHost: 127.0.0.1:3080and satisfyisTrustedApiRequest(request, [])today — the privileged pinning is spoofable behind any Host-preserving proxy. Not a bug in the fence's own terms (it is a rebinding defense, sound when the client controls its own Host) — but it means the pinning provides less than it appears to in proxied deployments.The missing authentication layer can come from the proxy. Some proxies attest identity rather than forward it.
tailscale serveinjects on every proxied request:We verified empirically that client-supplied
Tailscale-User-Loginvalues are overwritten with the verified login (a spoofedattacker@evil.comarrived as the real one), andX-Forwarded-Foris likewise forced. On the loopback bind dsh already requires by default, nothing but the proxy can reach the socket — so that header is unforgeable attestation of who is calling.The proposal (implemented, tested). Opt-in config on the connection row:
A privileged-method request whose proxy-attested login is listed is treated as operator-equivalent; the empty default is bit-identical to today. Deliberately narrow:
Residual trust to review: the proxy joins the trusted computing base; other proxies emitting the same header inherit the same semantics (the loopback bind, not the proxy brand, carries the guarantee — worth documenting); the allowlist is operator-maintained.
Tested change: connection suite 110/110 green (4 new tests),
tsc -bclean. Branch: mrlfarano:identity-authority · diff: compare with master (4 files, +123/−8, tests included).Companion to the
externalUrlproposal (#3209) — together they make a loopback-bounddsh webbehindtailscale servea complete remote deployment: correct narration, working Settings, and a stricter effective privilege boundary for the 15 pinned methods than the status quo behind such proxies.(PR creation appears restricted to collaborators, so posting here per CONTRIBUTING — glad to turn this into a PR whenever the team wants it.)
All reactions