Skip to content

Commit 064c46f

Browse files
authored
move logic for validating node names (#2127)
* move logic for validating node names this commits moves the generation of "given names" of nodes into the registration function, and adds validation of renames to RenameNode using the same logic. Fixes #2121 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> * fix double arg Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com> --------- Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
1 parent 64319f7 commit 064c46f

4 files changed

Lines changed: 134 additions & 115 deletions

File tree

hscontrol/auth.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (h *Headscale) handleRegister(
6666
regReq tailcfg.RegisterRequest,
6767
machineKey key.MachinePublic,
6868
) {
69-
logInfo, logTrace, logErr := logAuthFunc(regReq, machineKey)
69+
logInfo, logTrace, _ := logAuthFunc(regReq, machineKey)
7070
now := time.Now().UTC()
7171
logTrace("handleRegister called, looking up machine in DB")
7272
node, err := h.db.GetNodeByAnyKey(machineKey, regReq.NodeKey, regReq.OldNodeKey)
@@ -105,24 +105,13 @@ func (h *Headscale) handleRegister(
105105

106106
logInfo("Node not found in database, creating new")
107107

108-
givenName, err := h.db.GenerateGivenName(
109-
machineKey,
110-
regReq.Hostinfo.Hostname,
111-
)
112-
if err != nil {
113-
logErr(err, "Failed to generate given name for node")
114-
115-
return
116-
}
117-
118108
// The node did not have a key to authenticate, which means
119109
// that we rely on a method that calls back some how (OpenID or CLI)
120110
// We create the node and then keep it around until a callback
121111
// happens
122112
newNode := types.Node{
123113
MachineKey: machineKey,
124114
Hostname: regReq.Hostinfo.Hostname,
125-
GivenName: givenName,
126115
NodeKey: regReq.NodeKey,
127116
LastSeen: &now,
128117
Expiry: &time.Time{},
@@ -354,21 +343,8 @@ func (h *Headscale) handleAuthKey(
354343
} else {
355344
now := time.Now().UTC()
356345

357-
givenName, err := h.db.GenerateGivenName(machineKey, registerRequest.Hostinfo.Hostname)
358-
if err != nil {
359-
log.Error().
360-
Caller().
361-
Str("func", "RegistrationHandler").
362-
Str("hostinfo.name", registerRequest.Hostinfo.Hostname).
363-
Err(err).
364-
Msg("Failed to generate given name for node")
365-
366-
return
367-
}
368-
369346
nodeToRegister := types.Node{
370347
Hostname: registerRequest.Hostinfo.Hostname,
371-
GivenName: givenName,
372348
UserID: pak.User.ID,
373349
User: pak.User,
374350
MachineKey: machineKey,

hscontrol/db/node.go

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -90,20 +90,6 @@ func (hsdb *HSDatabase) ListEphemeralNodes() (types.Nodes, error) {
9090
})
9191
}
9292

93-
func listNodesByGivenName(tx *gorm.DB, givenName string) (types.Nodes, error) {
94-
nodes := types.Nodes{}
95-
if err := tx.
96-
Preload("AuthKey").
97-
Preload("AuthKey.User").
98-
Preload("User").
99-
Preload("Routes").
100-
Where("given_name = ?", givenName).Find(&nodes).Error; err != nil {
101-
return nil, err
102-
}
103-
104-
return nodes, nil
105-
}
106-
10793
func (hsdb *HSDatabase) getNode(user string, name string) (*types.Node, error) {
10894
return Read(hsdb.DB, func(rx *gorm.DB) (*types.Node, error) {
10995
return getNode(rx, user, name)
@@ -242,9 +228,9 @@ func SetTags(
242228
}
243229

244230
// RenameNode takes a Node struct and a new GivenName for the nodes
245-
// and renames it.
231+
// and renames it. If the name is not unique, it will return an error.
246232
func RenameNode(tx *gorm.DB,
247-
nodeID uint64, newName string,
233+
nodeID types.NodeID, newName string,
248234
) error {
249235
err := util.CheckForFQDNRules(
250236
newName,
@@ -253,6 +239,15 @@ func RenameNode(tx *gorm.DB,
253239
return fmt.Errorf("renaming node: %w", err)
254240
}
255241

242+
uniq, err := isUnqiueName(tx, newName)
243+
if err != nil {
244+
return fmt.Errorf("checking if name is unique: %w", err)
245+
}
246+
247+
if !uniq {
248+
return fmt.Errorf("name is not unique: %s", newName)
249+
}
250+
256251
if err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("given_name", newName).Error; err != nil {
257252
return fmt.Errorf("failed to rename node in the database: %w", err)
258253
}
@@ -415,6 +410,15 @@ func RegisterNode(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Ad
415410
node.IPv4 = ipv4
416411
node.IPv6 = ipv6
417412

413+
if node.GivenName == "" {
414+
givenName, err := ensureUniqueGivenName(tx, node.Hostname)
415+
if err != nil {
416+
return nil, fmt.Errorf("failed to ensure unique given name: %w", err)
417+
}
418+
419+
node.GivenName = givenName
420+
}
421+
418422
if err := tx.Save(&node).Error; err != nil {
419423
return nil, fmt.Errorf("failed register(save) node in the database: %w", err)
420424
}
@@ -642,40 +646,32 @@ func generateGivenName(suppliedName string, randomSuffix bool) (string, error) {
642646
return normalizedHostname, nil
643647
}
644648

645-
func (hsdb *HSDatabase) GenerateGivenName(
646-
mkey key.MachinePublic,
647-
suppliedName string,
648-
) (string, error) {
649-
return Read(hsdb.DB, func(rx *gorm.DB) (string, error) {
650-
return GenerateGivenName(rx, mkey, suppliedName)
651-
})
649+
func isUnqiueName(tx *gorm.DB, name string) (bool, error) {
650+
nodes := types.Nodes{}
651+
if err := tx.
652+
Where("given_name = ?", name).Find(&nodes).Error; err != nil {
653+
return false, err
654+
}
655+
656+
return len(nodes) == 0, nil
652657
}
653658

654-
func GenerateGivenName(
659+
func ensureUniqueGivenName(
655660
tx *gorm.DB,
656-
mkey key.MachinePublic,
657-
suppliedName string,
661+
name string,
658662
) (string, error) {
659-
givenName, err := generateGivenName(suppliedName, false)
663+
givenName, err := generateGivenName(name, false)
660664
if err != nil {
661665
return "", err
662666
}
663667

664-
// Tailscale rules (may differ) https://tailscale.com/kb/1098/machine-names/
665-
nodes, err := listNodesByGivenName(tx, givenName)
668+
unique, err := isUnqiueName(tx, givenName)
666669
if err != nil {
667670
return "", err
668671
}
669672

670-
var nodeFound *types.Node
671-
for idx, node := range nodes {
672-
if node.GivenName == givenName {
673-
nodeFound = nodes[idx]
674-
}
675-
}
676-
677-
if nodeFound != nil && nodeFound.MachineKey.String() != mkey.String() {
678-
postfixedName, err := generateGivenName(suppliedName, true)
673+
if !unique {
674+
postfixedName, err := generateGivenName(name, true)
679675
if err != nil {
680676
return "", err
681677
}

hscontrol/db/node_test.go

Lines changed: 98 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/puzpuzpuz/xsync/v3"
2020
"github.com/stretchr/testify/assert"
2121
"gopkg.in/check.v1"
22+
"gorm.io/gorm"
2223
"tailscale.com/tailcfg"
2324
"tailscale.com/types/key"
2425
"tailscale.com/types/ptr"
@@ -313,51 +314,6 @@ func (s *Suite) TestExpireNode(c *check.C) {
313314
c.Assert(nodeFromDB.IsExpired(), check.Equals, true)
314315
}
315316

316-
func (s *Suite) TestGenerateGivenName(c *check.C) {
317-
user1, err := db.CreateUser("user-1")
318-
c.Assert(err, check.IsNil)
319-
320-
pak, err := db.CreatePreAuthKey(user1.Name, false, false, nil, nil)
321-
c.Assert(err, check.IsNil)
322-
323-
_, err = db.getNode("user-1", "testnode")
324-
c.Assert(err, check.NotNil)
325-
326-
nodeKey := key.NewNode()
327-
machineKey := key.NewMachine()
328-
329-
machineKey2 := key.NewMachine()
330-
331-
node := &types.Node{
332-
ID: 0,
333-
MachineKey: machineKey.Public(),
334-
NodeKey: nodeKey.Public(),
335-
Hostname: "hostname-1",
336-
GivenName: "hostname-1",
337-
UserID: user1.ID,
338-
RegisterMethod: util.RegisterMethodAuthKey,
339-
AuthKeyID: ptr.To(pak.ID),
340-
}
341-
342-
trx := db.DB.Save(node)
343-
c.Assert(trx.Error, check.IsNil)
344-
345-
givenName, err := db.GenerateGivenName(machineKey2.Public(), "hostname-2")
346-
comment := check.Commentf("Same user, unique nodes, unique hostnames, no conflict")
347-
c.Assert(err, check.IsNil, comment)
348-
c.Assert(givenName, check.Equals, "hostname-2", comment)
349-
350-
givenName, err = db.GenerateGivenName(machineKey.Public(), "hostname-1")
351-
comment = check.Commentf("Same user, same node, same hostname, no conflict")
352-
c.Assert(err, check.IsNil, comment)
353-
c.Assert(givenName, check.Equals, "hostname-1", comment)
354-
355-
givenName, err = db.GenerateGivenName(machineKey2.Public(), "hostname-1")
356-
comment = check.Commentf("Same user, unique nodes, same hostname, conflict")
357-
c.Assert(err, check.IsNil, comment)
358-
c.Assert(givenName, check.Matches, fmt.Sprintf("^hostname-1-[a-z0-9]{%d}$", NodeGivenNameHashLength), comment)
359-
}
360-
361317
func (s *Suite) TestSetTags(c *check.C) {
362318
user, err := db.CreateUser("test")
363319
c.Assert(err, check.IsNil)
@@ -778,3 +734,100 @@ func TestListEphemeralNodes(t *testing.T) {
778734
assert.Equal(t, nodeEph.UserID, ephemeralNodes[0].UserID)
779735
assert.Equal(t, nodeEph.Hostname, ephemeralNodes[0].Hostname)
780736
}
737+
738+
func TestRenameNode(t *testing.T) {
739+
db, err := newTestDB()
740+
if err != nil {
741+
t.Fatalf("creating db: %s", err)
742+
}
743+
744+
user, err := db.CreateUser("test")
745+
assert.NoError(t, err)
746+
747+
user2, err := db.CreateUser("test2")
748+
assert.NoError(t, err)
749+
750+
node := types.Node{
751+
ID: 0,
752+
MachineKey: key.NewMachine().Public(),
753+
NodeKey: key.NewNode().Public(),
754+
Hostname: "test",
755+
UserID: user.ID,
756+
RegisterMethod: util.RegisterMethodAuthKey,
757+
}
758+
759+
node2 := types.Node{
760+
ID: 0,
761+
MachineKey: key.NewMachine().Public(),
762+
NodeKey: key.NewNode().Public(),
763+
Hostname: "test",
764+
UserID: user2.ID,
765+
RegisterMethod: util.RegisterMethodAuthKey,
766+
}
767+
768+
err = db.DB.Save(&node).Error
769+
assert.NoError(t, err)
770+
771+
err = db.DB.Save(&node2).Error
772+
assert.NoError(t, err)
773+
774+
err = db.DB.Transaction(func(tx *gorm.DB) error {
775+
_, err := RegisterNode(tx, node, nil, nil)
776+
if err != nil {
777+
return err
778+
}
779+
_, err = RegisterNode(tx, node2, nil, nil)
780+
return err
781+
})
782+
assert.NoError(t, err)
783+
784+
nodes, err := db.ListNodes()
785+
assert.NoError(t, err)
786+
787+
assert.Len(t, nodes, 2)
788+
789+
t.Logf("node1 %s %s", nodes[0].Hostname, nodes[0].GivenName)
790+
t.Logf("node2 %s %s", nodes[1].Hostname, nodes[1].GivenName)
791+
792+
assert.Equal(t, nodes[0].Hostname, nodes[0].GivenName)
793+
assert.NotEqual(t, nodes[1].Hostname, nodes[1].GivenName)
794+
assert.Equal(t, nodes[0].Hostname, nodes[1].Hostname)
795+
assert.NotEqual(t, nodes[0].Hostname, nodes[1].GivenName)
796+
assert.Contains(t, nodes[1].GivenName, nodes[0].Hostname)
797+
assert.Equal(t, nodes[0].GivenName, nodes[1].Hostname)
798+
assert.Len(t, nodes[0].Hostname, 4)
799+
assert.Len(t, nodes[1].Hostname, 4)
800+
assert.Len(t, nodes[0].GivenName, 4)
801+
assert.Len(t, nodes[1].GivenName, 13)
802+
803+
// Nodes can be renamed to a unique name
804+
err = db.Write(func(tx *gorm.DB) error {
805+
return RenameNode(tx, nodes[0].ID, "newname")
806+
})
807+
assert.NoError(t, err)
808+
809+
nodes, err = db.ListNodes()
810+
assert.NoError(t, err)
811+
assert.Len(t, nodes, 2)
812+
assert.Equal(t, nodes[0].Hostname, "test")
813+
assert.Equal(t, nodes[0].GivenName, "newname")
814+
815+
// Nodes can reuse name that is no longer used
816+
err = db.Write(func(tx *gorm.DB) error {
817+
return RenameNode(tx, nodes[1].ID, "test")
818+
})
819+
assert.NoError(t, err)
820+
821+
nodes, err = db.ListNodes()
822+
assert.NoError(t, err)
823+
assert.Len(t, nodes, 2)
824+
assert.Equal(t, nodes[0].Hostname, "test")
825+
assert.Equal(t, nodes[0].GivenName, "newname")
826+
assert.Equal(t, nodes[1].GivenName, "test")
827+
828+
// Nodes cannot be renamed to used names
829+
err = db.Write(func(tx *gorm.DB) error {
830+
return RenameNode(tx, nodes[0].ID, "test")
831+
})
832+
assert.ErrorContains(t, err, "name is not unique")
833+
}

hscontrol/grpcv1.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ func (api headscaleV1APIServer) RenameNode(
373373
node, err := db.Write(api.h.db.DB, func(tx *gorm.DB) (*types.Node, error) {
374374
err := db.RenameNode(
375375
tx,
376-
request.GetNodeId(),
376+
types.NodeID(request.GetNodeId()),
377377
request.GetNewName(),
378378
)
379379
if err != nil {
@@ -802,18 +802,12 @@ func (api headscaleV1APIServer) DebugCreateNode(
802802
return nil, err
803803
}
804804

805-
givenName, err := api.h.db.GenerateGivenName(mkey, request.GetName())
806-
if err != nil {
807-
return nil, err
808-
}
809-
810805
nodeKey := key.NewNode()
811806

812807
newNode := types.Node{
813808
MachineKey: mkey,
814809
NodeKey: nodeKey.Public(),
815810
Hostname: request.GetName(),
816-
GivenName: givenName,
817811
User: *user,
818812

819813
Expiry: &time.Time{},

0 commit comments

Comments
 (0)