-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Fix /machine/map endpoint vulnerability
#2642
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pull Request Revisions
✅ AI review completed for r5 HelpReact with emojis to give feedback on AI-generated reviews:
We'd love to hear from you—reach out anytime at team@review.ai. |
hscontrol/noise.go
Outdated
| if ns.nodeKey != mapRequest.NodeKey { | ||
| httpError(writer, NewHTTPError(http.StatusNotFound, "node does not belong to machine key", nil)) | ||
| return | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hscontrol/noise.go, the code now checks that the nodeKey from the map request matches the existing nodeKey before processing. This is a security improvement, but have you considered adding test cases to verify this validation works correctly?
hscontrol/noise.go
Outdated
| if ns.nodeKey != mapRequest.NodeKey { | ||
| httpError(writer, NewHTTPError(http.StatusNotFound, "node does not belong to machine key", nil)) | ||
| return | ||
| } | ||
|
|
||
| ns.nodeKey = node.NodeKey |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hscontrol/noise.go, line 228 assigns ns.nodeKey = node.NodeKey after the validation check. However, if ns.nodeKey hasn't been properly initialized before this handler is called, the validation check on line 223 might fail incorrectly. Consider verifying if ns.nodeKey is always properly set before reaching this handler.
hscontrol/noise.go
Outdated
| } | ||
|
|
||
| if ns.nodeKey != mapRequest.NodeKey { | ||
| httpError(writer, NewHTTPError(http.StatusNotFound, "node does not belong to machine key", nil)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hscontrol/noise.go, the error message 'node does not belong to machine key' on line 224 might be confusing. Consider changing it to something more precise like 'node key in request doesn't match the one associated with this machine key' to clarify the exact validation that's failing.
hscontrol/noise.go
Outdated
| if node.NodeKey != mapRequest.NodeKey { | ||
| httpError(writer, NewHTTPError(http.StatusNotFound, "node does not belong to machine key", nil)) | ||
| return | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hscontrol/noise.go, consider adding a comment explaining the security implications of this change. The new approach correctly verifies that the node key in the map request matches the one associated with the machine key before proceeding, which prevents potential spoofing attacks.
kradalby
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!, thank you. I think the review agent has two good points, can you address those?
| return ®Req, resp | ||
| } else { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hscontrol/noise.go, there's an empty else block at lines 275-277. Consider removing the empty block for cleaner code.
Addressed 👍 |
|
Thank you, i'll merge after the tests |
|
Also I just realized that MapRequest is used to submit updates for the node as well, so just by knowing the node's public key, an attacker might be able to modify the network configuration. I'm not fully familiar with the broader codebase, so I'll leave it to you to assess the severity of this. |
|
I'll take a look tomorrow, also the failing tests |
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>
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This should not be a problem as it happens in the same endpoint as we just added the check. So any update mechanism will not start a stream nor post. I have just pushed two changes to address some test failures and comments for future. Please take a look @Thifhi, thanks. |
|
Looks good , thank you |
|
Though still failing 😕 |
I think it is only github actions having a bad time, im gonna pull it down and run the ones locally. |
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
* 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>
* 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>
Current Behavior
POST /machine/maprequests are processed by extracting the Node from the publicNodeKeyprovided in the request, without any additional verification. This allows an attacker who possesses the public key of any Node in the network to:MapRequestcontaining aNodeKeythat does not belong to them, andMapResponses.As a result, the attacker can learn sensitive details about the entire Tailnet.
Problem
The current approach implicitly trusts the
NodeKeyin the request, even though the client has not proven ownership of its corresponding private key.Proposed Change
Instead, we should retrieve the Node based on the machine key used in the Noise connection, as the client must possess the corresponding private key to establish the session. This provides proper authentication and matches the behavior of Tailscale servers. This is what Tailscale control plane returns in this case:
