From a956c56031aac3b409bdc3552b3dd38c496e2d1f Mon Sep 17 00:00:00 2001 From: Leon Lenzen Date: Sun, 15 Oct 2023 12:34:19 +1100 Subject: [PATCH 1/3] feat: add controlURL via env Signed-off-by: Leon Lenzen --- module.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/module.go b/module.go index 3ad8945..cb82ed3 100644 --- a/module.go +++ b/module.go @@ -104,6 +104,12 @@ func getServer(_, addr string) (*tsnetServerDestructor, error) { }, } + // Setting ControlURL to "TS_CONTROL_URL". If empty or not found will default to default of tsnet "https://controlplane.tailscale.com" + controlUrl, controlUrlFound := os.LookupEnv("TS_CONTROL_URL") + if controlUrlFound && controlUrl != "" { + s.ControlURL = controlUrl + } + if host != "" { // Set authkey to "TS_AUTHKEY_". If empty, // fall back to "TS_AUTHKEY". From d5fac072522766796153579933bbfa6d45181581 Mon Sep 17 00:00:00 2001 From: Leon Lenzen Date: Sun, 15 Oct 2023 17:39:50 +1100 Subject: [PATCH 2/3] feat: add controlURL options without env variables Signed-off-by: Leon Lenzen --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ module.go | 52 +++++++++++++++++++++++++++------------ transport.go | 7 +++--- 3 files changed, 109 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a9f720d..4ab66de 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,36 @@ However, having a single Caddy site connect to separate Tailscale nodes doesn't quite work correctly. If this is something you actually need, please open an issue. +#### Alternative ControlURL + +To use with a custom coordination server (Headscale) you can include your ControlURL with the listen network. + +``` +:80 { + bind tailscale/example.de/a +} + +:80 { + bind tailscale/example.de/b +} +``` + +It also supports specifying the used protocol (defaults to tcp): + +``` +:80 { + bind tailscale/example.de/udp/a +} + +:80 { + bind tailscale/udp/b +} +``` + +Current shortcommings: +- no support for path with ControlURL, +- it defaults to HTTPS for connection to custom control server + ### HTTPS support At this time, the Tailscale plugin for Caddy doesn't support using Caddy's @@ -186,3 +216,42 @@ For example: ``` xcaddy tailscale-proxy --from "tailscale/myhost:80" --to localhost:8000 ``` + +## Caddy transport provider + +You can also specifiy tailscale as protocol to be used by caddy to proxy to your application. + +```json +{ + { + "apps": { + "http": { + "servers": { + "status": { + "listen": [ + "tailscale/example.com/status:80" + ], + "routes": [ + { + "handle": [ + { + "handler": "reverse_proxy", + "upstreams": [ + { + "dial": "node1:3000" + } + ], + "transport": { + "protocol": "tailscale", + "controlURL": "https://example.com" + } + } + ] + } + ] + } + } + } + } +} +``` \ No newline at end of file diff --git a/module.go b/module.go index cb82ed3..8b9d557 100644 --- a/module.go +++ b/module.go @@ -33,37 +33,30 @@ func init() { } func getPlainListener(_ context.Context, _ string, addr string, _ net.ListenConfig) (any, error) { - network, host, port, err := caddy.SplitNetworkAddress(addr) + controlURL, network, host, port, err := parseAddr(addr) if err != nil { return nil, err } - s, err := getServer("", host) + s, err := getServer("", host, controlURL) if err != nil { return nil, err } - if network == "" { - network = "tcp" - } - return s.Listen(network, ":"+port) } func getTLSListener(_ context.Context, _ string, addr string, _ net.ListenConfig) (any, error) { - network, host, port, err := caddy.SplitNetworkAddress(addr) + controlURL, network, host, port, err := parseAddr(addr) if err != nil { return nil, err } - s, err := getServer("", host) + s, err := getServer("", host, controlURL) if err != nil { return nil, err } - if network == "" { - network = "tcp" - } ln, err := s.Listen(network, ":"+port) if err != nil { return nil, err @@ -86,7 +79,7 @@ func getTLSListener(_ context.Context, _ string, addr string, _ net.ListenConfig // // Auth keys can be provided in environment variables of the form TS_AUTHKEY_. If // no host is specified in the address, the environment variable TS_AUTHKEY will be used. -func getServer(_, addr string) (*tsnetServerDestructor, error) { +func getServer(_, addr string, controlURL string) (*tsnetServerDestructor, error) { _, host, _, err := caddy.SplitNetworkAddress(addr) if err != nil { return nil, err @@ -104,10 +97,9 @@ func getServer(_, addr string) (*tsnetServerDestructor, error) { }, } - // Setting ControlURL to "TS_CONTROL_URL". If empty or not found will default to default of tsnet "https://controlplane.tailscale.com" - controlUrl, controlUrlFound := os.LookupEnv("TS_CONTROL_URL") - if controlUrlFound && controlUrl != "" { - s.ControlURL = controlUrl + // Setting ControlURL. If empty or not found will default to default of tsnet "https://controlplane.tailscale.com" + if controlURL != "" { + s.ControlURL = controlURL } if host != "" { @@ -233,6 +225,34 @@ func parseCaddyfile(_ httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) }, nil } +func parseAddr(addr string) (controlURL, network, host, port string, err error) { + controlURL = "" + + network, host, port, err = caddy.SplitNetworkAddress(addr) + if err != nil { + return "", "", "", "", err + } + + switch network { + case "", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + default: + controlURL = fmt.Sprintf("https://%s", network) + network = "tcp" + beforeSlash, afterSlash, slashFound := strings.Cut(host, "/") + if slashFound { + switch beforeSlash { + case "", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": + network = beforeSlash + host = afterSlash + default: + network = "tcp" + } + } + } + + return controlURL, network, host, port, nil +} + type tsnetServerDestructor struct { *tsnet.Server } diff --git a/transport.go b/transport.go index 6c8767b..ad5d7b8 100644 --- a/transport.go +++ b/transport.go @@ -10,8 +10,9 @@ import ( ) type TailscaleCaddyTransport struct { - logger *zap.Logger - server *tsnetServerDestructor + logger *zap.Logger + server *tsnetServerDestructor + ControlURL string `json:"controlURL,omitempty"` } func (t *TailscaleCaddyTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { @@ -21,7 +22,7 @@ func (t *TailscaleCaddyTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) err func (t *TailscaleCaddyTransport) Provision(context caddy.Context) error { t.logger = context.Logger() - s, err := getServer("", "caddy-tsnet-client:80") + s, err := getServer("", "caddy-tsnet-client:80", t.ControlURL) if err != nil { return err } From aca1834771d7985f63d49e4d7793ee51b88a3e3f Mon Sep 17 00:00:00 2001 From: ChibangLW Date: Fri, 2 Feb 2024 19:58:14 +0100 Subject: [PATCH 3/3] dep: updated tailscale --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 40f6423..85ebd10 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/caddyserver/caddy/v2 v2.7.3 go.uber.org/zap v1.25.0 - tailscale.com v1.1.1-0.20230810153433-3d56cafd7d23 + tailscale.com v1.58.2 ) require (