Skip to content

Commit

Permalink
feat: add controlURL options without env variables
Browse files Browse the repository at this point in the history
Signed-off-by: Leon Lenzen <ich@leonlenzen.de>
  • Loading branch information
ChibangLW committed Oct 15, 2023
1 parent a956c56 commit d5fac07
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 19 deletions.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
}
}
]
}
]
}
}
}
}
}
```
52 changes: 36 additions & 16 deletions module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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_<HOST>. 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
Expand All @@ -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 != "" {
Expand Down Expand Up @@ -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
}
Expand Down
7 changes: 4 additions & 3 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down

0 comments on commit d5fac07

Please sign in to comment.