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 3, 2024
1 parent 1690471 commit f2562ba
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 18 deletions.
40 changes: 25 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,31 @@ 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.
Caddy will use [Tailscale's HTTPS support] to issue certificates for your node's hostname.
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.

[Tailscale's HTTPS support]: https://tailscale.com/kb/1153/enabling-https

## Authentication provider

Expand Down Expand Up @@ -316,7 +326,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
5 changes: 4 additions & 1 deletion examples/proxyauth.caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@

# This will run an identical site as above, but with TLS enabled.
:443 {
bind tailscale+tls/caddytest
bind tailscale/caddytest
tls {
get_certificate tailscale
}
tailscale_auth
reverse_proxy localhost:3333 {
header_up X-Webauth-User {http.auth.user.tailscale_login}
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
45 changes: 45 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,11 @@ func init() {
caddy.RegisterNetwork("tailscale+tls", getTLSListener)
caddy.RegisterNetwork("tailscale/udp", getUDPListener)
caddyhttp.RegisterNetworkHTTP3("tailscale", "tailscale/udp")

// Caddy uses tscert to get certificates for Tailscale hostnames.
// Update the tscert dialer to dial the LocalAPI of the correct tsnet node,
// rather than just always dialing the local tailscaled.
tscert.TailscaledDialer = localAPIDialer
}

func getTCPListener(c context.Context, _ string, addr string, _ net.ListenConfig) (any, error) {
Expand Down Expand Up @@ -304,3 +311,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 dialer 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 f2562ba

Please sign in to comment.