Skip to content

Commit d5fac07

Browse files
committed
feat: add controlURL options without env variables
Signed-off-by: Leon Lenzen <ich@leonlenzen.de>
1 parent a956c56 commit d5fac07

File tree

3 files changed

+109
-19
lines changed

3 files changed

+109
-19
lines changed

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,36 @@ However, having a single Caddy site connect to separate Tailscale nodes doesn't
9393
quite work correctly. If this is something you actually need, please open an
9494
issue.
9595

96+
#### Alternative ControlURL
97+
98+
To use with a custom coordination server (Headscale) you can include your ControlURL with the listen network.
99+
100+
```
101+
:80 {
102+
bind tailscale/example.de/a
103+
}
104+
105+
:80 {
106+
bind tailscale/example.de/b
107+
}
108+
```
109+
110+
It also supports specifying the used protocol (defaults to tcp):
111+
112+
```
113+
:80 {
114+
bind tailscale/example.de/udp/a
115+
}
116+
117+
:80 {
118+
bind tailscale/udp/b
119+
}
120+
```
121+
122+
Current shortcommings:
123+
- no support for path with ControlURL,
124+
- it defaults to HTTPS for connection to custom control server
125+
96126
### HTTPS support
97127

98128
At this time, the Tailscale plugin for Caddy doesn't support using Caddy's
@@ -186,3 +216,42 @@ For example:
186216
```
187217
xcaddy tailscale-proxy --from "tailscale/myhost:80" --to localhost:8000
188218
```
219+
220+
## Caddy transport provider
221+
222+
You can also specifiy tailscale as protocol to be used by caddy to proxy to your application.
223+
224+
```json
225+
{
226+
{
227+
"apps": {
228+
"http": {
229+
"servers": {
230+
"status": {
231+
"listen": [
232+
"tailscale/example.com/status:80"
233+
],
234+
"routes": [
235+
{
236+
"handle": [
237+
{
238+
"handler": "reverse_proxy",
239+
"upstreams": [
240+
{
241+
"dial": "node1:3000"
242+
}
243+
],
244+
"transport": {
245+
"protocol": "tailscale",
246+
"controlURL": "https://example.com"
247+
}
248+
}
249+
]
250+
}
251+
]
252+
}
253+
}
254+
}
255+
}
256+
}
257+
```

module.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,37 +33,30 @@ func init() {
3333
}
3434

3535
func getPlainListener(_ context.Context, _ string, addr string, _ net.ListenConfig) (any, error) {
36-
network, host, port, err := caddy.SplitNetworkAddress(addr)
36+
controlURL, network, host, port, err := parseAddr(addr)
3737
if err != nil {
3838
return nil, err
3939
}
4040

41-
s, err := getServer("", host)
41+
s, err := getServer("", host, controlURL)
4242
if err != nil {
4343
return nil, err
4444
}
4545

46-
if network == "" {
47-
network = "tcp"
48-
}
49-
5046
return s.Listen(network, ":"+port)
5147
}
5248

5349
func getTLSListener(_ context.Context, _ string, addr string, _ net.ListenConfig) (any, error) {
54-
network, host, port, err := caddy.SplitNetworkAddress(addr)
50+
controlURL, network, host, port, err := parseAddr(addr)
5551
if err != nil {
5652
return nil, err
5753
}
5854

59-
s, err := getServer("", host)
55+
s, err := getServer("", host, controlURL)
6056
if err != nil {
6157
return nil, err
6258
}
6359

64-
if network == "" {
65-
network = "tcp"
66-
}
6760
ln, err := s.Listen(network, ":"+port)
6861
if err != nil {
6962
return nil, err
@@ -86,7 +79,7 @@ func getTLSListener(_ context.Context, _ string, addr string, _ net.ListenConfig
8679
//
8780
// Auth keys can be provided in environment variables of the form TS_AUTHKEY_<HOST>. If
8881
// no host is specified in the address, the environment variable TS_AUTHKEY will be used.
89-
func getServer(_, addr string) (*tsnetServerDestructor, error) {
82+
func getServer(_, addr string, controlURL string) (*tsnetServerDestructor, error) {
9083
_, host, _, err := caddy.SplitNetworkAddress(addr)
9184
if err != nil {
9285
return nil, err
@@ -104,10 +97,9 @@ func getServer(_, addr string) (*tsnetServerDestructor, error) {
10497
},
10598
}
10699

107-
// Setting ControlURL to "TS_CONTROL_URL". If empty or not found will default to default of tsnet "https://controlplane.tailscale.com"
108-
controlUrl, controlUrlFound := os.LookupEnv("TS_CONTROL_URL")
109-
if controlUrlFound && controlUrl != "" {
110-
s.ControlURL = controlUrl
100+
// Setting ControlURL. If empty or not found will default to default of tsnet "https://controlplane.tailscale.com"
101+
if controlURL != "" {
102+
s.ControlURL = controlURL
111103
}
112104

113105
if host != "" {
@@ -233,6 +225,34 @@ func parseCaddyfile(_ httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
233225
}, nil
234226
}
235227

228+
func parseAddr(addr string) (controlURL, network, host, port string, err error) {
229+
controlURL = ""
230+
231+
network, host, port, err = caddy.SplitNetworkAddress(addr)
232+
if err != nil {
233+
return "", "", "", "", err
234+
}
235+
236+
switch network {
237+
case "", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
238+
default:
239+
controlURL = fmt.Sprintf("https://%s", network)
240+
network = "tcp"
241+
beforeSlash, afterSlash, slashFound := strings.Cut(host, "/")
242+
if slashFound {
243+
switch beforeSlash {
244+
case "", "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
245+
network = beforeSlash
246+
host = afterSlash
247+
default:
248+
network = "tcp"
249+
}
250+
}
251+
}
252+
253+
return controlURL, network, host, port, nil
254+
}
255+
236256
type tsnetServerDestructor struct {
237257
*tsnet.Server
238258
}

transport.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010
)
1111

1212
type TailscaleCaddyTransport struct {
13-
logger *zap.Logger
14-
server *tsnetServerDestructor
13+
logger *zap.Logger
14+
server *tsnetServerDestructor
15+
ControlURL string `json:"controlURL,omitempty"`
1516
}
1617

1718
func (t *TailscaleCaddyTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
@@ -21,7 +22,7 @@ func (t *TailscaleCaddyTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) err
2122
func (t *TailscaleCaddyTransport) Provision(context caddy.Context) error {
2223
t.logger = context.Logger()
2324

24-
s, err := getServer("", "caddy-tsnet-client:80")
25+
s, err := getServer("", "caddy-tsnet-client:80", t.ControlURL)
2526
if err != nil {
2627
return err
2728
}

0 commit comments

Comments
 (0)