Compare commits

...

3 Commits

Author SHA1 Message Date
Florian Preinstorfer
00a5cce7fd The preauthkeys commands expect a user id instead of a username
(cherry picked from commit a98d9bd05f)
2025-07-16 09:53:58 +02:00
Florian Preinstorfer
4d89030701 Set doc version to 0.26.1 2025-06-07 11:08:54 +02:00
Mustafa Enes Batur
474ea236d0 Fix /machine/map endpoint vulnerability (#2642)
* Improve map auth logic

* Bugfix

* Add comment, improve error message

* noise: make func, get by node

this commit splits the additional validation into a
separate function so it can be reused if we add more
endpoints in the future.

It swaps the check, so we still look up by NodeKey, but before
accepting the connection, we validate the known machinekey from
the db against the noise connection.

The reason for this is that when a node logs in or out, the node key
is replaced and it will no longer be possible to look it up, breaking
reauthentication.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Co-authored-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-06-06 12:16:37 +02:00
5 changed files with 39 additions and 14 deletions

View File

@@ -1,6 +1,12 @@
# CHANGELOG
## Next
## 0.26.1 (2025-06-06)
### Changes
- Ensure nodes are matching both node key and machine key
when connecting.
[#2642](https://github.com/juanfont/headscale/pull/2642)
## 0.26.0 (2025-05-14)

View File

@@ -112,11 +112,11 @@ docker exec -it headscale \
### Register a machine using a pre authenticated key
Generate a key using the command line:
Generate a key using the command line for the user with ID 1:
```shell
docker exec -it headscale \
headscale preauthkeys create --user myfirstuser --reusable --expiration 24h
headscale preauthkeys create --user 1 --reusable --expiration 24h
```
This will return a pre-authenticated key that can be used to connect a node to headscale with the `tailscale up` command:

View File

@@ -117,14 +117,14 @@ headscale instance. By default, the key is valid for one hour and can only be us
=== "Native"
```shell
headscale preauthkeys create --user <USER>
headscale preauthkeys create --user <USER_ID>
```
=== "Container"
```shell
docker exec -it headscale \
headscale preauthkeys create --user <USER>
headscale preauthkeys create --user <USER_ID>
```
The command returns the preauthkey on success which is used to connect a node to the headscale instance via the

View File

@@ -100,6 +100,10 @@ func (h *Headscale) NoiseUpgradeHandler(
router.HandleFunc("/machine/register", noiseServer.NoiseRegistrationHandler).
Methods(http.MethodPost)
// Endpoints outside of the register endpoint must use getAndValidateNode to
// get the node to ensure that the MachineKey matches the Node setting up the
// connection.
router.HandleFunc("/machine/map", noiseServer.NoisePollNetMapHandler)
noiseServer.httpBaseConfig = &http.Server{
@@ -209,18 +213,14 @@ func (ns *noiseServer) NoisePollNetMapHandler(
return
}
ns.nodeKey = mapRequest.NodeKey
node, err := ns.headscale.db.GetNodeByNodeKey(mapRequest.NodeKey)
node, err := ns.getAndValidateNode(mapRequest)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
httpError(writer, NewHTTPError(http.StatusNotFound, "node not found", nil))
return
}
httpError(writer, err)
return
}
ns.nodeKey = node.NodeKey
sess := ns.headscale.newMapSession(req.Context(), mapRequest, writer, node)
sess.tracef("a node sending a MapRequest with Noise protocol")
if !sess.isStreaming() {
@@ -266,8 +266,8 @@ func (ns *noiseServer) NoiseRegistrationHandler(
Error: httpErr.Msg,
}
return &regReq, resp
} else {
}
return &regReq, regErr(err)
}
@@ -289,3 +289,22 @@ func (ns *noiseServer) NoiseRegistrationHandler(
writer.WriteHeader(http.StatusOK)
writer.Write(respBody)
}
// getAndValidateNode retrieves the node from the database using the NodeKey
// and validates that it matches the MachineKey from the Noise session.
func (ns *noiseServer) getAndValidateNode(mapRequest tailcfg.MapRequest) (*types.Node, error) {
node, err := ns.headscale.db.GetNodeByNodeKey(mapRequest.NodeKey)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, NewHTTPError(http.StatusNotFound, "node not found", nil)
}
return nil, err
}
// Validate that the MachineKey in the Noise session matches the one associated with the NodeKey.
if ns.machineKey != node.MachineKey {
return nil, NewHTTPError(http.StatusNotFound, "node key in request does not match the one associated with this machine key", nil)
}
return node, nil
}

View File

@@ -107,7 +107,7 @@ extra:
- icon: fontawesome/brands/discord
link: https://discord.gg/c84AZQhmpx
headscale:
version: 0.26.0
version: 0.26.1
# Extensions
markdown_extensions: