Skip to content

cmd/tailscale/cli: use the tcp scheme for tls-terminated-tcp set-config targets - #21789

Merged
bcreane merged 2 commits into
tailscale:mainfrom
Vivek1898:fix/21786-serve-set-config-tls-terminated-tcp
Oct 16, 2026
Merged

bcreane merged 2 commits into
tailscale:mainfrom
Vivek1898:fix/21786-serve-set-config-tls-terminated-tcp

Conversation

@Vivek1898

Copy link
Copy Markdown
Contributor

Fixes #21786.

tailscale serve set-config rejected every tls-terminated-tcp endpoint:

service "svc:obs-query": failed to apply TCP serve: unable to expand target: must be a URL starting with one of the supported schemes: [tcp unix]

runServeSetConfig builds each non-file, non-unix target as
fmt.Sprintf("%s://%s", ep.Protocol, …), so a tls-terminated-tcp endpoint
becomes the target tls-terminated-tcp://localhost:9481. But TLS termination
is carried by the serve type, not by the proxy target: applyTCPServe
derives terminateTLS from serveTypeTLSTerminatedTCP and then calls
ipn.ExpandProxyTargetValue(target, []string{"tcp", "unix"}, "tcp"), which
refuses that scheme. The flag form already passes the pair the expander
expects — --tls-terminated-tcp=443 tcp://localhost:9481 — which is why it
works and the config file does not.

So this maps the protocol to the tcp scheme for that one case, leaving the
serve type to carry TLS termination.

https+insecure deliberately needs no such mapping: the web path expands with
{"http", "https", "https+insecure", "unix"}, so the protocol name is already
a valid target scheme there. Unix targets were unaffected because they skip the
scheme construction entirely. tls-terminated-tcp was the only protocol whose
name is not accepted as a target scheme by the path it reaches.

Testing

Added TestRunServeSetConfig/tls_terminated_tcp_roundtrip, which applies the
config from the issue and then round-trips it through get-config, following
the existing http_over_unix_roundtrip subtest.

Without the change it fails with the reported error verbatim:

serve_v2_test.go:2753: set-config: service "svc:foo": failed to apply TCP serve: unable to expand target: must be a URL starting with one of the supported schemes: [tcp unix]

With it, the endpoint applies as TCPForward: "localhost:9481" with
TerminateTLS: "foo.test.ts.net", and get-config reproduces
"tcp:443": "tls-terminated-tcp://localhost:9481".

go test ./cmd/tailscale/cli/, go vet ./cmd/tailscale/cli/ and gofmt are
clean (Go 1.27.2, linux/amd64).

…ig targets

runServeSetConfig built every non-file, non-unix endpoint target as
"<protocol>://<host:port>", so a tls-terminated-tcp endpoint produced the
target tls-terminated-tcp://host:port. TLS termination is carried by the serve
type rather than the target, though: applyTCPServe derives terminateTLS from
serveTypeTLSTerminatedTCP and then expands the target allowing only
{tcp, unix}, so every such endpoint failed with "unable to expand target".
The flag form already passes the pair the expander expects,
--tls-terminated-tcp=<port> tcp://host:port.

Map the protocol to the tcp scheme for that one case. https+insecure needs no
mapping, since the web path accepts it as a target scheme, and unix targets
skip scheme construction entirely.

Fixes tailscale#21786

Signed-off-by: Vivek1898 <[email protected]>

@bcreane bcreane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for submitting a PR. This looks good to me with a few minor caveats.

Comment thread cmd/tailscale/cli/serve_v2.go Resolved
Comment thread cmd/tailscale/cli/serve_v2.go Resolved
Comment thread cmd/tailscale/cli/serve_v2_test.go Resolved
Comment thread cmd/tailscale/cli/serve_v2_test.go Resolved
@Vivek1898

Copy link
Copy Markdown
Contributor Author

Thanks — all four taken, in cd52087.

Keying on the serve type. Agreed, and it reads better as the rule rather than the exception:

if serveType == serveTypeTCP || serveType == serveTypeTLSTerminatedTCP {
	scheme = conffile.ProtoTCP
}

Behaviour is unchanged: serveTypeTCP only comes from conffile.ProtoTCP, so the assignment is a no-op on that arm. It also matches the dispatch a few lines down (case serveTypeTCP, serveTypeTLSTerminatedTCP: → applyTCPServe), which is the thing imposing the constraint.

Both comments. Cut to your wording. You were right that they argued the case instead of documenting the behaviour.

The --all variant. Added as tls_terminated_tcp_all_services. Cheap, as you said, and it does cover its own path — I checked it isn't just riding along, by restoring the pre-fix serve_v2.go with both tests in place:

--- FAIL: TestRunServeSetConfig/tls_terminated_tcp_roundtrip
    set-config: service "svc:foo": failed to apply TCP serve: unable to expand target: must be a URL starting with one of the supported schemes: [tcp unix]
--- FAIL: TestRunServeSetConfig/tls_terminated_tcp_all_services
    set-config: service "svc:foo": failed to apply TCP serve: unable to expand target: must be a URL starting with one of the supported schemes: [tcp unix]

Both green with the fix. gofmt -l and go vet ./cmd/tailscale/cli/ are clean, and the full TestRunServeSetConfig passes (16 subtests).

Key the target-scheme mapping on the serve type rather than on the
protocol string, so it states the rule applyTCPServe enforces -- a TCP
apply always wants a tcp target scheme -- instead of special-casing
tls-terminated-tcp. Same behaviour: serveTypeTCP already arrives with
the tcp protocol.

Cut the code and test comments to what they describe rather than the bug
history, and add an --all variant of the round-trip test, since --all
shares the endpoint loop and so needs the same scheme.

Signed-off-by: Vivek1898 <[email protected]>
@Vivek1898
Vivek1898 force-pushed the fix/21786-serve-set-config-tls-terminated-tcp branch from cd52087 to 137fe27 Compare October 16, 2026 07:47
@Vivek1898

Copy link
Copy Markdown
Contributor Author

@bcreane all four threads are addressed and resolved above.

One correction to my earlier comment: it named cd52087, but the head is now 137fe27. The amend to fix the comment wording dropped the Signed-off-by line and turned DCO red, so I re-signed and force-pushed. DCO passes; no code changed between the two.

The policy bot reads 0/1 rules approved, so this needs another look from you whenever you have a moment.

@bcreane
bcreane self-requested a review October 16, 2026 15:17

@bcreane bcreane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for making the edits, looks good!

@bcreane
bcreane merged commit cfa83f7 into tailscale:main Oct 16, 2026
62 checks passed
kradalby added a commit to kradalby/dotfiles that referenced this pull request Oct 30, 2026
tailscale/tailscale#21789 is in the tailscale input; the patch no longer applies.
ony-boom added a commit to ony-boom/remote-os that referenced this pull request Nov 8, 2026
services.tailscale.serve's set-config rejects tls-terminated-tcp endpoints
in 1.104.x (fixed upstream in tailscale/tailscale#21789, not yet released),
so publish navidrome and filebrowser with `tailscale serve --service` from
a small unit instead, as HTTPS in front of their HTTP ports.
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.

serve set-config: tls-terminated-tcp endpoints fail with "must be a URL starting with one of the supported schemes: [tcp unix]"

2 participants