Skip to content

Commit

Permalink
certs: update tscert dialer to connect to tsnet server
Browse files Browse the repository at this point in the history
This provides an alternate implementation for tscert.TailscaledDialer
that tries to find a tsnet server for the requested certificate.
This allows caddy-tailscale to be used with caddy's auto_https support.

Fixes #19

Signed-off-by: Will Norris <will@tailscale.com>
  • Loading branch information
willnorris committed Jun 2, 2024
1 parent ca5b652 commit 7075183
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 17 deletions.
37 changes: 22 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ In those cases, this plugin may be helpful.

Use [xcaddy](https://github.com/caddyserver/xcaddy) to build Caddy with the Tailscale plugin included.

```
```sh
xcaddy build v2.8.4 --with github.com/tailscale/caddy-tailscale
```

Expand Down Expand Up @@ -149,7 +149,7 @@ For Caddy [JSON config], add the `tailscale` app with fields from [tscaddy.App]:
The provided network listener allows privately serving sites on your tailnet.
Configure a site block as usual, and use the [bind] directive to specify a tailscale network address:

```
```caddyfile
:80 {
bind tailscale/
}
Expand All @@ -158,7 +158,7 @@ Configure a site block as usual, and use the [bind] directive to specify a tails
The trailing slash is required.
You can also specify a named node configuration to use for the Tailscale node:

```
```caddyfile
:80 {
bind tailscale/myapp
}
Expand Down Expand Up @@ -186,15 +186,15 @@ If using the Caddy JSON configuration, specify a "tailscale/" network in your li
Caddy will join your Tailscale network and listen only on that network interface.
Multiple addresses can be specified if you want to listen on different Tailscale nodes as well as a local address:

```
```caddyfile
:80 {
bind tailscale/myhost tailscale/my-other-host localhost
}
```

Different sites can be configured to join the network as different nodes:

```
```caddyfile
:80 {
bind tailscale/myhost
}
Expand All @@ -206,7 +206,7 @@ Different sites can be configured to join the network as different nodes:

Or they can be served on different ports of the same Tailscale node:

```
```caddyfile
:80 {
bind tailscale/myhost
}
Expand All @@ -220,21 +220,28 @@ Or they can be served on different ports of the same Tailscale node:

### HTTPS support

At this time, the Tailscale plugin for Caddy doesn't support using Caddy's native HTTPS resolvers.
You will need to use the `tailscale+tls` bind protocol with a configuration like this:
Caddy's automatic HTTPS support can be used with the Tailscale network listener like any other site.
If the site address includes the full `ts.net` hostname, no additional configuration is necessary:

```
{
auto_https off
```caddyfile
https://myhost.tail1234.ts.net {
bind tailscale/myhost
}
```

If the site address does not include the full hostname, specify the tailscale cert manager:

```caddyfile
:443 {
bind tailscale+tls/myhost
bind tailscale/myhost
tls {
get_certificate tailscale
}
}
```

Please note that because you currently need to turn `auto_https` support off,
you may want to run a separate Caddy instance for sites that do need `auto_https`.
This plugin previously used a `tailcale+tls` network listener that required disabling caddy's `auto_https` feature.
That is no longer required nor recommended and will be removed in a future version.

## Authentication provider

Expand Down Expand Up @@ -316,7 +323,7 @@ and will enforce Tailscale authentication and map user values to HTTP headers.

For example:

```
```sh
xcaddy tailscale-proxy --from "tailscale/myhost:80" --to localhost:8000
```

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ go 1.22.0

require (
github.com/caddyserver/caddy/v2 v2.8.4
github.com/caddyserver/certmagic v0.21.3
github.com/google/go-cmp v0.6.0
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933
go.uber.org/zap v1.27.0
tailscale.com v1.67.0-pre.0.20240602211424-42cfbf427c67
)
Expand Down Expand Up @@ -36,7 +38,6 @@ require (
github.com/aws/smithy-go v1.20.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/caddyserver/certmagic v0.21.3 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
Expand Down Expand Up @@ -139,7 +140,6 @@ require (
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20211101221916-cabfb018fe85 // indirect
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
github.com/tailscale/tscert v0.0.0-20240517230440-bbccfbf48933 // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
github.com/tailscale/wireguard-go v0.0.0-20240429185444-03c5a0ccf754 // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect
Expand Down
42 changes: 42 additions & 0 deletions module.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/caddyserver/certmagic"
"github.com/tailscale/tscert"
"go.uber.org/zap"
"tailscale.com/tsnet"
)
Expand All @@ -26,6 +28,8 @@ func init() {
caddy.RegisterNetwork("tailscale+tls", getTLSListener)
caddy.RegisterNetwork("tailscale/udp", getUDPListener)
caddyhttp.RegisterNetworkHTTP3("tailscale", "tailscale/udp")

tscert.TailscaledDialer = localAPIDialer
}

func getTCPListener(c context.Context, _ string, addr string, _ net.ListenConfig) (any, error) {
Expand Down Expand Up @@ -304,3 +308,41 @@ func (t *tsnetServerListener) Close() error {
_, err := nodes.Delete(t.name)
return err
}

// localAPIDialer finds the node that matches the requested certificate in ctx
// and dials that node's local API.
// If no matching node is found, the default dailer is used,
// which tries to connect to a local tailscaled on the machine.
func localAPIDialer(ctx context.Context, network, addr string) (net.Conn, error) {
if addr != "local-tailscaled.sock:80" {
return nil, fmt.Errorf("unexpected URL address %q", addr)
}

clientHello, ok := ctx.Value(certmagic.ClientHelloInfoCtxKey).(*tls.ClientHelloInfo)
if !ok || clientHello == nil {
return tscert.DialLocalAPI(ctx, network, addr)
}

var tn *tailscaleNode
nodes.Range(func(key, value any) bool {
if n, ok := value.(*tailscaleNode); ok && n != nil {
for _, d := range n.CertDomains() {
// Tailscale doesn't do wildcard certs, but caddy uses MatchWildcard
// for the built-in Tailscale cert manager, so we do so here as well.
if certmagic.MatchWildcard(clientHello.ServerName, d) {
tn = n
return false
}
}
}
return true
})

if tn != nil {
if lc, err := tn.LocalClient(); err == nil {
return lc.Dial(ctx, network, addr)
}
}

return tscert.DialLocalAPI(ctx, network, addr)
}

0 comments on commit 7075183

Please sign in to comment.