tscaddy

package module
v0.0.0-...-a2370f7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2024 License: Apache-2.0 Imports: 32 Imported by: 0

README

Tailscale plugin for Caddy

The Tailscale plugin for Caddy allows running a Tailscale node directly inside of the Caddy web server. This allows a caddy server to join your Tailscale network directly without needing a separate Tailscale client.

It's really a collection of plugins, providing:

  • a Caddy network listener, to serve sites privately on your tailnet
  • a Caddy proxy transport, to proxy requests to another device on your tailnet
  • a Caddy authentication provider, to pass a user's Tailscale identity to an application
  • a Caddy subcommand, to quickly setup a reverse-proxy using either or both of the network listener or authentication provider

This plugin is still very experimental.

Why

It's important to note that you don't necessarily need this plugin to use Caddy with Tailscale. With Tailscale installed on a machine, Caddy can already bind to the Tailscale network interface, proxy requests to other Tailnet nodes, get automatic certificates, and authenticate Tailscale users.

However, there may be cases where it is inconvenient to install Tailscale on a machine, and it would be preferable to have everything self-contained in the single caddy binary. Or, you may want to serve multiple sites, each connected as a separate Tailnet node. In those cases, this plugin may be helpful.

Installation

Use xcaddy to build Caddy with the Tailscale plugin included.

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

Alternatively, you can build the included cmd/caddy package:

go build ./cmd/caddy
Running examples

Multiple example configurations are provided in the examples directory. These examples expect an auth key to be set in the TS_AUTHKEY environment variable. All nodes registered while running these examples will be ephemeral and removed after disconnect. See the comments in the individual files for details.

Run them with:

TS_AUTHKEY=<tskey-auth-XXXXX> ./caddy run -c examples/<file>

Configuration

In a Caddyfile, use the tailscale global option to configure your Tailscale nodes. Most options can be set at the top-level, in which case they will apply to all nodes. They can also be set for a specific named node, which override the top-level options. Named node configurations can be referenced elsewhere in the caddy configuration.

The tailscale global option only defines configuration values for Tailscale nodes. Nodes are not actually registered and connected to your tailnet until they are used, such as listening on the node's interface or using it as a proxy transport.

String options support the use of placeholders to populate values dynamically, such as from an environment variable.

Supported options are:

{
  tailscale {
    # Tailscale auth key used to register nodes.
    auth_key <auth_key>

    # Alternate control server URL. Leave empty to use the default server.
    control_url <control_url>

    # If true, register ephemeral nodes that are removed after disconnect.
    # Default: false
    ephemeral true|false

    # Directory to store Tailscale state in. A subdirectory will be created for each node.
    # The default is to store state in the user's config dir (see os.UserConfDir).
    state_dir <filepath>

    # If true, run the Tailscale web UI for remotely managing the node. (https://tailscale.com/kb/1325)
    # Default: false
    webui true|false

    # Any number of named node configs can be specified to override global options.
    <node_name> {
      # Tailscale auth key used to register this node.
      auth_key <auth_key>

      # Alternate control server URL.
      control_url <control_url>

      # If true, remove this node after disconnect.
      ephemeral true|false

      # Hostname to request when registering this node.
      # Default: <node_name> used for this node configuration
      hostname <hostname>

      # Directory to store Tailscale state in for this node. No subdirectory is created.
      state_dir <filepath>

      # If true, run the Tailscale web UI for remotely managing this node.
      webui true|false
    }
  }
}

All configuration values are optional, though an auth key is strongly recommended. If no auth key is present, one will be loaded from the default $TS_AUTHKEY environment variable. Failing that, it will log an auth URL to the Caddy log that can be used to register the node.

Unless the node is registered as ephemeral, the auth key is only needed on first run. Node state is stored in state_dir and reused when Caddy restarts. When running in a container, it is generally recommended to use ephemeral and always provide an auth key, or to mount the state directory on a persistent volume, depending on the use case.

For Caddy JSON config, add the tailscale app with fields from tscaddy.App:

{
  "apps": {
    "tailscale": {
      ...
    }
  }
}

Network listener

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:

:80 {
  bind tailscale/
}

The trailing slash is required. You can also specify a named node configuration to use for the Tailscale node:

:80 {
  bind tailscale/myapp
}

If no node configuration is specified, a default configuration will be used, which names the node based on the name of the running binary (typically, caddy).

If using the Caddy JSON configuration, specify a "tailscale/" network in your listen address:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": ["tailscale/myhost:80"]
        }
      }
    }
  }
}

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:

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

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

:80 {
  bind tailscale/myhost
}

:80 {
  bind tailscale/my-other-host
}

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

:80 {
  bind tailscale/myhost
}

:8080 {
  bind tailscale/myhost
}
HTTPS support

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:

https://myhost.tail1234.ts.net {
  bind tailscale/myhost
}

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

:443 {
  bind tailscale/myhost
  tls {
    get_certificate tailscale
  }
}

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

Set up the Tailscale authentication provider with the 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. For sites listening only on the Tailscale network interface, user access will already be enforced by the tailnet access controls. The authentication provider currently only works with connections from user-owned devices. It does not currently support connections from tagged devices.

For example, in a Caddyfile:

:80 {
  tailscale_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 values can be mapped to HTTP headers that are then passed to an application that supports proxy authentication such as Gitea or Grafana. You might have something like the following in your Caddyfile:

:80 {
  bind tailscale/gitea
  tailscale_auth
  reverse_proxy http://localhost:3000 {
    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}
  }
}

When used with a Tailscale listener (described above), that Tailscale node is used to identify the remote user. Otherwise, the authentication provider will attempt to connect to the Tailscale daemon running on the local machine.

Proxy Transport

The tailscale proxy transport allows using a Tailscale node to connect to a reverse proxy upstream. This might be useful to proxy non-Tailscale traffic to a node on your tailnet, similar to Funnel.

You can specify a named node configuration, or a default caddy-proxy node will be used.

:8080 {
  reverse_proxy http://my-other-node:10000 {
    transport tailscale myhost
  }
}

Note that the node name is separated by a space, rather than a slash, as in the network listener.

tailscale-proxy subcommand

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

(The tailscale-proxy subcommand does not yet work with the tailscale proxy transport.)

Documentation

Overview

Package tscaddy provides a set of Caddy modules to integrate Tailscale into Caddy.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App struct {
	// DefaultAuthKey is the default auth key to use for Tailscale if no other auth key is specified.
	DefaultAuthKey string `json:"auth_key,omitempty" caddy:"namespace=tailscale.auth_key"`

	// ControlURL specifies the default control URL to use for nodes.
	ControlURL string `json:"control_url,omitempty" caddy:"namespace=tailscale.control_url"`

	// Ephemeral specifies whether Tailscale nodes should be registered as ephemeral.
	Ephemeral bool `json:"ephemeral,omitempty" caddy:"namespace=tailscale.ephemeral"`

	// StateDir specifies the default state directory for Tailscale nodes.
	// Each node will have a subdirectory under this parent directory for its state.
	StateDir string `json:"state_dir,omitempty" caddy:"namespace=tailscale.state_dir"`

	// WebUI specifies whether Tailscale nodes should run the Web UI for remote management.
	WebUI bool `json:"webui,omitempty" caddy:"namespace=tailscale.webui"`

	// Nodes is a map of per-node configuration which overrides global options.
	Nodes map[string]Node `json:"nodes,omitempty" caddy:"namespace=tailscale"`
	// contains filtered or unexported fields
}

App is the Tailscale Caddy app used to configure Tailscale nodes. Nodes can be used to serve sites privately on a Tailscale network, or to connect to other Tailnet nodes as upstream proxy backend.

func (App) CaddyModule

func (App) CaddyModule() caddy.ModuleInfo

func (*App) Provision

func (t *App) Provision(ctx caddy.Context) error

func (*App) Start

func (t *App) Start() error

func (*App) Stop

func (t *App) Stop() error

type Auth

type Auth struct {
	// contains filtered or unexported fields
}

Auth is an HTTP authentication provider that authenticates users based on their Tailscale identity. If configured on a caddy site that is listening on a tailscale node, that node will be used to identify the user information for inbound requests. Otherwise, it will attempt to find and use the local tailscaled daemon running on the system.

func (Auth) Authenticate

func (ta Auth) Authenticate(w http.ResponseWriter, r *http.Request) (caddyauth.User, bool, error)

Authenticate authenticates the request and sets Tailscale user data on the caddy User object.

This method will set the following user metadata:

  • tailscale_login: the user's login name without the domain
  • tailscale_user: the user's full login name
  • tailscale_name: the user's display name
  • tailscale_profile_picture: the user's profile picture URL
  • tailscale_tailnet: the user's tailnet name (if the user is not connecting to a shared node)

func (Auth) CaddyModule

func (Auth) CaddyModule() caddy.ModuleInfo

type Node

type Node struct {
	// AuthKey is the Tailscale auth key used to register the node.
	AuthKey string `json:"auth_key,omitempty" caddy:"namespace=auth_key"`

	// ControlURL specifies the control URL to use for the node.
	ControlURL string `json:"control_url,omitempty" caddy:"namespace=tailscale.control_url"`

	// Ephemeral specifies whether the node should be registered as ephemeral.
	Ephemeral opt.Bool `json:"ephemeral,omitempty" caddy:"namespace=tailscale.ephemeral"`

	// WebUI specifies whether the node should run the Web UI for remote management.
	WebUI opt.Bool `json:"webui,omitempty" caddy:"namespace=tailscale.webui"`

	// Hostname is the hostname to use when registering the node.
	Hostname string `json:"hostname,omitempty" caddy:"namespace=tailscale.hostname"`

	// StateDir specifies the state directory for the node.
	StateDir string `json:"state_dir,omitempty" caddy:"namespace=tailscale.state_dir"`
	// contains filtered or unexported fields
}

Node is a Tailscale node configuration. A single node can be used to serve multiple sites on different domains or ports, and/or to connect to other Tailscale nodes.

type Transport

type Transport struct {
	Name string `json:"name,omitempty"`

	// A non-nil TLS config enables TLS.
	// We do not currently use the config values for anything.
	TLS *reverseproxy.TLSConfig `json:"tls,omitempty"`
	// contains filtered or unexported fields
}

Transport is a caddy transport that uses a tailscale node to make requests.

func (*Transport) CaddyModule

func (t *Transport) CaddyModule() caddy.ModuleInfo

func (*Transport) Cleanup

func (t *Transport) Cleanup() error

func (*Transport) EnableTLS

func (h *Transport) EnableTLS(config *reverseproxy.TLSConfig) error

EnableTLS enables TLS on the transport.

func (*Transport) Provision

func (t *Transport) Provision(ctx caddy.Context) error

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

func (Transport) TLSEnabled

func (h Transport) TLSEnabled() bool

TLSEnabled returns true if TLS is enabled.

func (*Transport) UnmarshalCaddyfile

func (t *Transport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error

UnmarshalCaddyfile populates a Transport config from a caddyfile.

We only support a single token identifying the name of a node in the App config. For example:

reverse_proxy {
  transport tailscale my-node
}

If a node name is not specified, a default name is used.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL