state: stop tag approval sorting the reported RequestTags

nodeToRegister.Tags aliased Hostinfo.RequestTags, so sort/compact rewrote
the stored Hostinfo ([b a a] became [a b ""]).
This commit is contained in:
Kristoffer Dalby
2026-09-30 15:23:34 +00:00
committed by Kristoffer Dalby
parent a99c8a030c
commit 9c34c0c90d
2 changed files with 38 additions and 3 deletions
+36
View File
@@ -270,6 +270,42 @@ func TestRegistrationRejectsNodeKeyClaimedByAnotherMachine(t *testing.T) {
"registering a NodeKey already bound to another machine must be rejected")
}
// TestRegistrationKeepsRequestTagsIntact guards the node's reported
// Hostinfo.RequestTags against the in-place sort/compact that derives the
// approved tag set: the two must not share a backing array.
func TestRegistrationKeepsRequestTagsIntact(t *testing.T) {
dbPath := t.TempDir() + "/headscale.db"
cfg := persistTestConfig(dbPath)
database, err := db.NewHeadscaleDatabase(cfg)
require.NoError(t, err)
user := database.CreateUserForTest("tagger")
require.NoError(t, database.Close())
s, err := NewState(cfg)
require.NoError(t, err)
t.Cleanup(func() { _ = s.Close() })
_, err = s.SetPolicy([]byte(`{"tagOwners":{"tag:a":["tagger@"],"tag:b":["tagger@"]}}`))
require.NoError(t, err)
node, err := s.createAndSaveNewNode(newNodeParams{
User: *user,
MachineKey: key.NewMachine().Public(),
NodeKey: key.NewNode().Public(),
DiscoKey: key.NewDisco().Public(),
Hostname: "node",
Hostinfo: &tailcfg.Hostinfo{RequestTags: []string{"tag:b", "tag:a", "tag:a"}},
RegisterMethod: util.RegisterMethodCLI,
})
require.NoError(t, err)
assert.Equal(t, []string{"tag:a", "tag:b"}, node.Tags().AsSlice())
assert.Equal(t, []string{"tag:b", "tag:a", "tag:a"}, node.Hostinfo().RequestTags().AsSlice(),
"reported RequestTags rewritten by tag approval")
}
// TestReauthRejectsNodeKeyClaimedByAnotherMachine proves the re-auth/update
// path enforces the same 1:1 NodeKey<->MachineKey binding as the create path
// (TestRegistrationRejectsNodeKeyClaimedByAnotherMachine) and the poll path
+2 -3
View File
@@ -2114,9 +2114,8 @@ func (s *State) createAndSaveNewNode(params newNodeParams) (types.NodeView, erro
// All tags are approved - apply them
approvedTags := params.Hostinfo.RequestTags
if len(approvedTags) > 0 {
nodeToRegister.Tags = approvedTags
slices.Sort(nodeToRegister.Tags)
nodeToRegister.Tags = slices.Compact(nodeToRegister.Tags)
// Sort a copy: approvedTags is the node's reported Hostinfo.
nodeToRegister.Tags = slices.Compact(slices.Sorted(slices.Values(approvedTags)))
// Node is now tagged, so clear user ownership.
// Tagged nodes are owned by their tags, not a user.