mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-24 17:47:30 +00:00
cmd/tailscale,ipn: add Unix socket support for serve
Based on PR #16700 by @lox, adapted to current codebase. Adds support for proxying HTTP requests to Unix domain sockets via tailscale serve unix:/path/to/socket, enabling exposure of services like Docker, containerd, PHP-FPM over Tailscale without TCP bridging. The implementation includes reasonable protections against exposure of tailscaled's own socket. Adaptations from original PR: - Use net.Dialer.DialContext instead of net.Dial for context propagation - Use http.Transport with Protocols API (current h2c approach, not http2.Transport) - Resolve conflicts with hasScheme variable in ExpandProxyTargetValue Updates #9771 Signed-off-by: Peter A. <ink.splatters@pm.me> Co-authored-by: Lachlan Donald <lachlan@ljd.cc>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
557457f3c2
commit
f4d34f38be
@@ -138,6 +138,7 @@ var serveHelpCommon = strings.TrimSpace(`
|
||||
<target> can be a file, directory, text, or most commonly the location to a service running on the
|
||||
local machine. The location to the location service can be expressed as a port number (e.g., 3000),
|
||||
a partial URL (e.g., localhost:3000), or a full URL including a path (e.g., http://localhost:3000/foo).
|
||||
On Unix-like systems, you can also specify a Unix domain socket (e.g., unix:/tmp/myservice.sock).
|
||||
|
||||
EXAMPLES
|
||||
- Expose an HTTP server running at 127.0.0.1:3000 in the foreground:
|
||||
@@ -149,6 +150,9 @@ EXAMPLES
|
||||
- Expose an HTTPS server with invalid or self-signed certificates at https://localhost:8443
|
||||
$ tailscale %[1]s https+insecure://localhost:8443
|
||||
|
||||
- Expose a service listening on a Unix socket (Linux/macOS/BSD only):
|
||||
$ tailscale %[1]s unix:/var/run/myservice.sock
|
||||
|
||||
For more examples and use cases visit our docs site https://tailscale.com/kb/1247/funnel-serve-use-cases
|
||||
`)
|
||||
|
||||
@@ -1172,7 +1176,8 @@ func (e *serveEnv) applyWebServe(sc *ipn.ServeConfig, dnsName string, srvPort ui
|
||||
}
|
||||
h.Path = target
|
||||
default:
|
||||
t, err := ipn.ExpandProxyTargetValue(target, []string{"http", "https", "https+insecure"}, "http")
|
||||
// Include unix in supported schemes for HTTP(S) serve
|
||||
t, err := ipn.ExpandProxyTargetValue(target, []string{"http", "https", "https+insecure", "unix"}, "http")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
86
cmd/tailscale/cli/serve_v2_unix_test.go
Normal file
86
cmd/tailscale/cli/serve_v2_unix_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build unix
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/ipn"
|
||||
)
|
||||
|
||||
func TestServeUnixSocketCLI(t *testing.T) {
|
||||
// Create a temporary directory for our socket path
|
||||
tmpDir := t.TempDir()
|
||||
socketPath := filepath.Join(tmpDir, "test.sock")
|
||||
|
||||
// Test that Unix socket targets are accepted by ExpandProxyTargetValue
|
||||
target := "unix:" + socketPath
|
||||
result, err := ipn.ExpandProxyTargetValue(target, []string{"http", "https", "https+insecure", "unix"}, "http")
|
||||
if err != nil {
|
||||
t.Fatalf("ExpandProxyTargetValue failed: %v", err)
|
||||
}
|
||||
|
||||
if result != target {
|
||||
t.Errorf("ExpandProxyTargetValue(%q) = %q, want %q", target, result, target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeUnixSocketConfigPreserved(t *testing.T) {
|
||||
// Test that Unix socket URLs are preserved in ServeConfig
|
||||
sc := &ipn.ServeConfig{
|
||||
Web: map[ipn.HostPort]*ipn.WebServerConfig{
|
||||
"foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{
|
||||
"/": {Proxy: "unix:/tmp/test.sock"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
// Verify the proxy value is preserved
|
||||
handler := sc.Web["foo.test.ts.net:443"].Handlers["/"]
|
||||
if handler.Proxy != "unix:/tmp/test.sock" {
|
||||
t.Errorf("proxy = %q, want %q", handler.Proxy, "unix:/tmp/test.sock")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeUnixSocketVariousPaths(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
target string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "absolute-path",
|
||||
target: "unix:/var/run/docker.sock",
|
||||
},
|
||||
{
|
||||
name: "tmp-path",
|
||||
target: "unix:/tmp/myservice.sock",
|
||||
},
|
||||
{
|
||||
name: "relative-path",
|
||||
target: "unix:./local.sock",
|
||||
},
|
||||
{
|
||||
name: "home-path",
|
||||
target: "unix:/home/user/.local/service.sock",
|
||||
},
|
||||
{
|
||||
name: "empty-path",
|
||||
target: "unix:",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ipn.ExpandProxyTargetValue(tt.target, []string{"http", "https", "unix"}, "http")
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExpandProxyTargetValue(%q) error = %v, wantErr %v", tt.target, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -401,6 +401,7 @@ func run() (err error) {
|
||||
// Install an event bus as early as possible, so that it's
|
||||
// available universally when setting up everything else.
|
||||
sys := tsd.NewSystem()
|
||||
sys.SocketPath = args.socketpath
|
||||
|
||||
// Parse config, if specified, to fail early if it's invalid.
|
||||
var conf *conffile.Config
|
||||
|
||||
Reference in New Issue
Block a user