Security fix: OIDC changes in Headscale 0.24.0
The following issue only affects Headscale installations which authenticate
with OIDC.
Headscale v0.23.0 and earlier identified OIDC users by the "username" part of
their email address (when strip_email_domain: true
, the default) or whole
email address (when strip_email_domain: false
).
Depending on how Headscale and your Identity Provider (IdP) were configured,
only using the email
claim could allow a malicious user with an IdP account to
take over another Headscale user's account, even when
strip_email_domain: false
.
This would also cause a user to lose access to their Headscale account if they
changed their email address.
Headscale v0.24.0 now identifies OIDC users by the iss
and sub
claims.
These are guaranteed by the OIDC specification to be stable and unique,
even if a user changes email address. A well-designed IdP will typically set
sub
to an opaque identifier like a UUID or numeric ID, which has no relation
to the user's name or email address.
Headscale v0.24.0 and later will also automatically update profile fields with
OIDC data on login. This means that users can change those details in your IdP,
and have it populate to Headscale automatically the next time they log in.
However, this may affect the way you reference users in policies.
Headscale v0.23.0 and earlier never recorded the iss
and sub
fields, so all
legacy (existing) OIDC accounts need to be migrated to be properly
secured.
What do I need to do to migrate?
Headscale v0.24.0 has an automatic migration feature, which is enabled by
default (map_legacy_users: true
). This will be disabled by default in a
future version of Headscale – any unmigrated users will get new accounts.
The migration will mostly be done automatically, with one exception. If your
OIDC does not provide an email_verified
claim, Headscale will ignore the
email
. This means that either the administrator will have to mark the user
emails as verified, or ensure the users verify their emails. Any unverified
emails will be ignored, meaning that the users will get new accounts instead
of being migrated.
After this exception is ensured, make all users log into Headscale with their
account, and Headscale will automatically update the account record. This will
be transparent to the users.
When all users have logged in, you can disable the automatic migration by
setting map_legacy_users: false
in your configuration file.
Please note that When automatic migration is enabled ( On migration, Headscale will change the account's username to their Like with Headscale v0.23.0 and earlier, this migration only works for users who A successful automated migration should otherwise be transparent to users.
Once a Headscale account has been migrated, it will be unavailable to be Because of the way OIDC works, Headscale's automated migration process can Legacy account migration should have no effect on new installations where all When automatic migration is disabled ( If there is no match, it will get a new Headscale account – even if there was We recommend new Headscale users explicitly disable automatic migration – but it When automatic migration is disabled, the map_legacy_users
will be set to false
by default in v0.25.0
and the migration mechanism will be removed in v0.26.0.
What does automatic migration do?
What does automatic migration do?
map_legacy_users: true
), Headscale will
first match an OIDC account to a Headscale account by iss
and sub
, and then
fall back to matching OIDC users similarly to how Headscale v0.23.0 did:
strip_email_domain: true
(the default): the Headscale username matches
the "username" part of their email address.
strip_email_domain: false
: the Headscale username matches the whole
email address.
preferred_username
. This could break any ACLs or policies which are
configured to match by username.
haven't changed their email address since their last Headscale login.
matched by the legacy process. An OIDC login with a matching username, but
non-matching iss
and sub
will instead get a new Headscale account.
only work when a user tries to log in after the update.
users have a recorded sub
and iss
.
What happens when automatic migration is disabled?
What happens when automatic migration is disabled?
map_legacy_users: false
), Headscale will
only try to match an OIDC account to a Headscale account by iss
and sub
.
a legacy account which could have matched and migrated.
should otherwise have no effect if every account has a recorded iss
and sub
.
strip_email_domain
setting will have
no effect.
Special thanks to @micolous for reviewing, proposing and working with us on
these changes.
Other OIDC changes
Headscale now uses
the standard OIDC claims
to populate and update user information every time they log in:
Headscale profile field | OIDC claim | Notes / examples |
---|---|---|
email address | email
| Only used when "email_verified": true
|
display name | name
| eg: Sam Smith
|
username | preferred_username
| Varies depending on IdP and configuration, eg: ssmith , ssmith@idp.example.com , \\example.com\ssmith
|
profile picture | picture
| URL to a profile picture or avatar |
These should show up nicely in the Tailscale client.
This will also affect the way you
reference users in policies.
BREAKING
- Remove
dns.use_username_in_magic_dns
configuration option
#2020,
#2279- Having usernames in magic DNS is no longer possible.
- Remove versions older than 1.56
#2149- Clean up old code required by old versions
- User gRPC/API #2261:
- If you depend on a Headscale Web UI, you should wait with this update until
the UI have been updated to match the new API. GET /api/v1/user/{name}
andGetUser
have been removed in favour ofListUsers
with an ID parameterRenameUser
andDeleteUser
now require an ID instead of a name.
- If you depend on a Headscale Web UI, you should wait with this update until
Changes
- Improved compatibilty of built-in DERP server with clients connecting over
WebSocket #2132 - Allow nodes to use SSH agent forwarding
#2145 - Fixed processing of fields in post request in MoveNode rpc
#2179 - Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules
applied #2198 - Fixed updating of hostname and givenName when it is updated in HostInfo
#2199 - Fixed missing
stable-debug
container tag
#2232 - Loosened up
server_url
andbase_domain
check. It was overly strict in some
cases. #2248 - CLI for managing users now accepts
--identifier
in addition to--name
,
usage of--identifier
is recommended
#2261 - Add
dns.extra_records_path
configuration option #2262
Changelog
- b6dc6eb #2140 Fixed reflection of hostname change (#2199)
- cc42fc3 #2177 Added conversion of 'Hostname' to 'givenName' in a node with FQDN rules applied (#2198)
- b3cda08 #2178 Fixed processing of fields in post request in MoveNode rpc (#2179)
- e367454 Add -it to docker exec (#2148)
- 75e7411 Add FAQ entry on which database to use
- 2345c38 Add a page for third-party tools (#2217)
- 1e61084 Add compatibility with only websocket-capable clients (#2132)
- 0602304 Add headplane
- 204a102 Add ouroboros to web ui list (#2154)
- 4445649 Add versioned documentation
- 380fcdb Add worker reading extra_records_path from file (#2271)
- 07b596d Allow nodes to use SSH agent forwarding (#2145)
- 7512e23 Bump deprecated github actions
- 24e7851 Changed all the html into go using go-elem (#2161)
- dc17b4d Documentation dependencies (#2252)
- 0089cea Feature tvos documentation (#2226)
- 0d3cf74 Fix README links to point to the stable version
- d2a86b1 Fix broken indent
- 52a3b54 Fixed loginUrl with "WithTLS()" used. Added "WithTLS()" to scenario integration tests (#2187)
- 9a46c57 Handle /derp/latency-check (#2227)
- 7821469 Harden OIDC migration and make optional
- 2c974dd MagicDNS no longer requires nameservers (#1681)
- 29119bb Misc doc fixes (#2240)
- 218138a Redo OIDC configuration (#2020)
- e724585 Refresh remote CLI documentation (#2216)
- 89a648c Remove use_username_in_magic_dns option
- fffd236 Resolve user to stable unique ID in policy (#2205)
- 8c7d8ee Restructure headscale documentation (#2163)
- 101ca7f Update flake.lock (#2173)
- 0c98d09 Update flake.lock (#2195)
- 63035cd Update headscale user creation settings in .deb (#2134)
- 6275399 Update tls.md to mention using the full cert chain (#2243)
- a7874af Use discord server invite link (#2235)
- 5fbf3f8 Websocket derp test fixes (#2247)
- dc07779 add @ to end of username if not present
- 35b669f add iss to identifier, only set email if verified
- a71a933 add nblock to doc owners (#2207)
- 028d9aa add new user fields to grpc and list command (#2202)
- 697d80d chore: configure some actions to be skipped for forks (#2005)
- e2d5ee0 cleanup linter warnings (#2206)
- c6336ad config: loosen up BaseDomain and ServerURL checks (#2248)
- 5eda9c8 denormalise PreAuthKey tags (#2155)
- 45c9585 feat: derpmap field in config (#1823)
- edf9e25 feat: support client verify for derp (add integration tests) (#2046)
- 7d9b430 fix constraints
- 281025b fix constraints
- 58d089c fix deletion of exit routes without nodes (#2286)
- 08bd4b9 fix docker network caps (#2273)
- 3780c9f fix nil in test
- 4dd12a2 fix oidc test, add tests for migration
- f6276ab fix postgres constraints, add postgres testing
- 4e44d57 fix: missing stable-debug tag (#2232)
- f3fca83 flake.lock: Update (#2143)
- 49ce573 flake.lock: Update (#2158)
- 8cfaa6b flake.lock: Update (#2222)
- 93ba21e flake.lock: Update (#2239)
- 2c1ad6d flake.lock: Update (#2254)
- 26d91ae flake.lock: Update (#2266)
- 64bb563 make configurable wal auto checkpoint (#2242)
- 4b58dc6 make preauthkey tags test stable
- 9515040 make reauth test compat with tailscale head (#2167)
- a6b19e8 more linter fixups (#2212)
- 5e7c315 nits
- d72663a remove log print
- 4f2fb65 remove versions older than 1.56 (#2149)
- 3a2589f rename dockerfile to integration to avoid confusion (#2225)
- 2fe6562 restore strip_email_domain for migration
- 64fd1f9 restructure command/api to use stable IDs (#2261)
- 757defa run cross compile of headscale as part of build (#2270)
- e16ea2e set hostinfo,ipv* columns explicitly (#2165)
- 0a82d3f update changelog
- 76d26a7 update oidc part of changelog for 0.24.0 (#2285)
- bc9e83b use gorm serialiser instead of custom hooks (#2156)
- 3964dec use tsaddr library and cleanups (#2150)
- 7ba0c3d use userID instead of username everywhere
- f7b0cbb wrap policy in policy manager interface (#2255)