v0.26.0
juanfont/headscalev0.26.0May 14, 2025by github-actions[bot]
AI Summary
A major release introducing Policy v2 (complete rewrite), route internals rewrite, and OIDC improvements, including fetching UserInfo for email verification.
Key Highlights
- Policy v2: Complete rewrite with validation at load time and better error messages; all users must contain '@'.
- Routes: Internals rewritten; routes now managed via Node API instead of dedicated route API.
- OIDC: Fetches UserInfo to get EmailVerified; populates name/email from UserInfo.
Breaking Changes
- Routes: Route API and CLI removed; managed via Node API.
- Policy v1 code removed.
- Disallow 'server_url' and 'base_domain' to be equal.
- Pre auth key API/CLI now uses ID over username.
- 'oidc.map_legacy_users' and 'oidc.strip_email_domain' removed.
New Features
- 'headscale policy check' command added.
- More information added to '/debug' endpoint (goroutines, profiles).
- Restore support for 'Override local DNS'.
Full Release Notes
## BREAKING
### Routes
Route internals have been rewritten, removing the dedicated route table in the
database. This was done to simplify the codebase, which had grown unnecessarily
complex after the routes were split into separate tables. The overhead of having
to go via the database and keeping the state in sync made the code very hard to
reason about and prone to errors. The majority of the route state is only
relevant when headscale is running, and is now only kept in memory. As part of
this, the CLI and API has been simplified to reflect the changes;
```console
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | ts-head-ruqsg8 | | 0.0.0.0/0, ::/0 |
2 | ts-unstable-fq7ob4 | | 0.0.0.0/0, ::/0 |
$ headscale nodes approve-routes --identifier 1 --routes 0.0.0.0/0,::/0
Node updated
$ headscale nodes list-routes
ID | Hostname | Approved | Available | Serving (Primary)
1 | ts-head-ruqsg8 | 0.0.0.0/0, ::/0 | 0.0.0.0/0, ::/0 | 0.0.0.0/0, ::/0
2 | ts-unstable-fq7ob4 | | 0.0.0.0/0, ::/0 |
```
Note that if an exit route is approved (0.0.0.0/0 or ::/0), both IPv4 and IPv6
will be approved.
- Route API and CLI has been removed
[#2422](https://github.com/juanfont/headscale/pull/2422)
- Routes are now managed via the Node API
[#2422](https://github.com/juanfont/headscale/pull/2422)
- Only routes accessible to the node will be sent to the node
[#2561](https://github.com/juanfont/headscale/pull/2561)
### Policy v2
This release introduces a new policy implementation. The new policy is a
complete rewrite, and it introduces some significant quality and consistency
improvements. In principle, there are not really any new features, but some long
standing bugs should have been resolved, or be easier to fix in the future. The
new policy code passes all of our tests.
**Changes**
- The policy is validated and "resolved" when loading, providing errors for
invalid rules and conditions.
- Previously this was done as a mix between load and runtime (when it was
applied to a node).
- This means that when you convert the first time, what was previously a
policy that loaded, but failed at runtime, will now fail at load time.
- Error messages should be more descriptive and informative.
- There is still work to be here, but it is already improved with "typing"
(e.g. only Users can be put in Groups)
- All users must contain an `@` character.
- If your user naturally contains and `@`, like an email, this will just work.
- If its based on usernames, or other identifiers not containing an `@`, an
`@` should be appended at the end. For example, if your user is `john`, it
must be written as `john@` in the policy.
<details>
<summary>Migration notes when the policy is stored in the database.</summary>
This section **only** applies if the policy is stored in the database and
Headscale 0.26 doesn't start due to a policy error
(`failed to load ACL policy`).
- Start Headscale 0.26 with the environment variable `HEADSCALE_POLICY_V1=1`
set. You can check that Headscale picked up the environment variable by
observing this message during startup: `Using policy manager version: 1`
- Dump the policy to a file: `headscale policy get > policy.json`
- Edit `policy.json` and migrate to policy V2. Use the command
`headscale policy check --file policy.json` to check for policy errors.
- Load the modified policy: `headscale policy set --file policy.json`
- Restart Headscale **without** the environment variable `HEADSCALE_POLICY_V1`.
Headscale should now print the message `Using policy manager version: 2` and
startup successfully.
</details>
**SSH**
The SSH policy has been reworked to be more consistent with the rest of the
policy. In addition, several inconsistencies between our implementation and
Tailscale's upstream has been closed and this might be a breaking change for
some users. Please refer to the
[upstream documentation](https://tailscale.com/kb/1337/acl-syntax#tailscale-ssh)
for more information on which types are allowed in `src`, `dst` and `users`.
There is one large inconsistency left, we allow `*` as a destination as we
currently do not support `autogroup:self`, `autogroup:member` and
`autogroup:tagged`. The support for `*` will be removed when we have support for
the autogroups.
**Current state**
The new policy is passing all tests, both integration and unit tests. This does
not mean it is perfect, but it is a good start. Corner cases that is currently
working in v1 and not tested might be broken in v2 (and vice versa).
**We do need help testing this code**
### Other breaking changes
- Disallow `server_url` and `base_domain` to be equal
[#2544](https://github.com/juanfont/headscale/pull/2544)
- Return full user in API for pre auth keys instead of string
[#2542](https://github.com/juanfont/headscale/pull/2542)
- Pre auth key API/CLI now uses ID over username
[#2542](https://github.com/juanfont/headscale/pull/2542)
## Changes
- Use Go 1.24 [#2427](https://github.com/juanfont/headscale/pull/2427)
- Add `headscale policy check` command to check policy
[#2553](https://github.com/juanfont/headscale/pull/2553)
- `oidc.map_legacy_users` and `oidc.strip_email_domain` has been removed
[#2411](https://github.com/juanfont/headscale/pull/2411)
- Add more information to `/debug` endpoint
[#2420](https://github.com/juanfont/headscale/pull/2420)
- It is now possible to inspect running goroutines and take profiles
- View of config, policy, filter, ssh policy per node, connected nodes and
DERPmap
- OIDC: Fetch UserInfo to get EmailVerified if necessary
[#2493](https://github.com/juanfont/headscale/pull/2493)
- If a OIDC provider doesn't include the `email_verified` claim in its ID
tokens, Headscale will attempt to get it from the UserInfo endpoint.
- OIDC: Try to populate name, email and username from UserInfo
[#2545](https://github.com/juanfont/headscale/pull/2545)
- Improve performance by only querying relevant nodes from the database for node
updates [#2509](https://github.com/juanfont/headscale/pull/2509)
- node FQDNs in the netmap will now contain a dot (".") at the end. This aligns
with behaviour of tailscale.com
[#2503](https://github.com/juanfont/headscale/pull/2503)
- Restore support for "Override local DNS"
[#2438](https://github.com/juanfont/headscale/pull/2438)
- Add documentation for routes
[#2496](https://github.com/juanfont/headscale/pull/2496)
## Changelog
* 8c7e65061649c62a5323ad13d6067daa4e34734b Remove map_legacy_users from example configuration (#2590)
* d7a503a34effa188e9bb27cb6b0fad2002112fb0 changelog: entry for 0.26 (#2594)
* 62b489dc686c090a025329673c67e28f441d14e1 fix: change FormatUint base from 64 to 10 in preauthkeys list command (#2588)
* 2dc2f3b3f0e0efcb5257379dec7ad1b4b09f8945 users: harden, test, and add cleaner of identifier (#2593)