mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-24 13:46:53 +02:00
derp
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
343cfd47e6
commit
2a906cd15e
@ -249,11 +249,10 @@ func (s *State) CreateUser(user types.User) (*types.User, change.ChangeSet, erro
|
||||
// Even if the policy manager doesn't detect a filter change, SSH policies
|
||||
// might now be resolvable when they weren't before. If there are existing
|
||||
// nodes, we should send a policy change to ensure they get updated SSH policies.
|
||||
// TODO(kradalby): detect this, or rebuild all SSH policies so we can determine
|
||||
// this upstream.
|
||||
if c.Empty() {
|
||||
nodes := s.ListNodes()
|
||||
if nodes.Len() > 0 {
|
||||
c = change.PolicyChange()
|
||||
}
|
||||
c = change.PolicyChange()
|
||||
}
|
||||
|
||||
log.Info().Str("user", user.Name).Bool("policyChanged", !c.Empty()).Msg("User created, policy manager updated")
|
||||
@ -444,7 +443,7 @@ func (s *State) Connect(id types.NodeID) change.ChangeSet {
|
||||
n.IsOnline = ptr.To(true)
|
||||
n.LastSeen = ptr.To(now)
|
||||
})
|
||||
|
||||
|
||||
// Also persist the last seen time to the database
|
||||
// Note: IsOnline is managed only in NodeStore (marked with gorm:"-"), not persisted to database
|
||||
_, err := s.updateNodeTx(id, func(tx *gorm.DB) error {
|
||||
@ -1064,32 +1063,33 @@ func (s *State) HandleNodeFromAuthPath(
|
||||
nodeToRegister := regEntry.Node
|
||||
nodeToRegister.RegisterMethod = registrationMethod
|
||||
|
||||
// Custom update function for existing nodes
|
||||
updateFunc := func(node *types.Node) {
|
||||
node.NodeKey = nodeToRegister.NodeKey
|
||||
node.DiscoKey = nodeToRegister.DiscoKey
|
||||
node.Hostname = nodeToRegister.Hostname
|
||||
|
||||
// Preserve NetInfo from existing node to maintain DERP connectivity during relogin
|
||||
// Registration requests typically don't include NetInfo with PreferredDERP,
|
||||
// but we need to preserve it to avoid nodes appearing as disconnected
|
||||
if nodeToRegister.Hostinfo != nil && node.Hostinfo != nil &&
|
||||
nodeToRegister.Hostinfo.NetInfo == nil && node.Hostinfo.NetInfo != nil {
|
||||
log.Debug().
|
||||
Uint64("node.id", node.ID.Uint64()).
|
||||
Int("preferredDERP", node.Hostinfo.NetInfo.PreferredDERP).
|
||||
Msg("preserving NetInfo during node re-registration")
|
||||
|
||||
// Create a copy of the new Hostinfo and preserve the existing NetInfo
|
||||
newHostinfo := *nodeToRegister.Hostinfo
|
||||
newHostinfo.NetInfo = node.Hostinfo.NetInfo
|
||||
node.Hostinfo = &newHostinfo
|
||||
} else {
|
||||
node.Hostinfo = nodeToRegister.Hostinfo
|
||||
// Handle IP allocation
|
||||
var ipv4, ipv6 *netip.Addr
|
||||
if existingMachineNode != nil && existingMachineNode.UserID == uint(userID) {
|
||||
// Reuse existing IPs and properties
|
||||
nodeToRegister.ID = existingMachineNode.ID
|
||||
nodeToRegister.GivenName = existingMachineNode.GivenName
|
||||
nodeToRegister.ApprovedRoutes = existingMachineNode.ApprovedRoutes
|
||||
ipv4 = existingMachineNode.IPv4
|
||||
ipv6 = existingMachineNode.IPv6
|
||||
} else {
|
||||
// Allocate new IPs
|
||||
ipv4, ipv6, err = s.ipAlloc.Next()
|
||||
if err != nil {
|
||||
return types.NodeView{}, change.EmptySet, fmt.Errorf("allocating IPs: %w", err)
|
||||
}
|
||||
|
||||
node.Endpoints = nodeToRegister.Endpoints
|
||||
node.RegisterMethod = nodeToRegister.RegisterMethod
|
||||
}
|
||||
|
||||
nodeToRegister.IPv4 = ipv4
|
||||
nodeToRegister.IPv6 = ipv6
|
||||
|
||||
// Ensure unique given name if not set
|
||||
if nodeToRegister.GivenName == "" {
|
||||
givenName, err := hsdb.EnsureUniqueGivenName(s.db.DB, nodeToRegister.Hostname)
|
||||
if err != nil {
|
||||
return types.NodeView{}, change.EmptySet, fmt.Errorf("failed to ensure unique given name: %w", err)
|
||||
}
|
||||
nodeToRegister.GivenName = givenName
|
||||
}
|
||||
|
||||
savedNode, err := s.registerOrUpdateNode(nodeRegistrationHelper{
|
||||
@ -1169,97 +1169,10 @@ func (s *State) HandleNodeFromPreAuthKey(
|
||||
Str("user", pak.User.Username()).
|
||||
Msg("Refreshing existing node registration with pre-auth key")
|
||||
|
||||
// Update NodeStore first with the new expiry and other fields
|
||||
s.nodeStore.UpdateNode(existingNodeView.ID(), func(node *types.Node) {
|
||||
if !regReq.Expiry.IsZero() {
|
||||
expiry := regReq.Expiry
|
||||
node.Expiry = &expiry
|
||||
}
|
||||
// Update machine key if it changed
|
||||
node.MachineKey = machineKey
|
||||
|
||||
// Preserve NetInfo from existing node to maintain DERP connectivity during relogin
|
||||
// Registration requests typically don't include NetInfo with PreferredDERP,
|
||||
// but we need to preserve it to avoid nodes appearing as disconnected
|
||||
if regReq.Hostinfo != nil && node.Hostinfo != nil &&
|
||||
regReq.Hostinfo.NetInfo == nil && node.Hostinfo.NetInfo != nil {
|
||||
log.Debug().
|
||||
Uint64("node.id", node.ID.Uint64()).
|
||||
Int("preferredDERP", node.Hostinfo.NetInfo.PreferredDERP).
|
||||
Msg("preserving NetInfo during pre-auth key re-registration")
|
||||
|
||||
// Create a copy of the new Hostinfo and preserve the existing NetInfo
|
||||
newHostinfo := *regReq.Hostinfo
|
||||
newHostinfo.NetInfo = node.Hostinfo.NetInfo
|
||||
node.Hostinfo = &newHostinfo
|
||||
} else {
|
||||
node.Hostinfo = regReq.Hostinfo
|
||||
}
|
||||
|
||||
// Node is re-registering, so it's coming online
|
||||
node.IsOnline = ptr.To(true)
|
||||
node.LastSeen = ptr.To(time.Now())
|
||||
// Update auth key association
|
||||
node.AuthKey = pak
|
||||
node.AuthKeyID = &pak.ID
|
||||
// Update forced tags from the pre-auth key
|
||||
node.ForcedTags = pak.Proto().GetAclTags()
|
||||
})
|
||||
|
||||
// Save to database
|
||||
_, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
|
||||
// Update the node in database
|
||||
node := existingNodeView.AsStruct()
|
||||
if !regReq.Expiry.IsZero() {
|
||||
err := hsdb.NodeSetExpiry(tx, existingNodeView.ID(), regReq.Expiry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Update machine key if changed
|
||||
if node.MachineKey != machineKey {
|
||||
err := hsdb.NodeSetMachineKey(tx, node, machineKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Update tags
|
||||
err := hsdb.SetTags(tx, existingNodeView.ID(), pak.Proto().GetAclTags())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Update last seen
|
||||
err = hsdb.SetLastSeen(tx, existingNodeView.ID(), time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Mark the pre-auth key as used if not reusable
|
||||
if !pak.Reusable {
|
||||
err = hsdb.UsePreAuthKey(tx, pak)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Return the node to satisfy the Write signature
|
||||
return hsdb.GetNodeByID(tx, existingNodeView.ID())
|
||||
})
|
||||
if err != nil {
|
||||
return types.NodeView{}, change.EmptySet, fmt.Errorf("failed to update node: %w", err)
|
||||
return types.NodeView{}, c, nil
|
||||
}
|
||||
|
||||
// Get updated node from NodeStore
|
||||
updatedNode, _ := s.nodeStore.GetNode(existingNodeView.ID())
|
||||
|
||||
// Check if policy manager needs updating
|
||||
c, err := s.updatePolicyManagerNodes()
|
||||
if err != nil {
|
||||
return updatedNode, change.EmptySet, fmt.Errorf("failed to update policy manager after node update: %w", err)
|
||||
}
|
||||
if !c.IsFull() {
|
||||
c = change.KeyExpiry(existingNodeView.ID())
|
||||
}
|
||||
|
||||
return updatedNode, c, nil
|
||||
return types.NodeView{}, change.EmptySet, nil
|
||||
}
|
||||
|
||||
log.Debug().
|
||||
@ -1284,38 +1197,7 @@ func (s *State) HandleNodeFromPreAuthKey(
|
||||
|
||||
var expiry *time.Time
|
||||
if !regReq.Expiry.IsZero() {
|
||||
expiry = ®Req.Expiry
|
||||
nodeToRegister.Expiry = expiry
|
||||
}
|
||||
|
||||
// Custom update function for existing nodes
|
||||
updateFunc := func(node *types.Node) {
|
||||
node.NodeKey = nodeToRegister.NodeKey
|
||||
node.Hostname = nodeToRegister.Hostname
|
||||
|
||||
// Preserve NetInfo from existing node to maintain DERP connectivity during relogin
|
||||
// Registration requests typically don't include NetInfo with PreferredDERP,
|
||||
// but we need to preserve it to avoid nodes appearing as disconnected
|
||||
if nodeToRegister.Hostinfo != nil && node.Hostinfo != nil &&
|
||||
nodeToRegister.Hostinfo.NetInfo == nil && node.Hostinfo.NetInfo != nil {
|
||||
log.Debug().
|
||||
Uint64("node.id", node.ID.Uint64()).
|
||||
Int("preferredDERP", node.Hostinfo.NetInfo.PreferredDERP).
|
||||
Msg("preserving NetInfo during pre-auth key node registration")
|
||||
|
||||
// Create a copy of the new Hostinfo and preserve the existing NetInfo
|
||||
newHostinfo := *nodeToRegister.Hostinfo
|
||||
newHostinfo.NetInfo = node.Hostinfo.NetInfo
|
||||
node.Hostinfo = &newHostinfo
|
||||
} else {
|
||||
node.Hostinfo = nodeToRegister.Hostinfo
|
||||
}
|
||||
|
||||
node.Endpoints = nodeToRegister.Endpoints
|
||||
node.RegisterMethod = nodeToRegister.RegisterMethod
|
||||
node.ForcedTags = nodeToRegister.ForcedTags
|
||||
node.AuthKey = nodeToRegister.AuthKey
|
||||
node.AuthKeyID = nodeToRegister.AuthKeyID
|
||||
nodeToRegister.Expiry = ®Req.Expiry
|
||||
}
|
||||
|
||||
// Post-save callback to use the pre-auth key
|
||||
@ -1651,166 +1533,3 @@ func peerChangeEmpty(peerChange tailcfg.PeerChange) bool {
|
||||
peerChange.LastSeen == nil &&
|
||||
peerChange.KeyExpiry == nil
|
||||
}
|
||||
|
||||
// nodeRegistrationHelper contains common logic for registering or updating nodes.
|
||||
// It handles IP allocation, given name generation, and the NodeStore vs Database update pattern.
|
||||
type nodeRegistrationHelper struct {
|
||||
node *types.Node
|
||||
userID types.UserID
|
||||
user *types.User
|
||||
expiry *time.Time
|
||||
updateExistingNode func(*types.Node)
|
||||
postSaveCallback func(tx *gorm.DB, savedNode *types.Node) error
|
||||
}
|
||||
|
||||
// registerOrUpdateNode is a common helper for node registration that both HandleNodeFromAuthPath
|
||||
// and HandleNodeFromPreAuthKey can use. It encapsulates the complex logic of handling
|
||||
// existing vs new nodes, IP allocation, and the NodeStore/Database update pattern.
|
||||
func (s *State) registerOrUpdateNode(helper nodeRegistrationHelper) (*types.Node, error) {
|
||||
// Check if node exists with same machine key
|
||||
var existingNode *types.Node
|
||||
if nv, exists := s.nodeStore.GetNodeByMachineKey(helper.node.MachineKey); exists && nv.Valid() {
|
||||
existingNode = nv.AsStruct()
|
||||
}
|
||||
|
||||
// Check for different user registration
|
||||
if existingNode != nil && existingNode.UserID != uint(helper.userID) {
|
||||
return nil, hsdb.ErrDifferentRegisteredUser
|
||||
}
|
||||
|
||||
// Handle IP allocation and existing node properties
|
||||
var ipv4, ipv6 *netip.Addr
|
||||
if existingNode != nil && existingNode.UserID == uint(helper.userID) {
|
||||
// Reuse existing node properties
|
||||
helper.node.ID = existingNode.ID
|
||||
helper.node.GivenName = existingNode.GivenName
|
||||
helper.node.ApprovedRoutes = existingNode.ApprovedRoutes
|
||||
ipv4 = existingNode.IPv4
|
||||
ipv6 = existingNode.IPv6
|
||||
} else {
|
||||
// Allocate new IPs
|
||||
var err error
|
||||
ipv4, ipv6, err = s.ipAlloc.Next()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("allocating IPs: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
helper.node.IPv4 = ipv4
|
||||
helper.node.IPv6 = ipv6
|
||||
helper.node.UserID = uint(helper.userID)
|
||||
helper.node.User = *helper.user
|
||||
if helper.expiry != nil {
|
||||
helper.node.Expiry = helper.expiry
|
||||
}
|
||||
|
||||
// Ensure unique given name if not set
|
||||
if helper.node.GivenName == "" {
|
||||
givenName, err := hsdb.EnsureUniqueGivenName(s.db.DB, helper.node.Hostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to ensure unique given name: %w", err)
|
||||
}
|
||||
helper.node.GivenName = givenName
|
||||
}
|
||||
|
||||
var savedNode *types.Node
|
||||
var err error
|
||||
if existingNode != nil && existingNode.UserID == uint(helper.userID) {
|
||||
// Update existing node - NodeStore first, then database
|
||||
s.nodeStore.UpdateNode(existingNode.ID, func(node *types.Node) {
|
||||
// Apply common updates
|
||||
node.UserID = helper.node.UserID
|
||||
node.User = helper.node.User
|
||||
node.IPv4 = helper.node.IPv4
|
||||
node.IPv6 = helper.node.IPv6
|
||||
node.IsOnline = ptr.To(false)
|
||||
node.LastSeen = ptr.To(time.Now())
|
||||
if helper.expiry != nil {
|
||||
node.Expiry = helper.expiry
|
||||
}
|
||||
|
||||
// Apply custom updates from caller
|
||||
if helper.updateExistingNode != nil {
|
||||
helper.updateExistingNode(node)
|
||||
}
|
||||
})
|
||||
|
||||
// Get the updated node from NodeStore to save to database
|
||||
// This ensures any changes made by updateExistingNode (like preserving NetInfo)
|
||||
// are persisted to the database
|
||||
updatedNodeView, exists := s.nodeStore.GetNode(existingNode.ID)
|
||||
if !exists || !updatedNodeView.Valid() {
|
||||
return nil, fmt.Errorf("failed to get updated node from NodeStore after update")
|
||||
}
|
||||
nodeToSave := updatedNodeView.AsStruct()
|
||||
|
||||
// Save to database
|
||||
savedNode, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
|
||||
if err := tx.Save(nodeToSave).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to save node: %w", err)
|
||||
}
|
||||
|
||||
// Run post-save callback if provided
|
||||
if helper.postSaveCallback != nil {
|
||||
if err := helper.postSaveCallback(tx, nodeToSave); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nodeToSave, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// New node - database first to get ID, then NodeStore
|
||||
savedNode, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
|
||||
if err := tx.Save(helper.node).Error; err != nil {
|
||||
return nil, fmt.Errorf("failed to save node: %w", err)
|
||||
}
|
||||
|
||||
// Run post-save callback if provided
|
||||
if helper.postSaveCallback != nil {
|
||||
if err := helper.postSaveCallback(tx, helper.node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return helper.node, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add to NodeStore after database creates the ID
|
||||
s.nodeStore.PutNode(*savedNode)
|
||||
}
|
||||
|
||||
return savedNode, nil
|
||||
}
|
||||
|
||||
// finalizeNodeRegistration handles the common final steps after node registration:
|
||||
// updating policy managers and generating the appropriate change set.
|
||||
func (s *State) finalizeNodeRegistration(savedNode *types.Node) (change.ChangeSet, error) {
|
||||
// Update policy managers
|
||||
usersChange, err := s.updatePolicyManagerUsers()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to update policy manager users after node registration")
|
||||
// Don't fail the registration, just log the error
|
||||
}
|
||||
|
||||
nodesChange, err := s.updatePolicyManagerNodes()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to update policy manager nodes after node registration")
|
||||
// Don't fail the registration, just log the error
|
||||
}
|
||||
|
||||
var c change.ChangeSet
|
||||
if !usersChange.Empty() || !nodesChange.Empty() {
|
||||
c = change.PolicyChange()
|
||||
} else {
|
||||
c = change.NodeAdded(savedNode.ID)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user