Merge remote-tracking branch 'origin/main'

This commit is contained in:
Ryan Malloy 2026-05-21 17:58:02 -06:00
commit 2c8640f822
1496 changed files with 12110903 additions and 24774 deletions

View file

@ -1,206 +0,0 @@
Headscale implements the same policy ACLs as Tailscale.com, adapted to the self-hosted environment.
For instance, instead of referring to users when defining groups you must
use users (which are the equivalent to user/logins in Tailscale.com).
Please check https://tailscale.com/kb/1018/acls/ for further information.
When using ACL's the User borders are no longer applied. All machines
whichever the User have the ability to communicate with other hosts as
long as the ACL's permits this exchange.
## ACL Setup
To enable and configure ACLs in Headscale, you need to specify the path to your ACL policy file in the `policy.path` key in `config.yaml`.
Your ACL policy file must be formatted using [huJSON](https://github.com/tailscale/hujson).
Info on how these policies are written can be found
[here](https://tailscale.com/kb/1018/acls/).
Please reload or restart Headscale after updating the ACL file. Headscale may be reloaded either via its systemd service
(`sudo systemctl reload headscale`) or by sending a SIGHUP signal (`sudo kill -HUP $(pidof headscale)`) to the main
process. Headscale logs the result of ACL policy processing after each reload.
## Simple Examples
- [**Allow All**](https://tailscale.com/kb/1192/acl-samples#allow-all-default-acl): If you define an ACL file but completely omit the `"acls"` field from its content, Headscale will default to an "allow all" policy. This means all devices connected to your tailnet will be able to communicate freely with each other.
```json
{}
```
- [**Deny All**](https://tailscale.com/kb/1192/acl-samples#deny-all): To prevent all communication within your tailnet, you can include an empty array for the `"acls"` field in your policy file.
```json
{
"acls": []
}
```
## Complex Example
Let's build a more complex example use case for a small business (It may be the place where
ACL's are the most useful).
We have a small company with a boss, an admin, two developers and an intern.
The boss should have access to all servers but not to the user's hosts. Admin
should also have access to all hosts except that their permissions should be
limited to maintaining the hosts (for example purposes). The developers can do
anything they want on dev hosts but only watch on productions hosts. Intern
can only interact with the development servers.
There's an additional server that acts as a router, connecting the VPN users
to an internal network `10.20.0.0/16`. Developers must have access to those
internal resources.
Each user have at least a device connected to the network and we have some
servers.
- database.prod
- database.dev
- app-server1.prod
- app-server1.dev
- billing.internal
- router.internal
![ACL implementation example](../images/headscale-acl-network.png)
When [registering the servers](../usage/getting-started.md#register-a-node) we
will need to add the flag `--advertise-tags=tag:<tag1>,tag:<tag2>`, and the user
that is registering the server should be allowed to do it. Since anyone can add
tags to a server they can register, the check of the tags is done on headscale
server and only valid tags are applied. A tag is valid if the user that is
registering it is allowed to do it.
Here are the ACL's to implement the same permissions as above:
```json title="acl.json"
{
// groups are collections of users having a common scope. A user can be in multiple groups
// groups cannot be composed of groups
"groups": {
"group:boss": ["boss@"],
"group:dev": ["dev1@", "dev2@"],
"group:admin": ["admin1@"],
"group:intern": ["intern1@"]
},
// tagOwners in tailscale is an association between a TAG and the people allowed to set this TAG on a server.
// This is documented [here](https://tailscale.com/kb/1068/acl-tags#defining-a-tag)
// and explained [here](https://tailscale.com/blog/rbac-like-it-was-meant-to-be/)
"tagOwners": {
// the administrators can add servers in production
"tag:prod-databases": ["group:admin"],
"tag:prod-app-servers": ["group:admin"],
// the boss can tag any server as internal
"tag:internal": ["group:boss"],
// dev can add servers for dev purposes as well as admins
"tag:dev-databases": ["group:admin", "group:dev"],
"tag:dev-app-servers": ["group:admin", "group:dev"]
// interns cannot add servers
},
// hosts should be defined using its IP addresses and a subnet mask.
// to define a single host, use a /32 mask. You cannot use DNS entries here,
// as they're prone to be hijacked by replacing their IP addresses.
// see https://github.com/tailscale/tailscale/issues/3800 for more information.
"hosts": {
"postgresql.internal": "10.20.0.2/32",
"webservers.internal": "10.20.10.1/29"
},
"acls": [
// boss have access to all servers
{
"action": "accept",
"src": ["group:boss"],
"dst": [
"tag:prod-databases:*",
"tag:prod-app-servers:*",
"tag:internal:*",
"tag:dev-databases:*",
"tag:dev-app-servers:*"
]
},
// admin have only access to administrative ports of the servers, in tcp/22
{
"action": "accept",
"src": ["group:admin"],
"proto": "tcp",
"dst": [
"tag:prod-databases:22",
"tag:prod-app-servers:22",
"tag:internal:22",
"tag:dev-databases:22",
"tag:dev-app-servers:22"
]
},
// we also allow admin to ping the servers
{
"action": "accept",
"src": ["group:admin"],
"proto": "icmp",
"dst": [
"tag:prod-databases:*",
"tag:prod-app-servers:*",
"tag:internal:*",
"tag:dev-databases:*",
"tag:dev-app-servers:*"
]
},
// developers have access to databases servers and application servers on all ports
// they can only view the applications servers in prod and have no access to databases servers in production
{
"action": "accept",
"src": ["group:dev"],
"dst": [
"tag:dev-databases:*",
"tag:dev-app-servers:*",
"tag:prod-app-servers:80,443"
]
},
// developers have access to the internal network through the router.
// the internal network is composed of HTTPS endpoints and Postgresql
// database servers.
{
"action": "accept",
"src": ["group:dev"],
"dst": ["10.20.0.0/16:443,5432"]
},
// servers should be able to talk to database in tcp/5432. Database should not be able to initiate connections to
// applications servers
{
"action": "accept",
"src": ["tag:dev-app-servers"],
"proto": "tcp",
"dst": ["tag:dev-databases:5432"]
},
{
"action": "accept",
"src": ["tag:prod-app-servers"],
"dst": ["tag:prod-databases:5432"]
},
// interns have access to dev-app-servers only in reading mode
{
"action": "accept",
"src": ["group:intern"],
"dst": ["tag:dev-app-servers:80,443"]
},
// We still have to allow internal users communications since nothing guarantees that each user have
// their own users.
{ "action": "accept", "src": ["boss@"], "dst": ["boss@:*"] },
{ "action": "accept", "src": ["dev1@"], "dst": ["dev1@:*"] },
{ "action": "accept", "src": ["dev2@"], "dst": ["dev2@:*"] },
{ "action": "accept", "src": ["admin1@"], "dst": ["admin1@:*"] },
{ "action": "accept", "src": ["intern1@"], "dst": ["intern1@:*"] }
]
}
```

129
docs/ref/api.md Normal file
View file

@ -0,0 +1,129 @@
# API
Headscale provides a [HTTP REST API](#rest-api) and a [gRPC interface](#grpc) which may be used to integrate a [web
interface](integration/web-ui.md), [remote control Headscale](#setup-remote-control) or provide a base for custom
integration and tooling.
Both interfaces require a valid API key before use. To create an API key, log into your Headscale server and generate
one with the default expiration of 90 days:
```shell
headscale apikeys create
```
Copy the output of the command and save it for later. Please note that you can not retrieve an API key again. If the API
key is lost, expire the old one, and create a new one.
To list the API keys currently associated with the server:
```shell
headscale apikeys list
```
and to expire an API key:
```shell
headscale apikeys expire --prefix <PREFIX>
```
## REST API
- API endpoint: `/api/v1`, e.g. `https://headscale.example.com/api/v1`
- Documentation: `/swagger`, e.g. `https://headscale.example.com/swagger`
- Headscale Version: `/version`, e.g. `https://headscale.example.com/version`
- Authenticate using HTTP Bearer authentication by sending the [API key](#api) with the HTTP `Authorization: Bearer <API_KEY>` header.
Start by [creating an API key](#api) and test it with the examples below. Read the API documentation provided by your
Headscale server at `/swagger` for details.
=== "Get details for all users"
```console
curl -H "Authorization: Bearer <API_KEY>" \
https://headscale.example.com/api/v1/user
```
=== "Get details for user 'bob'"
```console
curl -H "Authorization: Bearer <API_KEY>" \
https://headscale.example.com/api/v1/user?name=bob
```
=== "Register a node"
```console
curl -H "Authorization: Bearer <API_KEY>" \
--json '{"user": "<USER>", "authId": "AUTH_ID>"}' \
https://headscale.example.com/api/v1/auth/register
```
## gRPC
The gRPC interface can be used to control a Headscale instance from a remote machine with the `headscale` binary.
### Prerequisite
- A workstation to run `headscale` (any supported platform, e.g. Linux).
- A Headscale server with gRPC enabled.
- Connections to the gRPC port (default: `50443`) are allowed.
- Remote access requires an encrypted connection via TLS.
- An [API key](#api) to authenticate with the Headscale server.
### Setup remote control
1. Download the [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases). Make
sure to use the same version as on the server.
1. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headscale`
1. Make `headscale` executable: `chmod +x /usr/local/bin/headscale`
1. [Create an API key](#api) on the Headscale server.
1. Provide the connection parameters for the remote Headscale server either via a minimal YAML configuration file or
via environment variables:
=== "Minimal YAML configuration file"
```yaml title="config.yaml"
cli:
address: <HEADSCALE_ADDRESS>:<PORT>
api_key: <API_KEY>
```
=== "Environment variables"
```shell
export HEADSCALE_CLI_ADDRESS="<HEADSCALE_ADDRESS>:<PORT>"
export HEADSCALE_CLI_API_KEY="<API_KEY>"
```
This instructs the `headscale` binary to connect to a remote instance at `<HEADSCALE_ADDRESS>:<PORT>`, instead of
connecting to the local instance.
1. Test the connection by listing all nodes:
```shell
headscale nodes list
```
You should now be able to see a list of your nodes from your workstation, and you can
now control the Headscale server from your workstation.
### Behind a proxy
It's possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as Headscale.
While this is _not a supported_ feature, an example on how this can be set up on
[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91).
### Troubleshooting
- Make sure you have the _same_ Headscale version on your server and workstation.
- Ensure that connections to the gRPC port are allowed.
- Verify that your TLS certificate is valid and trusted.
- If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
- Add your self-signed certificate to the trust store of your OS _or_
- Disable certificate verification by either setting `cli.insecure: true` in the configuration file or by setting
`HEADSCALE_CLI_INSECURE=1` via an environment variable. We do **not** recommend to disable certificate validation.

View file

@ -17,8 +17,8 @@
=== "View on GitHub"
* Development version: <https://github.com/juanfont/headscale/blob/main/config-example.yaml>
* Version {{ headscale.version }}: <https://github.com/juanfont/headscale/blob/v{{ headscale.version }}/config-example.yaml>
- Development version: <https://github.com/juanfont/headscale/blob/main/config-example.yaml>
- Version {{ headscale.version }}: https://github.com/juanfont/headscale/blob/v{{ headscale.version }}/config-example.yaml
=== "Download with `wget`"

View file

@ -3,16 +3,16 @@
Headscale and Tailscale provide debug and introspection capabilities that can be helpful when things don't work as
expected. This page explains some debugging techniques to help pinpoint problems.
Please also have a look at [Tailscale's Troubleshooting guide](https://tailscale.com/kb/1023/troubleshooting). It offers
a many tips and suggestions to troubleshoot common issues.
Please also have a look at [Tailscale's Troubleshooting guide](https://tailscale.com/docs/reference/troubleshooting). It
offers a many tips and suggestions to troubleshoot common issues.
## Tailscale
The Tailscale client itself offers many commands to introspect its state as well as the state of the network:
- [Check local network conditions](https://tailscale.com/kb/1080/cli#netcheck): `tailscale netcheck`
- [Get the client status](https://tailscale.com/kb/1080/cli#status): `tailscale status --json`
- [Get DNS status](https://tailscale.com/kb/1080/cli#dns): `tailscale dns status --all`
- [Check local network conditions](https://tailscale.com/docs/reference/tailscale-cli#netcheck): `tailscale netcheck`
- [Get the client status](https://tailscale.com/docs/reference/tailscale-cli#status): `tailscale status --json`
- [Get DNS status](https://tailscale.com/docs/reference/tailscale-cli#dns): `tailscale dns status --all`
- Client logs: `tailscale debug daemon-logs`
- Client netmap: `tailscale debug netmap`
- Test DERP connection: `tailscale debug derp headscale`
@ -53,17 +53,20 @@ Headscale provides a metrics and debug endpoint. It allows to introspect differe
- Information about the Go runtime, memory usage and statistics
- Connected nodes and pending registrations
- Active ACLs, filters and SSH policy
- Active policy, filters and SSH policy
- Current DERPMap
- Prometheus metrics
!!! warning "Keep the metrics and debug endpoint private"
The listen address and port can be configured with the `metrics_listen_addr` variable in the [configuration
file](./configuration.md). By default it listens on localhost, port 9090.
file](configuration.md). By default it listens on localhost, port 9090.
Keep the metrics and debug endpoint private to your internal network and don't expose it to the Internet.
The metrics and debug interface can be disabled completely by setting `metrics_listen_addr: null` in the
[configuration file](configuration.md).
Query metrics via <http://localhost:9090/metrics> and get an overview of available debug information via
<http://localhost:9090/debug/>. Metrics may be queried from outside localhost but the debug interface is subject to
additional protection despite listening on all interfaces.

View file

@ -1,13 +1,13 @@
# DERP
A [DERP (Designated Encrypted Relay for Packets) server](https://tailscale.com/kb/1232/derp-servers) is mainly used to
relay traffic between two nodes in case a direct connection can't be established. Headscale provides an embedded DERP
server to ensure seamless connectivity between nodes.
A [DERP (Designated Encrypted Relay for Packets) server](https://tailscale.com/docs/reference/derp-servers) is mainly
used to relay traffic between two nodes in case a direct connection can't be established. Headscale provides an embedded
DERP server to ensure seamless connectivity between nodes.
## Configuration
DERP related settings are configured within the `derp` section of the [configuration file](./configuration.md). The
following sections only use a few of the available settings, check the [example configuration](./configuration.md) for
DERP related settings are configured within the `derp` section of the [configuration file](configuration.md). The
following sections only use a few of the available settings, check the [example configuration](configuration.md) for
all available configuration options.
### Enable embedded DERP
@ -31,8 +31,8 @@ traversal. [Check DERP server connectivity](#check-derp-server-connectivity) to
### Remove Tailscale's DERP servers
Once enabled, Headscale's embedded DERP is added to the list of free-to-use [DERP
servers](https://tailscale.com/kb/1232/derp-servers) offered by Tailscale Inc. To only use Headscale's embedded DERP
server, disable the loading of the default DERP map:
servers](https://tailscale.com/docs/reference/derp-servers) offered by Tailscale Inc. To only use Headscale's embedded
DERP server, disable the loading of the default DERP map:
```yaml title="config.yaml" hl_lines="6"
derp:
@ -59,14 +59,14 @@ maps fetched via URL or to offer your own, custom DERP servers to nodes.
=== "Remove specific DERP regions"
The free-to-use [DERP servers](https://tailscale.com/kb/1232/derp-servers) are organized into regions via a region
ID. You can explicitly disable a specific region by setting its region ID to `null`. The following sample
The free-to-use [DERP servers](https://tailscale.com/docs/reference/derp-servers) are organized into regions via a
region ID. You can explicitly disable a specific region by setting its region ID to `null`. The following sample
`derp.yaml` disables the New York DERP region (which has the region ID 1):
```yaml title="derp.yaml"
regions:
1: null
```
```yaml title="derp.yaml"
regions:
1: null
```
Use the following configuration to serve the default DERP map (excluding New York) to nodes:
@ -163,13 +163,12 @@ Any Tailscale client may be used to introspect the DERP map and to check for con
- Check connectivity with the embedded DERP[^1]:`tailscale debug derp headscale`
Additional DERP related metrics and information is available via the [metrics and debug
endpoint](./debug.md#metrics-and-debug-endpoint).
[^1]:
This assumes that the default region code of the [configuration file](./configuration.md) is used.
endpoint](debug.md#metrics-and-debug-endpoint).
## Limitations
- The embedded DERP server can't be used for Tailscale's captive portal checks as it doesn't support the `/generate_204`
endpoint via HTTP on port tcp/80.
- There are no speed or throughput optimisations, the main purpose is to assist in node connectivity.
[^1]: This assumes that the default region code of the [configuration file](configuration.md) is used.

View file

@ -1,19 +1,19 @@
# DNS
Headscale supports [most DNS features](../about/features.md) from Tailscale. DNS related settings can be configured
within `dns` section of the [configuration file](./configuration.md).
within the `dns` section of the [configuration file](configuration.md).
## Setting extra DNS records
Headscale allows to set extra DNS records which are made available via
[MagicDNS](https://tailscale.com/kb/1081/magicdns). Extra DNS records can be configured either via static entries in the
[configuration file](./configuration.md) or from a JSON file that Headscale continuously watches for changes:
[MagicDNS](https://tailscale.com/docs/features/magicdns). Extra DNS records can be configured either via static entries
in the [configuration file](configuration.md) or from a JSON file that Headscale continuously watches for changes:
- Use the `dns.extra_records` option in the [configuration file](./configuration.md) for entries that are static and
- Use the `dns.extra_records` option in the [configuration file](configuration.md) for entries that are static and
don't change while Headscale is running. Those entries are processed when Headscale is starting up and changes to the
configuration require a restart of Headscale.
- For dynamic DNS records that may be added, updated or removed while Headscale is running or DNS records that are
generated by scripts the option `dns.extra_records_path` in the [configuration file](./configuration.md) is useful.
generated by scripts the option `dns.extra_records_path` in the [configuration file](configuration.md) is useful.
Set it to the absolute path of the JSON file containing DNS records and Headscale processes this file as it detects
changes.
@ -25,7 +25,7 @@ hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:30
Currently, [only A and AAAA records are processed by Tailscale](https://github.com/tailscale/tailscale/blob/v1.86.5/ipn/ipnlocal/node_backend.go#L662).
1. Configure extra DNS records using one of the available configuration options:
1. Configure extra DNS records using one of the available configuration options:
=== "Static entries, via `dns.extra_records`"
@ -66,12 +66,12 @@ hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:30
!!! tip "Good to know"
* The `dns.extra_records_path` option in the [configuration file](./configuration.md) needs to reference the
- The `dns.extra_records_path` option in the [configuration file](configuration.md) needs to reference the
JSON file containing extra DNS records.
* Be sure to "sort keys" and produce a stable output in case you generate the JSON file with a script.
- Be sure to "sort keys" and produce a stable output in case you generate the JSON file with a script.
Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing.
1. Verify that DNS records are properly set using the DNS querying tool of your choice:
1. Verify that DNS records are properly set using the DNS querying tool of your choice:
=== "Query with dig"
@ -87,7 +87,7 @@ hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:30
100.64.0.3
```
1. Optional: Setup the reverse proxy
1. Optional: Setup the reverse proxy
The motivating example here was to be able to access internal monitoring services on the same host without
specifying a port, depicted as NGINX configuration snippet:

View file

@ -31,6 +31,30 @@ tls_cert_path: ""
tls_key_path: ""
```
### Trusted proxies
Headscale ignores `True-Client-IP`, `X-Real-IP` and `X-Forwarded-For`
unless the request's TCP peer matches `trusted_proxies`. Set this to
the CIDR(s) your reverse proxy connects from so the real client IP
appears in access logs:
```yaml title="config.yaml"
trusted_proxies:
- 127.0.0.1/32
- ::1/128
```
The reverse proxy must also strip any client-supplied
`True-Client-IP` / `X-Real-IP` / `X-Forwarded-For` on inbound requests
and set its own values. nginx's `$proxy_add_x_forwarded_for` only
appends to whatever the client sent — pair it with
`proxy_set_header X-Real-IP $remote_addr;` and clear the inbound XFF
yourself if your nginx version does not do so.
Leaving `trusted_proxies` empty when there is no proxy in front is
safe: the headers are dropped from every request and the access log
shows the directly-connecting TCP peer.
## nginx
The following example configuration can be used in your nginx setup, substituting values as necessary. `<IP:PORT>` should be the IP address and port where headscale is running. In most cases, this will be `http://localhost:8080`.

View file

@ -7,10 +7,16 @@
This page collects third-party tools, client libraries, and scripts related to headscale.
| Name | Repository Link | Description |
| --------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- |
| tailscale-manager | [Github](https://github.com/singlestore-labs/tailscale-manager) | Dynamically manage Tailscale route advertisements |
| headscalebacktosqlite | [Github](https://github.com/bigbozza/headscalebacktosqlite) | Migrate headscale from PostgreSQL back to SQLite |
| headscale-pf | [Github](https://github.com/YouSysAdmin/headscale-pf) | Populates user groups based on user groups in Jumpcloud or Authentik |
| headscale-client-go | [Github](https://github.com/hibare/headscale-client-go) | A Go client implementation for the Headscale HTTP API. |
| headscale-zabbix | [Github](https://github.com/dblanque/headscale-zabbix) | A Zabbix Monitoring Template for the Headscale Service. |
- [headscale-operator](https://github.com/infradohq/headscale-operator) - Headscale Kubernetes Operator
- [tailscale-manager](https://github.com/singlestore-labs/tailscale-manager) - Dynamically manage Tailscale route
advertisements
- [headscalebacktosqlite](https://github.com/bigbozza/headscalebacktosqlite) - Migrate headscale from PostgreSQL back to
SQLite
- [headscale-pf](https://github.com/YouSysAdmin/headscale-pf) - Populates user groups based on user groups in Jumpcloud
or Authentik
- [headscale-client-go](https://github.com/hibare/headscale-client-go) - A Go client implementation for the Headscale
HTTP API.
- [headscale-zabbix](https://github.com/dblanque/headscale-zabbix) - A Zabbix Monitoring Template for the Headscale
Service.
- [tailscale-exporter](https://github.com/adinhodovic/tailscale-exporter) - A Prometheus exporter for Headscale that
provides network-level metrics using the Headscale API.

View file

@ -7,14 +7,21 @@
Headscale doesn't provide a built-in web interface but users may pick one from the available options.
| Name | Repository Link | Description |
| ---------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| headscale-ui | [Github](https://github.com/gurucomputing/headscale-ui) | A web frontend for the headscale Tailscale-compatible coordination server |
| HeadscaleUi | [GitHub](https://github.com/simcu/headscale-ui) | A static headscale admin ui, no backend environment required |
| Headplane | [GitHub](https://github.com/tale/headplane) | An advanced Tailscale inspired frontend for headscale |
| headscale-admin | [Github](https://github.com/GoodiesHQ/headscale-admin) | Headscale-Admin is meant to be a simple, modern web interface for headscale |
| ouroboros | [Github](https://github.com/yellowsink/ouroboros) | Ouroboros is designed for users to manage their own devices, rather than for admins |
| unraid-headscale-admin | [Github](https://github.com/ich777/unraid-headscale-admin) | A simple headscale admin UI for Unraid, it offers Local (`docker exec`) and API Mode |
| headscale-console | [Github](https://github.com/rickli-cloud/headscale-console) | WebAssembly-based client supporting SSH, VNC and RDP with optional self-service capabilities |
- [headscale-ui](https://github.com/gurucomputing/headscale-ui) - A web frontend for the headscale Tailscale-compatible
coordination server
- [HeadscaleUi](https://github.com/simcu/headscale-ui) - A static headscale admin ui, no backend environment required
- [Headplane](https://github.com/tale/headplane) - An advanced Tailscale inspired frontend for headscale
- [headscale-admin](https://github.com/GoodiesHQ/headscale-admin) - Headscale-Admin is meant to be a simple, modern web
interface for headscale
- [ouroboros](https://github.com/yellowsink/ouroboros) - Ouroboros is designed for users to manage their own devices,
rather than for admins
- [unraid-headscale-admin](https://github.com/ich777/unraid-headscale-admin) - A simple headscale admin UI for Unraid,
it offers Local (`docker exec`) and API Mode
- [headscale-console](https://github.com/rickli-cloud/headscale-console) - WebAssembly-based client supporting SSH, VNC
and RDP with optional self-service capabilities
- [headscale-piying](https://github.com/wszgrcy/headscale-piying) - headscale web ui, support visual ACL configuration
- [HeadControl](https://github.com/ahmadzip/HeadControl) - Minimal Headscale admin dashboard, built with Go and HTMX
- [Headscale Manager](https://github.com/hkdone/headscalemanager) - Headscale UI for Android
- [Headscale UI](https://github.com/MunMunMiao/headscale-ui) - Headscale UI online and Self-hosting
You can ask for support on our [Discord server](https://discord.gg/c84AZQhmpx) in the "web-interfaces" channel.

View file

@ -40,9 +40,9 @@ A basic configuration connects Headscale to an identity provider and typically r
=== "Identity provider"
* Create a new confidential client (`Client ID`, `Client secret`)
* Add Headscale's OIDC callback URL as valid redirect URL: `https://headscale.example.com/oidc/callback`
* Configure additional parameters to improve user experience such as: name, description, logo, …
- Create a new confidential client (`Client ID`, `Client secret`)
- Add Headscale's OIDC callback URL as valid redirect URL: `https://headscale.example.com/oidc/callback`
- Configure additional parameters to improve user experience such as: name, description, logo, …
### Enable PKCE (recommended)
@ -63,8 +63,8 @@ recommended and needs to be configured for Headscale and the identity provider a
=== "Identity provider"
* Enable PKCE for the headscale client
* Set the PKCE challenge method to "S256"
- Enable PKCE for the headscale client
- Set the PKCE challenge method to "S256"
### Authorize users with filters
@ -75,10 +75,11 @@ are configured, a user needs to pass all of them.
=== "Allowed domains"
* Check the email domain of each authenticating user against the list of allowed domains and only authorize users
- Check the email domain of each authenticating user against the list of allowed domains and only authorize users
whose email domain matches `example.com`.
* Access allowed: `alice@example.com`
* Access denied: `bob@example.net`
- A verified email address is required [unless email verification is disabled](#control-email-verification).
- Access allowed: `alice@example.com`
- Access denied: `bob@example.net`
```yaml hl_lines="5-6"
oidc:
@ -91,10 +92,11 @@ are configured, a user needs to pass all of them.
=== "Allowed users/emails"
* Check the email address of each authenticating user against the list of allowed email addresses and only authorize
- Check the email address of each authenticating user against the list of allowed email addresses and only authorize
users whose email is part of the `allowed_users` list.
* Access allowed: `alice@example.com`, `bob@example.net`
* Access denied: `mallory@example.net`
- A verified email address is required [unless email verification is disabled](#control-email-verification).
- Access allowed: `alice@example.com`, `bob@example.net`
- Access denied: `mallory@example.net`
```yaml hl_lines="5-7"
oidc:
@ -108,10 +110,10 @@ are configured, a user needs to pass all of them.
=== "Allowed groups"
* Use the OIDC `groups` claim of each authenticating user to get their group membership and only authorize users
- Use the OIDC `groups` claim of each authenticating user to get their group membership and only authorize users
which are members in at least one of the referenced groups.
* Access allowed: users in the `headscale_users` group
* Access denied: users without groups, users with other groups
- Access allowed: users in the `headscale_users` group
- Access denied: users without groups, users with other groups
```yaml hl_lines="5-7"
oidc:
@ -123,19 +125,32 @@ are configured, a user needs to pass all of them.
- "headscale_users"
```
### Control email verification
Headscale uses the `email` claim from the identity provider to synchronize the email address to its user profile. By
default, a user's email address is only synchronized when the identity provider reports the email address as verified
via the `email_verified: true` claim.
Unverified emails may be allowed in case an identity provider does not send the `email_verified` claim or email
verification is not required. In that case, a user's email address is always synchronized to the user profile.
```yaml hl_lines="5"
oidc:
issuer: "https://sso.example.com"
client_id: "headscale"
client_secret: "generated-secret"
email_verified_required: false
```
### Customize node expiration
The node expiration is the amount of time a node is authenticated with OpenID Connect until it expires and needs to
reauthenticate. The default node expiration is 180 days. This can either be customized or set to the expiration from the
Access Token.
reauthenticate. The default node expiration can be configured via the top-level `node.expiry` setting.
=== "Customize node expiration"
```yaml hl_lines="5"
oidc:
issuer: "https://sso.example.com"
client_id: "headscale"
client_secret: "generated-secret"
```yaml hl_lines="2"
node:
expiry: 30d # Use 0 to disable node expiration
```
@ -144,7 +159,6 @@ Access Token.
Please keep in mind that the Access Token is typically a short-lived token that expires within a few minutes. You
will have to configure token expiration in your identity provider to avoid frequent re-authentication.
```yaml hl_lines="5"
oidc:
issuer: "https://sso.example.com"
@ -156,6 +170,7 @@ Access Token.
!!! tip "Expire a node and force re-authentication"
A node can be expired immediately via:
```console
headscale node expire -i <NODE_ID>
```
@ -166,13 +181,16 @@ You may refer to users in the Headscale policy via:
- Email address
- Username
- Provider identifier (only available in the database or from your identity provider)
- Provider identifier (this value is currently only available from the [API](api.md), database or directly from your
identity provider)
!!! note "A user identifier in the policy must contain a single `@`"
The Headscale policy requires a single `@` to reference a user. If the username or provider identifier doesn't
already contain a single `@`, it needs to be appended at the end. For example: the username `ssmith` has to be
written as `ssmith@` to be correctly identified as user within the policy.
already contain a single `@`, it needs to be appended at the end. For example: the Headscale username `ssmith` has
to be written as `ssmith@` to be correctly identified as user within the policy.
Ensure that the Headscale username itself does not end with `@`.
!!! warning "Email address or username might be updated by users"
@ -181,6 +199,34 @@ You may refer to users in the Headscale policy via:
consequences for Headscale where a policy might no longer work or a user might obtain more access by hijacking an
existing username or email address.
!!! tip "Howto use the provider identifier in the policy"
The provider identifier uniquely identifies an OIDC user and a well-behaving identity provider guarantees that this
value never changes for a particular user. It is usually an opaque and long string and its value is currently only
available from the [API](api.md), database or directly from your identity provider).
Use the [API](api.md) with the `/api/v1/user` endpoint to fetch the provider identifier (`providerId`). The value
(be sure to append an `@` in case the provider identifier doesn't already contain an `@` somewhere) can be used
directly to reference a user in the policy. To improve readability of the policy, one may use the `groups` section
as an alias:
```json
{
"groups": {
"group:alice": [
"https://sso.example.com/oauth2/openid/59ac9125-c31b-46c5-814e-06242908cf57@"
]
},
"grants": [
{
"src": ["group:alice"],
"dst": ["*"],
"ip": ["*"]
}
]
}
```
## Supported OIDC claims
Headscale uses [the standard OIDC claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) to
@ -189,7 +235,7 @@ endpoint.
| Headscale profile | OIDC claim | Notes / examples |
| ------------------- | -------------------- | ------------------------------------------------------------------------------------------------- |
| email address | `email` | Only used when `email_verified: true` |
| email address | `email` | Only verified emails are synchronized, unless `email_verified_required: false` is configured |
| display name | `name` | eg: `Sam Smith` |
| username | `preferred_username` | Depends on identity provider, eg: `ssmith`, `ssmith@idp.example.com`, `\\example.com\ssmith` |
| profile picture | `picture` | URL to a profile picture or avatar |
@ -251,13 +297,11 @@ For detailed Headplane OIDC configuration, see the [Headplane documentation](htt
- Support for OpenID Connect aims to be generic and vendor independent. It offers only limited support for quirks of
specific identity providers.
- OIDC groups cannot be directly used in Headscale ACLs (use external integrations for role-based access control).
- OIDC groups cannot be used in policy rules directly (use external integrations for role-based access control).
- The username provided by the identity provider needs to adhere to this pattern:
- The username must be at least two characters long.
- It must only contain letters, digits, hyphens, dots, underscores, and up to a single `@`.
- The username must start with a letter.
- A user's email address is only synchronized to the local user profile when the identity provider marks the email
address as verified (`email_verified: true`).
Please see the [GitHub label "OIDC"](https://github.com/juanfont/headscale/labels/OIDC) for OIDC related issues.
@ -284,14 +328,15 @@ Authelia is fully supported by Headscale.
### Authentik
- Authentik is fully supported by Headscale.
- [Headscale does not JSON Web Encryption](https://github.com/juanfont/headscale/issues/2446). Leave the field
- [Headscale does not support JSON Web Encryption](https://github.com/juanfont/headscale/issues/2446). Leave the field
`Encryption Key` in the providers section unset.
- See Authentik's [Integrate with Headscale](https://integrations.goauthentik.io/networking/headscale/)
### Google OAuth
!!! warning "No username due to missing preferred_username"
!!! warning "No username due to missing preferred_username claim"
Google OAuth does not send the `preferred_username` claim when the scope `profile` is requested. The username in
Google OAuth does not send the `preferred_username` claim when the `profile` scope is requested. The username in
Headscale will be blank/not set.
In order to integrate Headscale with Google, you'll need to have a [Google Cloud
@ -307,22 +352,30 @@ Console.
#### Steps
1. Go to [Google Console](https://console.cloud.google.com) and login or create an account if you don't have one.
2. Create a project (if you don't already have one).
3. On the left hand menu, go to `APIs and services` -> `Credentials`
4. Click `Create Credentials` -> `OAuth client ID`
5. Under `Application Type`, choose `Web Application`
6. For `Name`, enter whatever you like
7. Under `Authorised redirect URIs`, add Headscale's OIDC callback URL: `https://headscale.example.com/oidc/callback`
8. Click `Save` at the bottom of the form
9. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it.
10. [Configure Headscale following the "Basic configuration" steps](#basic-configuration). The issuer URL for Google
OAuth is: `https://accounts.google.com`.
1. Create a project (if you don't already have one).
1. On the left hand menu, go to `APIs and services` -> `Credentials`
1. Click `Create Credentials` -> `OAuth client ID`
1. Under `Application Type`, choose `Web Application`
1. For `Name`, enter whatever you like
1. Under `Authorised redirect URIs`, add Headscale's OIDC callback URL: `https://headscale.example.com/oidc/callback`
1. Click `Save` at the bottom of the form
1. Take note of the `Client ID` and `Client secret`, you can also download it for reference if you need it.
1. [Configure Headscale following the "Basic configuration" steps](#basic-configuration). The issuer URL for Google
OAuth is: `https://accounts.google.com`.
### Kanidm
- Kanidm is fully supported by Headscale.
- Groups for the [allowed groups filter](#authorize-users-with-filters) need to be specified with their full SPN, for
example: `headscale_users@sso.example.com`.
- Kanidm sends the full SPN (`alice@sso.example.com`) as `preferred_username` by default. Headscale stores this value as
username which might be confusing as the username and email fields now contain values that look like an email address.
[Kanidm can be configured to send the short username as `preferred_username` attribute
instead](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html#short-names):
```console
kanidm system oauth2 prefer-short-username <client name>
```
Once configured, the short username in Headscale will be `alice` and can be referred to as `alice@` in the policy.
### Keycloak
@ -356,5 +409,19 @@ Entra ID is: `https://login.microsoftonline.com/<tenant-UUID>/v2.0`. The followi
- `domain_hint: example.com` to use your own domain
- `prompt: select_account` to force an account picker during login
Groups for the [allowed groups filter](#authorize-users-with-filters) need to be specified with their group ID instead
When using Microsoft Entra ID together with the [allowed groups filter](#authorize-users-with-filters), configure the
Headscale OIDC scope without the `groups` claim, for example:
```yaml
oidc:
scope: ["openid", "profile", "email"]
```
Groups for the [allowed groups filter](#authorize-users-with-filters) need to be specified with their group ID(UUID) instead
of the group name.
## Switching OIDC providers
Headscale only supports a single OIDC provider in its configuration, but it does store the provider identifier of each user. When switching providers, this might lead to issues with existing users: all user details (name, email, groups) might be identical with the new provider, but the identifier will differ. Headscale will be unable to create a new user as the name and email will already be in use for the existing users.
At this time, you will need to manually update the `provider_identifier` column in the `users` table for each user with the appropriate value for the new provider. The identifier is built from the `iss` and `sub` claims of the OIDC ID token, for example `https://id.example.com/12340987`.

249
docs/ref/policy.md Normal file
View file

@ -0,0 +1,249 @@
# Policy
Headscale implements a large portion of Tailscale's [policy
features](https://tailscale.com/docs/features/tailnet-policy-file), most notably access control based on
[ACLs](https://tailscale.com/docs/features/access-control/acls) and
[Grants](https://tailscale.com/docs/features/access-control/grants) or [Tailscale
SSH](https://tailscale.com/docs/features/tailscale-ssh). See [limitations](#limitations) to learn about missing features
and notable implementation differences between Headscale and Tailscale.
Headscale uses the same [huJSON](https://github.com/tailscale/hujson) based file format as Tailscale. By default, no
policy is loaded which means that Headscale allows all traffic between nodes. To start using a policy file[^1], specify
its path in the `policy.path` key in the [configuration file](configuration.md).
Headscale needs to be reloaded to pick up changes to the policy file. Either reload Headscale via its systemd service
(`sudo systemctl reload headscale`) or by sending a SIGHUP signal (`sudo kill -HUP $(pidof headscale)`) to the main
process. Headscale logs the result of policy processing after each reload.
Please have a look at Tailscale's policy related documentation to learn more:
- [Tailscale policy file](https://tailscale.com/docs/features/tailnet-policy-file): A description of supported sections
within the policy file along with links to syntax references for each section.
- [ACLs](https://tailscale.com/docs/features/access-control/acls): How to configure access control using ACLs.
- [Grants](https://tailscale.com/docs/features/access-control/grants): Introduction to Grants with links to [syntax
reference](https://tailscale.com/docs/reference/syntax/grants),
[examples](https://tailscale.com/docs/reference/examples/grants) and a [migration guide from ACLs to
Grants](https://tailscale.com/docs/reference/migrate-acls-grants).
## Getting started
Headscale supports both [ACLs](https://tailscale.com/docs/features/access-control/acls) and
[Grants](https://tailscale.com/docs/features/access-control/grants) to write an access control policy. We recommend the
use of Grants since ACLs are considered legacy and will not receive new features by Tailscale.
### Allow All
If you define a policy file but completely omit the `"acls"` or `"grants"` section, Headscale will default to an [allow
all](https://tailscale.com/docs/reference/examples/acls#allow-all-default-acl) policy. This means all devices connected
to your tailnet will be able to communicate freely with each other.
```json title="policy.json"
{}
```
### Deny All
To [prevent all communication within your tailnet](https://tailscale.com/docs/reference/examples/acls#deny-all), you can
include an empty array for the `"grants"` section in your policy file.
```json title="policy.json"
{
"grants": []
}
```
### More examples
- See our documentation on [subnet routers](routes.md#subnet-router) and [exit nodes](routes.md#exit-node) to learn how
to restrict their use or how to automatically approve them.
- The Tailscale documentation provides a large collection of configuration examples:
- [ACL examples](https://tailscale.com/docs/reference/examples/acls)
- [Grants examples](https://tailscale.com/docs/reference/examples/grants)
- [SSH configuration](https://tailscale.com/docs/features/tailscale-ssh#configure-tailscale-ssh)
- [Define a tag](https://tailscale.com/docs/features/tags#define-a-tag)
______________________________________________________________________
## Limitations
- [Device postures](https://tailscale.com/docs/features/device-posture) and the related sections such as `postures` or
`srcPosture` aren't supported.
- [IP sets](https://tailscale.com/docs/features/tailnet-policy-file/ip-sets) aren't supported.
- A subset of [Autogroups](#autogroups) are available.
## Autogroups
Headscale supports several [Autogroups](https://tailscale.com/docs/reference/targets-and-selectors#autogroups) that
automatically include users, destinations, or devices with specific properties. Autogroups provide a convenient way to
write policy rules without manually listing individual users or devices.
### [`autogroup:internet`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupinternet)
Allows access to the internet through [exit nodes](routes.md#exit-node). Can only be used in policy destinations.
```json title="policy.json"
{
"grants": [
{
"src": ["alice@"],
"dst": ["autogroup:internet"],
"ip": ["*"]
}
]
}
```
### [`autogroup:member`](https://tailscale.com/docs/reference/targets-and-selectors#autogrouprole)
Includes all [personal (untagged) devices](registration.md/#identity-model).
```json title="policy.json"
{
"grants": [
{
"src": ["autogroup:member"],
"dst": ["tag:prod-app-servers"],
"ip": ["80,443"]
}
]
}
```
### [`autogroup:tagged`](https://tailscale.com/docs/reference/targets-and-selectors#autogrouptagged)
Includes all devices that [have at least one tag](registration.md/#identity-model).
```json title="policy.json"
{
"grants": [
{
"src": ["autogroup:tagged"],
"dst": ["tag:monitoring"],
"ip": ["9090"]
}
]
}
```
### [`autogroup:self`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupself)
Includes devices where the same user is authenticated on both the source and destination. Does not include tagged
devices. Can only be used in policy destinations.
```json title="policy.json"
{
"grants": [
{
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"ip": ["*"]
}
]
}
```
!!! warning "The current implementation of `autogroup:self` is inefficient"
Using `autogroup:self` may cause performance degradation on the Headscale coordinator server in large deployments,
as filter rules must be compiled per-node rather than globally and the current implementation is not very efficient.
If you experience performance issues, consider using more specific policy rules or limiting the use of
`autogroup:self`.
```json title="policy.json"
{
"grants": [
// The following rules allow internal users to communicate with their
// own nodes in case autogroup:self is causing performance issues.
{
"src": ["boss@"],
"dst": ["boss@"],
"ip": "*"
},
{
"src": ["dev1@"],
"dst": ["dev1@"],
"ip": "*"
},
{
"src": ["intern1@"],
"dst": ["intern1@"],
"ip": "*"
}
]
}
```
### [`autogroup:nonroot`](https://tailscale.com/docs/reference/targets-and-selectors#other-built-in-targets)
Used in Tailscale SSH rules to allow access to any user except root. Can only be used in the `users` field of SSH rules.
```json title="policy.json"
{
"ssh": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot"]
}
]
}
```
### [`autogroup:danger-all`](https://tailscale.com/docs/reference/targets-and-selectors#autogroupdanger-all)
This autogroup resolves to all IP addresses (`0.0.0.0/0` and `::/0`) which also includes all IP addresses outside the
standard Tailscale IP ranges. This autogroup can only be used as source.
## Node Attributes
[Node attributes](https://tailscale.com/docs/reference/syntax/policy-file#node-attributes) allow for device-specific
configuration and attributes. At least the following node attributes are currently supported by Headscale[^2]:
- `drive:access`, `drive:share`: [Taildrive support](https://tailscale.com/docs/features/taildrive).
- `nextdns:<profile>`, `nextdns:no-device-info`: [NextDNS integration](https://tailscale.com/docs/integrations/nextdns).
Be sure to set NextDNS as global resolver in the [configuration](configuration.md).
- `magicdns-aaaa`: Respond to AAAA queries on the local [MagicDNS](https://tailscale.com/docs/features/magicdns)
resolver at 100.100.100.100.
- `disable-ipv4`: Selectively disable IPv4 for specfic nodes. This is may be useful to workaround [CGNat
conflicts](https://tailscale.com/docs/reference/troubleshooting/network-configuration/cgnat-conflicts).
- `randomize-client-port`: Allocate a [random port for WireGuard
traffic](https://tailscale.com/docs/reference/syntax/policy-file#randomizeclientport) instead of the static default
port 41641.
- `disable-captive-portal-detection`: [Disable automatic captive portal
detection](https://tailscale.com/docs/integrations/captive-portals#disable-captive-portal-detection).
```json title="policy.json"
{
"nodeAttrs": [
{
// Enable MagicDNS AAAA records for all nodes
"target": ["*"]
"attr": ["magicdns-aaaa"]
}
]
}
```
## Network-wide policy options
The following options are applied for the entire tailnet. Consider [node attributes](#node-attributes) for a more
fine-grained configuration instead.
- `randomizeClientPort`: Allocate a [random port for WireGuard
traffic](https://tailscale.com/docs/reference/syntax/policy-file#randomizeclientport) instead of the static default
port 41641.
```json title="policy.json"
{
// Use a random WireGuard port for the entire tailnet
"randomizeClientPort": true
}
```
[^1]: Headscale also allows to store the policy in the database. This is typically only required in case a [web
interface](integration/web-ui.md) is used.
[^2]: Other key-only node attributes can be used as well. Find them in the client source code with `grep -E '^\s+NodeAttr\w+' tailcfg/tailcfg.go` or by using [GitHub code search (requires
login)](https://github.com/search?q=repo%3Atailscale%2Ftailscale%20language%3Ago%20path%3Atailcfg%2Ftailcfg.go%20symbol%3A%2FNodeAttr%5Cw%2B%2F&type=code).

144
docs/ref/registration.md Normal file
View file

@ -0,0 +1,144 @@
# Registration methods
Headscale supports multiple ways to register a node. The preferred registration method depends on the identity of a node
and your use case.
## Identity model
Tailscale's identity model distinguishes between personal and tagged nodes:
- A personal node (or user-owned node) is owned by a human and typically refers to end-user devices such as laptops,
workstations or mobile phones. End-user devices are managed by a single user.
- A tagged node (or service-based node or non-human node) provides services to the network. Common examples include web-
and database servers. Those nodes are typically managed by a team of users. Some additional restrictions apply for
tagged nodes, e.g. a tagged node is not allowed to [Tailscale SSH](https://tailscale.com/docs/features/tailscale-ssh)
into a personal node.
Headscale implements Tailscale's identity model and distinguishes between personal and tagged nodes where a personal
node is owned by a Headscale user and a tagged node is owned by a tag. Tagged devices are grouped under the special user
`tagged-devices`.
## Registration methods
There are two main ways to register new nodes, [web authentication](#web-authentication) and [registration with a pre
authenticated key](#pre-authenticated-key). Both methods can be used to register personal and tagged nodes.
### Web authentication
Web authentication is the default method to register a new node. It's interactive, where the client initiates the
registration and the Headscale administrator needs to approve the new node before it is allowed to join the network. A
node can be approved with:
- Headscale CLI (described in this documentation)
- [Headscale API](api.md)
- Or delegated to an identity provider via [OpenID Connect](oidc.md)
Web authentication relies on the presence of a Headscale user. Use the `headscale users` command to create a new
user[^1]:
```console
headscale users create <USER>
```
=== "Personal devices"
Run `tailscale up` to login your personal device:
```console
tailscale up --login-server <YOUR_HEADSCALE_URL>
```
Usually, a browser window with further instructions is opened. This page explains how to complete the registration
on your Headscale server and it also prints the Auth ID required to approve the node:
```console
headscale auth register --user <USER> --auth-id <AUTH_ID>
```
Congrations, the registration of your personal node is complete and it should be listed as "online" in the output of
`headscale nodes list`. The "User" column displays `<USER>` as the owner of the node.
=== "Tagged devices"
Your Headscale user needs to be authorized to register tagged devices. This authorization is specified in the
[`tagOwners`](https://tailscale.com/docs/reference/syntax/policy-file#tag-owners) section of the
[policy](policy.md). A simple example looks like this:
```json title="The user alice can register nodes tagged with tag:server"
{
"tagOwners": {
"tag:server": ["alice@"]
},
// more rules
}
```
Run `tailscale up` and provide at least one tag to login a tagged device:
```console
tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:<TAG>
```
Usually, a browser window with further instructions is opened. This page explains how to complete the registration
on your Headscale server and it also prints the Auth ID required to approve the node:
```console
headscale auth register --user <USER> --auth-id <AUTH_ID>
```
Headscale checks that `<USER>` is allowed to register a node with the specified tag(s) and then transfers ownership
of the new node to the special user `tagged-devices`. The registration of a tagged node is complete and it should be
listed as "online" in the output of `headscale nodes list`. The "User" column displays `tagged-devices` as the owner
of the node. See the "Tags" column for the list of assigned tags.
### Pre authenticated key
Registration with a pre authenticated key (or auth key) is a non-interactive way to register a new node. The Headscale
administrator creates a preauthkey upfront and this preauthkey can then be used to register a node non-interactively.
Its best suited for automation.
=== "Personal devices"
A personal node is always assigned to a Headscale user. Use the `headscale users` command to create a new user[^1]:
```console
headscale users create <USER>
```
Use the `headscale user list` command to learn its `<USER_ID>` and create a new pre authenticated key for your user:
```console
headscale preauthkeys create --user <USER_ID>
```
The above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use
this auth key to register a node non-interactively:
```console
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
Congrations, the registration of your personal node is complete and it should be listed as "online" in the output of
`headscale nodes list`. The "User" column displays `<USER>` as the owner of the node.
=== "Tagged devices"
Create a new pre authenticated key and provide at least one tag:
```console
headscale preauthkeys create --tags tag:<TAG>
```
The above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use
this auth key to register a node non-interactively. You don't need to provide the `--advertise-tags` parameter as
the tags are automatically read from the pre authenticated key:
```console
tailscale up --login-server <YOUR_HEADSCALE_URL> --authkey <YOUR_AUTH_KEY>
```
The registration of a tagged node is complete and it should be listed as "online" in the output of
`headscale nodes list`. The "User" column displays `tagged-devices` as the owner of the node. See the "Tags" column for the list of
assigned tags.
[^1]: [Ensure that the Headscale username does not end with `@`.](oidc.md#reference-a-user-in-the-policy)

View file

@ -1,105 +0,0 @@
# Controlling headscale with remote CLI
This documentation has the goal of showing a user how-to control a headscale instance
from a remote machine with the `headscale` command line binary.
## Prerequisite
- A workstation to run `headscale` (any supported platform, e.g. Linux).
- A headscale server with gRPC enabled.
- Connections to the gRPC port (default: `50443`) are allowed.
- Remote access requires an encrypted connection via TLS.
- An API key to authenticate with the headscale server.
## Create an API key
We need to create an API key to authenticate with the remote headscale server when using it from our workstation.
To create an API key, log into your headscale server and generate a key:
```shell
headscale apikeys create --expiration 90d
```
Copy the output of the command and save it for later. Please note that you can not retrieve a key again,
if the key is lost, expire the old one, and create a new key.
To list the keys currently associated with the server:
```shell
headscale apikeys list
```
and to expire a key:
```shell
headscale apikeys expire --prefix "<PREFIX>"
```
## Download and configure headscale
1. Download the [`headscale` binary from GitHub's release page](https://github.com/juanfont/headscale/releases). Make
sure to use the same version as on the server.
1. Put the binary somewhere in your `PATH`, e.g. `/usr/local/bin/headscale`
1. Make `headscale` executable:
```shell
chmod +x /usr/local/bin/headscale
```
1. Provide the connection parameters for the remote headscale server either via a minimal YAML configuration file or via
environment variables:
=== "Minimal YAML configuration file"
```yaml title="config.yaml"
cli:
address: <HEADSCALE_ADDRESS>:<PORT>
api_key: <API_KEY_FROM_PREVIOUS_STEP>
```
=== "Environment variables"
```shell
export HEADSCALE_CLI_ADDRESS="<HEADSCALE_ADDRESS>:<PORT>"
export HEADSCALE_CLI_API_KEY="<API_KEY_FROM_PREVIOUS_STEP>"
```
!!! bug
Headscale currently requires at least an empty configuration file when environment variables are used to
specify connection details. See [issue 2193](https://github.com/juanfont/headscale/issues/2193) for more
information.
This instructs the `headscale` binary to connect to a remote instance at `<HEADSCALE_ADDRESS>:<PORT>`, instead of
connecting to the local instance.
1. Test the connection
Let us run the headscale command to verify that we can connect by listing our nodes:
```shell
headscale nodes list
```
You should now be able to see a list of your nodes from your workstation, and you can
now control the headscale server from your workstation.
## Behind a proxy
It is possible to run the gRPC remote endpoint behind a reverse proxy, like Nginx, and have it run on the _same_ port as headscale.
While this is _not a supported_ feature, an example on how this can be set up on
[NixOS is shown here](https://github.com/kradalby/dotfiles/blob/4489cdbb19cddfbfae82cd70448a38fde5a76711/machines/headscale.oracldn/headscale.nix#L61-L91).
## Troubleshooting
- Make sure you have the _same_ headscale version on your server and workstation.
- Ensure that connections to the gRPC port are allowed.
- Verify that your TLS certificate is valid and trusted.
- If you don't have access to a trusted certificate (e.g. from Let's Encrypt), either:
- Add your self-signed certificate to the trust store of your OS _or_
- Disable certificate verification by either setting `cli.insecure: true` in the configuration file or by setting
`HEADSCALE_CLI_INSECURE=1` via an environment variable. We do **not** recommend to disable certificate validation.

View file

@ -1,7 +1,8 @@
# Routes
Headscale supports route advertising and can be used to manage [subnet routers](https://tailscale.com/kb/1019/subnets)
and [exit nodes](https://tailscale.com/kb/1103/exit-nodes) for a tailnet.
Headscale supports route advertising and can be used to manage [subnet
routers](https://tailscale.com/docs/features/subnet-routers) and [exit
nodes](https://tailscale.com/docs/features/exit-nodes) for a tailnet.
- [Subnet routers](#subnet-router) may be used to connect an existing network such as a virtual
private cloud or an on-premise network with your tailnet. Use a subnet router to access devices where Tailscale can't
@ -42,8 +43,9 @@ can be used.
```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | myrouter | | 10.0.0.0/8, 192.168.0.0/24 |
ID | Hostname | Approved | Available | Serving (Primary)
1 | myrouter | | 10.0.0.0/8 |
| | | 192.168.0.0/24 |
```
Approve all desired routes of a subnet router by specifying them as comma separated list:
@ -57,8 +59,9 @@ The node `myrouter` can now route the IPv4 networks `10.0.0.0/8` and `192.168.0.
```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | myrouter | 10.0.0.0/8, 192.168.0.0/24 | 10.0.0.0/8, 192.168.0.0/24 | 10.0.0.0/8, 192.168.0.0/24
ID | Hostname | Approved | Available | Serving (Primary)
1 | myrouter | 10.0.0.0/8 | 10.0.0.0/8 | 10.0.0.0/8
| | 192.168.0.0/24 | 192.168.0.0/24 | 192.168.0.0/24
```
#### Use the subnet router
@ -70,32 +73,32 @@ $ sudo tailscale set --accept-routes
```
Please refer to the official [Tailscale
documentation](https://tailscale.com/kb/1019/subnets#use-your-subnet-routes-from-other-devices) for how to use a subnet
router on different operating systems.
documentation](https://tailscale.com/docs/features/subnet-routers#use-your-subnet-routes-from-other-devices) for how to
use a subnet router on different operating systems.
### Restrict the use of a subnet router with ACL
### Restrict the use of a subnet router with a policy
The routes announced by subnet routers are available to the nodes in a tailnet. By default, without an ACL enabled, all
nodes can accept and use such routes. Configure an ACL to explicitly manage who can use routes.
The routes announced by subnet routers are available to the nodes in a tailnet. By default, without a policy enabled,
all nodes can accept and use such routes. Configure a policy to explicitly manage who can use routes.
The ACL snippet below defines three hosts, a subnet router `router`, a regular node `node` and `service.example.net` as
internal service that can be reached via a route on the subnet router `router`. It allows the node `node` to access
The policy snippet below defines three hosts, a subnet router `router`, a regular node `node` and `service.example.net`
as internal service that can be reached via a route on the subnet router `router`. It allows the node `node` to access
`service.example.net` on port 80 and 443 which is reachable via the subnet router. Access to the subnet router itself is
denied.
```json title="Access the routes of a subnet router without the subnet router itself"
{
"hosts": {
// the router is not referenced but announces 192.168.0.0/24"
// the router is not referenced but announces 192.168.0.0/24
"router": "100.64.0.1/32",
"node": "100.64.0.2/32",
"service.example.net": "192.168.0.1/32"
},
"acls": [
"grants": [
{
"action": "accept",
"src": ["node"],
"dst": ["service.example.net:80,443"]
"dst": ["service.example.net"],
"ip": ["80,443"]
}
]
}
@ -104,14 +107,14 @@ denied.
### Automatically approve routes of a subnet router
The initial setup of a subnet router usually requires manual approval of their announced routes on the control server
before they can be used by a node in a tailnet. Headscale supports the `autoApprovers` section of an ACL to automate the
approval of routes served with a subnet router.
before they can be used by a node in a tailnet. Headscale supports the `autoApprovers` section in a policy to automate
the approval of routes served with a subnet router.
The ACL snippet below defines the tag `tag:router` owned by the user `alice`. This tag is used for `routes` in the
The policy snippet below defines the tag `tag:router` owned by the user `alice`. This tag is used for `routes` in the
`autoApprovers` section. The IPv4 route `192.168.0.0/24` is automatically approved once announced by a subnet router
owned by the user `alice` and that also advertises the tag `tag:router`.
that advertises the tag `tag:router`.
```json title="Subnet routers owned by alice and tagged with tag:router are automatically approved"
```json title="Subnet routers tagged with tag:router are automatically approved"
{
"tagOwners": {
"tag:router": ["alice@"]
@ -121,7 +124,7 @@ owned by the user `alice` and that also advertises the tag `tag:router`.
"192.168.0.0/24": ["tag:router"]
}
},
"acls": [
"grants": [
// more rules
]
}
@ -133,8 +136,9 @@ Advertise the route `192.168.0.0/24` from a subnet router that also advertises t
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:router --advertise-routes 192.168.0.0/24
```
Please see the [official Tailscale documentation](https://tailscale.com/kb/1337/acl-syntax#autoapprovers) for more
information on auto approvers.
Please see the [official Tailscale
documentation](https://tailscale.com/docs/reference/syntax/policy-file#auto-approvers) for more information on auto
approvers.
## Exit node
@ -168,8 +172,9 @@ available, but needs to be approved:
```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | myexit | | 0.0.0.0/0, ::/0 |
ID | Hostname | Approved | Available | Serving (Primary)
1 | myexit | | 0.0.0.0/0 |
| | | ::/0 |
```
For exit nodes, it is sufficient to approve either the IPv4 or IPv6 route. The other will be approved automatically.
@ -183,8 +188,9 @@ The node `myexit` is now approved as exit node for the tailnet:
```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | myexit | 0.0.0.0/0, ::/0 | 0.0.0.0/0, ::/0 | 0.0.0.0/0, ::/0
ID | Hostname | Approved | Available | Serving (Primary)
1 | myexit | 0.0.0.0/0 | 0.0.0.0/0 | 0.0.0.0/0
| | ::/0 | ::/0 | ::/0
```
#### Use the exit node
@ -195,22 +201,51 @@ The exit node can now be used on a node with:
$ sudo tailscale set --exit-node myexit
```
Please refer to the official [Tailscale documentation](https://tailscale.com/kb/1103/exit-nodes#use-the-exit-node) for
how to use an exit node on different operating systems.
Please refer to the official [Tailscale documentation](https://tailscale.com/docs/features/exit-nodes#use-the-exit-node)
for how to use an exit node on different operating systems.
### Restrict the use of an exit node with ACL
### Restrict the use of an exit node with a policy
An exit node is offered to all nodes in a tailnet. By default, without an ACL enabled, all nodes in a tailnet can select
and use an exit node. Configure `autogroup:internet` in an ACL rule to restrict who can use _any_ of the available exit
nodes.
An exit node is offered to all nodes in a tailnet. By default, without a policy enabled, all nodes in a tailnet can
select and use an exit node. Configure `autogroup:internet` in a policy rule to restrict who can use _any_ of the
available exit nodes.
```json title="Example use of autogroup:internet"
{
"acls": [
"grants": [
{
"action": "accept",
"src": ["..."],
"dst": ["autogroup:internet:*"]
"dst": ["autogroup:internet"],
"ip": ["*"]
}
]
}
```
### Restrict access to exit nodes per user or group
A user can use _any_ of the available exit nodes with `autogroup:internet`. Alternatively, the policy snippet below
assigns each user a specific exit node while hiding all other exit nodes. The user `alice` can only use an exit node
tagged with `tag:exit1` while user `bob` can only use an exit node tagged with `tag:exit2`.
```json title="Assign each user a dedicated exit node"
{
"tagOwners": {
"tag:exit1": ["alice@"],
"tag:exit2": ["bob@"]
},
"grants": [
{
"src": ["alice@"],
"dst": ["autogroup:internet"],
"via": ["tag:exit1"],
"ip": ["*"]
},
{
"src": ["bob@"],
"dst": ["autogroup:internet"],
"via": ["tag:exit2"],
"ip": ["*"]
}
]
}
@ -219,14 +254,13 @@ nodes.
### Automatically approve an exit node with auto approvers
The initial setup of an exit node usually requires manual approval on the control server before it can be used by a node
in a tailnet. Headscale supports the `autoApprovers` section of an ACL to automate the approval of a new exit node as
in a tailnet. Headscale supports the `autoApprovers` section in a policy to automate the approval of a new exit node as
soon as it joins the tailnet.
The ACL snippet below defines the tag `tag:exit` owned by the user `alice`. This tag is used for `exitNode` in the
`autoApprovers` section. A new exit node which is owned by the user `alice` and that also advertises the tag `tag:exit`
is automatically approved:
The policy snippet below defines the tag `tag:exit` owned by the user `alice`. This tag is used for the `exitNode` entry
in the `autoApprovers` section. A new exit node that advertises the tag `tag:exit` is automatically approved:
```json title="Exit nodes owned by alice and tagged with tag:exit are automatically approved"
```json title="Exit nodes tagged with tag:exit are automatically approved"
{
"tagOwners": {
"tag:exit": ["alice@"]
@ -234,7 +268,7 @@ is automatically approved:
"autoApprovers": {
"exitNode": ["tag:exit"]
},
"acls": [
"grants": [
// more rules
]
}
@ -246,26 +280,23 @@ Advertise a node as exit node and also advertise the tag `tag:exit` when joining
$ sudo tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags tag:exit --advertise-exit-node
```
Please see the [official Tailscale documentation](https://tailscale.com/kb/1337/acl-syntax#autoapprovers) for more
information on auto approvers.
Please see the [official Tailscale documentation](https://tailscale.com/docs/reference/syntax/policy-file#autoapprovers)
for more information on auto approvers.
## High availability
Headscale has limited support for high availability routing. Multiple subnet routers with overlapping routes or multiple
exit nodes can be used to provide high availability for users. If one router node goes offline, another one can serve
the same routes to clients. Please see the official [Tailscale documentation on high
availability](https://tailscale.com/kb/1115/high-availability#subnet-router-high-availability) for details.
Headscale supports high availability routing. Multiple subnet routers with overlapping routes or multiple exit nodes can
be used to provide high availability for users. If one router node goes offline, another one can serve the same routes
to clients. Please see the official [Tailscale documentation on high
availability](https://tailscale.com/docs/how-to/set-up-high-availability#subnet-router-high-availability) for details.
!!! bug
In certain situations it might take up to 16 minutes for Headscale to detect a node as offline. A failover node
might not be selected fast enough, if such a node is used as subnet router or exit node causing service
interruptions for clients. See [issue 2129](https://github.com/juanfont/headscale/issues/2129) for more information.
This feature is enabled by default when at least two nodes advertise the same prefix. See the configuration options
`node.routes.ha` in the [configuration file](configuration.md) for details.
## Troubleshooting
### Enable IP forwarding
A subnet router or exit node is routing traffic on behalf of other nodes and thus requires IP forwarding. Check the
official [Tailscale documentation](https://tailscale.com/kb/1019/subnets/?tab=linux#enable-ip-forwarding) for how to
official [Tailscale documentation](https://tailscale.com/docs/features/subnet-routers#enable-ip-forwarding) for how to
enable IP forwarding.

54
docs/ref/tags.md Normal file
View file

@ -0,0 +1,54 @@
# Tags
Headscale supports Tailscale tags. Please read [Tailscale's tag documentation](https://tailscale.com/docs/features/tags)
to learn how tags work and how to use them.
Tags can be applied during [node registration](registration.md):
- using the `--advertise-tags` flag, see [web authentication for tagged devices](registration.md#__tabbed_1_2)
- using a tagged pre authenticated key, see [how to create and use it](registration.md#__tabbed_2_2)
Administrators can manage tags with:
- Headscale CLI
- [Headscale API](api.md)
## Common operations
### Manage tags for a node
Run `headscale nodes list` to list the tags for a node.
Use the `headscale nodes tag` command to modify the tags for a node. At least one tag is required and multiple tags can
be provided as comma separated list. The following command sets the tags `tag:server` and `tag:prod` on node with ID 1:
```console
headscale nodes tag -i 1 -t tag:server,tag:prod
```
### Convert from personal to tagged node
Use the `headscale nodes tag` command to convert a personal (user-owned) node to a tagged node:
```console
headscale nodes tag -i <NODE_ID> -t <TAG>
```
The node is now owned by the special user `tagged-devices` and has the specified tags assigned to it.
### Convert from tagged to personal node
Tagged nodes can return to personal (user-owned) nodes by re-authenticating with:
```console
tailscale up --login-server <YOUR_HEADSCALE_URL> --advertise-tags= --force-reauth
```
Usually, a browser window with further instructions is opened. This page explains how to complete the registration on
your Headscale server and it also prints the Auth ID required to approve the node:
```console
headscale auth register --user <USER> --auth-id <AUTH_ID>
```
All previously assigned tags get removed and the node is now owned by the user specified in the above command.

View file

@ -50,7 +50,7 @@ Headscale uses [autocert](https://pkg.go.dev/golang.org/x/crypto/acme/autocert),
If you want to validate that certificate renewal completed successfully, this can be done either manually, or through external monitoring software. Two examples of doing this manually:
1. Open the URL for your headscale server in your browser of choice, and manually inspecting the expiry date of the certificate you receive.
2. Or, check remotely from CLI using `openssl`:
1. Or, check remotely from CLI using `openssl`:
```console
$ openssl s_client -servername [hostname] -connect [hostname]:443 | openssl x509 -noout -dates