-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
all: add support for custom caddy listener #1
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,82 +1,154 @@ | ||
# caddy tailscale auth | ||
# Tailscale Caddy plugin | ||
|
||
Using [nginx-auth][] together [with Caddy][] is pretty cool. But since Caddy | ||
is written in Go anyway, what if we combined the two? | ||
The Tailscale Caddy plugin brings Tailscale integration to the Caddy web server. | ||
It's really multiple plugins in one, providing: | ||
|
||
This repo provides a Caddy module that will talk with a local tailscaled in | ||
exactly the same way that nginx-auth does, but without needing a separate | ||
binary. Alternately, you can provide an auth_key and caddy will use tsnet to | ||
join your tailnet directly and use that connection to authenticate users | ||
(though tailscaled is still necessary for now to listen on the tailscale | ||
interface). | ||
- the ability for a Caddy server to directly join your Tailscale network | ||
without needing a separate Tailscale client. | ||
- a Caddy authentication provider, so that you can pass a user's Tailscale | ||
identity to an applicatiton. | ||
- a Caddy subcommand to quickly setup a reverse-proxy using either or both of | ||
the network listener or authentication provider. | ||
|
||
[nginx-auth]: https://github.com/tailscale/tailscale/tree/main/cmd/nginx-auth | ||
[with Caddy]: https://caddyserver.com/docs/caddyfile/directives/forward_auth#tailscale | ||
|
||
## demo | ||
This plugin currently uses unreleased functionality in both Caddy and Tailscale | ||
and is very experimental. | ||
|
||
First, you'll need to install [xcaddy][] to build a caddy binary with custom modules. | ||
## Installation | ||
|
||
$ go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest | ||
Use [xcaddy](https://github.com/caddyserver/xcaddy) to build Caddy with the | ||
Tailscale plugin included. | ||
|
||
[xcaddy]: https://github.com/caddyserver/xcaddy | ||
``` | ||
xcaddy build master --with github.com/tailscale/caddy-tailscale | ||
``` | ||
|
||
Clone this repo and run `xcaddy run` to build and run a caddy server. | ||
## Caddy network listener | ||
|
||
**NOTE:** The first time you run it, you may be prompted for your password | ||
because caddy uses your system certificate store to [install a certificate | ||
authority] to sign development certificates. | ||
Coming in Caddy 2.6, modules are able to provide custom network listeners. This | ||
allows your Caddy server to directly join your Tailscale network without needing | ||
a separate Tailcale client running on the machine exposing a network device. | ||
Each site can be configured in Caddy to join your network as a separate node, or | ||
you can have multiple sites listening on different ports of a single node. | ||
|
||
[install a certificate authority]: https://caddyserver.com/docs/automatic-https | ||
### Configuration | ||
|
||
Once caddy starts up, open http://yourhost:7000/ in your browser. You actually | ||
need to use your tailscale hostname or IP address; you can't use localhost. | ||
Ideally, you should be greeted with your tailscale account info. Now visit | ||
http://yourhost:7100/ and you should see the same information in a little | ||
different format. This demonstrates two different ways that the module can be | ||
used (see more details in [Caddyfile](./Caddyfile)). | ||
Configure Caddy to listen on a special "tailscale" network address. If using a | ||
Caddyfile, use the [bind directive](https://caddyserver.com/docs/caddyfile/directives/bind): | ||
|
||
1. port 7000 - authenticate as normal and populate the standard caddy user object | ||
2. port 7100 - do the same, but populate X-Webauth headers and proxy the | ||
request to a separate application that knows nothing about Tailscale. | ||
``` | ||
:80 { | ||
bind tailscale/ | ||
} | ||
``` | ||
|
||
Some other options you can try: | ||
You can also specify a hostname to use for the Tailscale node: | ||
|
||
Specify an auth key to use tsnet mode: | ||
``` | ||
:80 { | ||
bind tailscale/myhost | ||
} | ||
``` | ||
|
||
tailscale_auth { | ||
auth_key ts-key-xxxxxxCNTRL-xxxxxx | ||
If using the Caddy JSON configuration, specify a "tailscale/" network in your | ||
listen address: | ||
|
||
```json | ||
{ | ||
"apps": { | ||
"http": { | ||
"servers": { | ||
"srv0": { | ||
"listen": [ | ||
"tailscale/myhost:80" | ||
] | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
# or with an environment variable | ||
tailscale_auth { | ||
auth_key {env.TS_AUTH_KEY} | ||
} | ||
Caddy will join your Tailscale network and listen only on that network | ||
interface. Multiple addresses can be specified if you want to listen on the | ||
Tailscale address as well as a local address: | ||
|
||
In order to use the environment variable form, you'll need to actually build a | ||
caddy binary rather than just using `xcaddy run`. To do that: | ||
``` | ||
:80 { | ||
bind tailscale/myhost localhost | ||
} | ||
``` | ||
|
||
xcaddy build --with github.com/tailscale/caddy=./ | ||
TS_AUTH_KEY=ts-key-xxxxxxCNTRL-xxxxxx ./caddy run | ||
Different sites can be configured to join the network as different nodes: | ||
|
||
When using tsnet mode, you can also specify a custom hostname for your node as | ||
well as verbose logging: | ||
``` | ||
:80 { | ||
bind tailscale/a | ||
} | ||
|
||
:80 { | ||
bind tailscale/b | ||
} | ||
``` | ||
tailscale_auth { | ||
auth_key ts-key-xxxxxxCNTRL-xxxxxx | ||
hostname myhost | ||
verbose | ||
} | ||
|
||
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. | ||
|
||
### Authenticating to the Tailcale network | ||
|
||
New nodes can be added to your Tailscale network by providing an [Auth | ||
key](https://tailscale.com/kb/1085/auth-keys/) or by following a special URL. | ||
Auth keys are provided to Caddy via the `TS_AUTHKEY` or `TS_AUTHKEY_<host>` | ||
environment variable. So if your network listener was `tailscale/myhost`, then | ||
it would look for the `TS_AUTHKEY_MYHOST` environment variable, then | ||
`TS_AUTHKEY`. | ||
|
||
If no auth key is provided, then Tailscale will generate a URL that can be used | ||
to add the new node and print it to the Caddy log. Tailscale logs can be | ||
somewhat noisy so are turned off by default. Set `TS_DEBUG=1` to see the URL | ||
logged. After the node had been added to your network, you can restart Caddy | ||
without the debug flag. | ||
|
||
|
||
## Caddy authentication provider | ||
|
||
Setup the Tailscale authentication provider with `tailscale_auth` directive. | ||
The provider will enforce that all requests are coming from a Tailscale user, as | ||
well as set various fields on the Caddy user object that can be passed to | ||
applications, similar to [nginx-auth][]. | ||
|
||
[nginx-auth]: https://github.com/tailscale/tailscale/tree/main/cmd/nginx-auth | ||
|
||
The following fields are set on the Caddy user object: | ||
|
||
- `user.id`: the Tailscale email-ish user ID | ||
- `user.tailscale_login`: the username portion of the Tailscale user ID | ||
- `user.tailscale_user`: same as `user.id` | ||
- `user.tailscale_name`: the display name of the Tailscale user | ||
- `user.tailscale_profile_picture`: the URL of the Tailscale user's profile picture | ||
- `user.tailscale_tailnet`: the name of the Tailscale network the user is a member of | ||
|
||
These can be mapped to HTTP headers passed to an application using something | ||
like the following in your Caddyfile: | ||
|
||
``` | ||
header_up X-Webauth-User {http.auth.user.tailscale_login} | ||
header_up X-Webauth-Email {http.auth.user.tailscale_user} | ||
header_up X-Webauth-Name {http.auth.user.tailscale_name} | ||
``` | ||
|
||
tsnet mode still actually requires a local tailscaled running so that caddy can | ||
listen on the tailscale network interface. We're looking into options to remove | ||
the need for tailscaled at all. | ||
When used with a Tailscale listener (described above), that Tailscale connection | ||
is used to identify the remote user. Otherwise, the authentication provider | ||
will attempt to connect to the Tailscale daemon running on the local machine. | ||
|
||
# Related work | ||
## tailscale-proxy subcommand | ||
|
||
It looks like <https://github.com/astrophena/tsid> is very similar and did this | ||
in mid 2021, but hooks into caddy as an http handler rather than an | ||
authentication provider. It also doesn't support tsnet, which likely didn't | ||
exist at the time. | ||
The Tailscale Caddy plugin also includes a `tailscale-proxy` subcommand that | ||
sets up a simple reverse proxy that can optionally join your Tailscale network, | ||
and will enforce Tailscale authentication and map user values to HTTP headers. | ||
|
||
For example: | ||
|
||
``` | ||
xcaddy tailscale-proxy --from "tailscale/myhost:80" --to localhost:8000 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if this will work OK with IPv6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like it? I just tested with
caddy tailscale-proxy --from "tailscale/caddy:2000" --to :8000
and was able to access via tailscale ipv6 address.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sorry, I meant
caddy tailscale-proxy --from <ip6_addr>
e.g.--from 6240:3c33:...
I suspect the IPv6 address would have to be surrounded in
[ ]
, which isn't bad (and is probably more correct) but users should just be aware if they do something like that.Glad it's working over IPv6 though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope... fails to parse if ipv6 address is specified. I'll have to look into that