1
0
mirror of https://github.com/juanfont/headscale.git synced 2025-09-02 13:47:00 +02:00

stuff auth lint

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2025-08-18 22:31:51 +02:00
parent 2a906cd15e
commit 3a92b14c1a
No known key found for this signature in database
24 changed files with 296 additions and 180 deletions

View File

@ -551,7 +551,6 @@ be assigned to nodes.`,
}
}
if confirm || force {
ctx, client, conn, cancel := newHeadscaleCLIWithConfig()
defer cancel()

View File

@ -265,6 +265,7 @@ func (h *Headscale) handleRegisterInteractive(
)
log.Info().Msgf("Starting node registration using key: %s", registrationId)
return &tailcfg.RegisterResponse{
AuthURL: h.authProvider.AuthURL(registrationId),
}, nil

View File

@ -37,7 +37,6 @@ var tailscaleToCapVer = map[string]tailcfg.CapabilityVersion{
"v1.86.2": 123,
}
var capVerToTailscaleVer = map[tailcfg.CapabilityVersion]string{
90: "v1.64.2",
95: "v1.66.0",

View File

@ -271,7 +271,7 @@ func RenameNode(tx *gorm.DB,
}
if count > 0 {
return fmt.Errorf("name is not unique")
return errors.New("name is not unique")
}
if err := tx.Model(&types.Node{}).Where("id = ?", nodeID).Update("given_name", newName).Error; err != nil {
@ -327,7 +327,6 @@ func (hsdb *HSDatabase) DeleteEphemeralNode(
})
}
// RegisterNodeForTest is used only for testing purposes to register a node directly in the database.
// Production code should use state.HandleNodeFromAuthPath or state.HandleNodeFromPreAuthKey.
func RegisterNodeForTest(tx *gorm.DB, node types.Node, ipv4 *netip.Addr, ipv6 *netip.Addr) (*types.Node, error) {

View File

@ -359,6 +359,7 @@ func (b *LockFreeBatcher) IsConnected(id types.NodeID) bool {
// During grace period, always return true to allow DNS resolution
// for logout HTTP requests to complete successfully
gracePeriod := 45 * time.Second
return time.Since(*val) < gracePeriod
}

View File

@ -27,7 +27,7 @@ type batcherTestCase struct {
}
// testBatcherWrapper wraps a real batcher to add online/offline notifications
// that would normally be sent by poll.go in production
// that would normally be sent by poll.go in production.
type testBatcherWrapper struct {
Batcher
}
@ -58,7 +58,7 @@ func (t *testBatcherWrapper) RemoveNode(id types.NodeID, c chan<- *tailcfg.MapRe
return true
}
// wrapBatcherForTest wraps a batcher with test-specific behavior
// wrapBatcherForTest wraps a batcher with test-specific behavior.
func wrapBatcherForTest(b Batcher) Batcher {
return &testBatcherWrapper{Batcher: b}
}
@ -808,7 +808,7 @@ func TestBatcherBasicOperations(t *testing.T) {
// Disconnect the second node
batcher.RemoveNode(tn2.n.ID, tn2.ch)
assert.False(t, batcher.IsConnected(tn2.n.ID))
// Note: IsConnected may return true during grace period for DNS resolution
// First node should get update that second has disconnected.
select {
@ -841,9 +841,8 @@ func TestBatcherBasicOperations(t *testing.T) {
// Test RemoveNode
batcher.RemoveNode(tn.n.ID, tn.ch)
if batcher.IsConnected(tn.n.ID) {
t.Error("Node should be disconnected after RemoveNode")
}
// Note: IsConnected may return true during grace period for DNS resolution
// The node is actually removed from active connections but grace period allows DNS lookups
})
}
}

View File

@ -281,7 +281,7 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
util.LogErr(err, "could not get userinfo; only using claims from id token")
}
// The user claims are now updated from the the userinfo endpoint so we can verify the user a
// The user claims are now updated from the userinfo endpoint so we can verify the user
// against allowed emails, email domains, and groups.
if err := validateOIDCAllowedDomains(a.cfg.AllowedDomains, &claims); err != nil {
httpError(writer, err)

View File

@ -147,7 +147,7 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
// This ensures that:
// - Previously approved routes are ALWAYS preserved (auto-approval never removes routes)
// - New routes can be auto-approved according to policy
// - Routes can only be removed by explicit admin action (not by auto-approval)
// - Routes can only be removed by explicit admin action (not by auto-approval).
func ApproveRoutesWithPolicy(pm PolicyManager, nv types.NodeView, currentApproved, announcedRoutes []netip.Prefix) ([]netip.Prefix, bool) {
if pm == nil {
return currentApproved, false

View File

@ -152,7 +152,6 @@ func (pr *PrimaryRoutes) SetRoutes(node types.NodeID, prefixes ...netip.Prefix)
Strs("prefixes", util.PrefixesToString(prefixes)).
Msg("PrimaryRoutes.SetRoutes called")
// If no routes are being set, remove the node from the routes map.
if len(prefixes) == 0 {
wasPresent := false

View File

@ -1012,7 +1012,7 @@ func (s *State) HandleNodeFromAuthPath(
return types.NodeView{}, change.EmptySet, fmt.Errorf("failed to find user: %w", err)
}
// Check if node already exists by node key (this is a refresh/re-registration)
// Check if node already exists by node key
existingNodeView, exists := s.nodeStore.GetNodeByNodeKey(regEntry.Node.NodeKey)
if exists && existingNodeView.Valid() {
// Node exists - this is a refresh/re-registration
@ -1028,8 +1028,8 @@ func (s *State) HandleNodeFromAuthPath(
if expiry != nil {
node.Expiry = expiry
}
// Node is re-registering, so it's coming online
node.IsOnline = ptr.To(true)
// Mark as offline since node is reconnecting
node.IsOnline = ptr.To(false)
node.LastSeen = ptr.To(time.Now())
})
@ -1048,6 +1048,7 @@ func (s *State) HandleNodeFromAuthPath(
// Get updated node from NodeStore
updatedNode, _ := s.nodeStore.GetNode(existingNodeView.ID())
return updatedNode, change.KeyExpiry(existingNodeView.ID()), nil
}
@ -1059,9 +1060,25 @@ func (s *State) HandleNodeFromAuthPath(
Str("expiresAt", fmt.Sprintf("%v", expiry)).
Msg("Registering new node from auth callback")
// Check if node exists with same machine key
var existingMachineNode *types.Node
if nv, exists := s.nodeStore.GetNodeByMachineKey(regEntry.Node.MachineKey); exists && nv.Valid() {
existingMachineNode = nv.AsStruct()
}
// Check for different user registration
if existingMachineNode != nil && existingMachineNode.UserID != uint(userID) {
return types.NodeView{}, change.EmptySet, hsdb.ErrDifferentRegisteredUser
}
// Prepare the node for registration
nodeToRegister := regEntry.Node
nodeToRegister.UserID = uint(userID)
nodeToRegister.User = *user
nodeToRegister.RegisterMethod = registrationMethod
if expiry != nil {
nodeToRegister.Expiry = expiry
}
// Handle IP allocation
var ipv4, ipv6 *netip.Addr
@ -1092,17 +1109,48 @@ func (s *State) HandleNodeFromAuthPath(
nodeToRegister.GivenName = givenName
}
savedNode, err := s.registerOrUpdateNode(nodeRegistrationHelper{
node: &nodeToRegister,
userID: userID,
user: user,
expiry: expiry,
updateExistingNode: updateFunc,
postSaveCallback: nil, // No post-save callback needed
var savedNode *types.Node
if existingMachineNode != nil && existingMachineNode.UserID == uint(userID) {
// Update existing node - NodeStore first, then database
s.nodeStore.UpdateNode(existingMachineNode.ID, func(node *types.Node) {
node.NodeKey = nodeToRegister.NodeKey
node.DiscoKey = nodeToRegister.DiscoKey
node.Hostname = nodeToRegister.Hostname
node.Hostinfo = nodeToRegister.Hostinfo
node.Endpoints = nodeToRegister.Endpoints
node.RegisterMethod = nodeToRegister.RegisterMethod
if expiry != nil {
node.Expiry = expiry
}
node.IsOnline = ptr.To(false)
node.LastSeen = ptr.To(time.Now())
})
// Save to database
savedNode, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
if err := tx.Save(&nodeToRegister).Error; err != nil {
return nil, fmt.Errorf("failed to save node: %w", err)
}
return &nodeToRegister, nil
})
if err != nil {
return types.NodeView{}, change.EmptySet, 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(&nodeToRegister).Error; err != nil {
return nil, fmt.Errorf("failed to save node: %w", err)
}
return &nodeToRegister, nil
})
if err != nil {
return types.NodeView{}, change.EmptySet, err
}
// Add to NodeStore after database creates the ID
s.nodeStore.PutNode(*savedNode)
}
// Delete from registration cache
s.registrationCache.Delete(registrationID)
@ -1114,13 +1162,17 @@ func (s *State) HandleNodeFromAuthPath(
}
close(regEntry.Registered)
// Finalize registration
c, err := s.finalizeNodeRegistration(savedNode)
// Update policy manager
nodesChange, err := s.updatePolicyManagerNodes()
if err != nil {
return savedNode.View(), c, err
return savedNode.View(), change.NodeAdded(savedNode.ID), fmt.Errorf("failed to update policy manager: %w", err)
}
return savedNode.View(), c, nil
if !nodesChange.Empty() {
return savedNode.View(), nodesChange, nil
}
return savedNode.View(), change.NodeAdded(savedNode.ID), nil
}
// HandleNodeFromPreAuthKey handles node registration using a pre-authentication key.
@ -1145,29 +1197,17 @@ func (s *State) HandleNodeFromPreAuthKey(
if !regReq.Expiry.IsZero() && regReq.Expiry.Before(time.Now()) && pak.Ephemeral {
// Find the node to delete
var nodeToDelete types.NodeView
if nv, exists := s.nodeStore.GetNodeByMachineKey(machineKey); exists && nv.Valid() {
for _, nv := range s.nodeStore.ListNodes().All() {
if nv.Valid() && nv.MachineKey() == machineKey {
nodeToDelete = nv
break
}
}
if nodeToDelete.Valid() {
c, err := s.DeleteNode(nodeToDelete)
if err != nil {
return types.NodeView{}, change.EmptySet, fmt.Errorf("deleting ephemeral node during logout: %w", err)
}
return types.NodeView{}, c, nil
}
return types.NodeView{}, change.EmptySet, nil
}
// Check if node already exists by node key (this is a refresh/re-registration)
existingNodeView, exists := s.nodeStore.GetNodeByNodeKey(regReq.NodeKey)
if exists && existingNodeView.Valid() {
// Node exists - this is a refresh/re-registration
log.Debug().
Str("node", regReq.Hostinfo.Hostname).
Str("machine_key", machineKey.ShortString()).
Str("node_key", regReq.NodeKey.ShortString()).
Str("user", pak.User.Username()).
Msg("Refreshing existing node registration with pre-auth key")
return types.NodeView{}, c, nil
}
@ -1182,9 +1222,17 @@ func (s *State) HandleNodeFromPreAuthKey(
Str("user", pak.User.Username()).
Msg("Registering node with pre-auth key")
// Check if node already exists with same machine key
var existingNode *types.Node
if nv, exists := s.nodeStore.GetNodeByMachineKey(machineKey); exists && nv.Valid() {
existingNode = nv.AsStruct()
}
// Prepare the node for registration
nodeToRegister := types.Node{
Hostname: regReq.Hostinfo.Hostname,
UserID: pak.User.ID,
User: pak.User,
MachineKey: machineKey,
NodeKey: regReq.NodeKey,
Hostinfo: regReq.Hostinfo,
@ -1195,39 +1243,58 @@ func (s *State) HandleNodeFromPreAuthKey(
AuthKeyID: &pak.ID,
}
var expiry *time.Time
if !regReq.Expiry.IsZero() {
nodeToRegister.Expiry = &regReq.Expiry
}
// Post-save callback to use the pre-auth key
postSaveFunc := func(tx *gorm.DB, savedNode *types.Node) error {
if !pak.Reusable {
return hsdb.UsePreAuthKey(tx, pak)
}
return nil
}
// Check if node already exists with same machine key for logging
var existingNode *types.Node
if nv, exists := s.nodeStore.GetNodeByMachineKey(machineKey); exists && nv.Valid() {
existingNode = nv.AsStruct()
}
savedNode, err := s.registerOrUpdateNode(nodeRegistrationHelper{
node: &nodeToRegister,
userID: types.UserID(pak.User.ID),
user: &pak.User,
expiry: expiry,
updateExistingNode: updateFunc,
postSaveCallback: postSaveFunc,
})
if err != nil {
return types.NodeView{}, change.EmptySet, fmt.Errorf("registering node: %w", err)
}
// Log re-authorization if it was an existing node
// Handle IP allocation and existing node properties
var ipv4, ipv6 *netip.Addr
if existingNode != nil && existingNode.UserID == pak.User.ID {
// Reuse existing node properties
nodeToRegister.ID = existingNode.ID
nodeToRegister.GivenName = existingNode.GivenName
nodeToRegister.ApprovedRoutes = existingNode.ApprovedRoutes
ipv4 = existingNode.IPv4
ipv6 = existingNode.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)
}
}
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
}
var savedNode *types.Node
if existingNode != nil && existingNode.UserID == pak.User.ID {
// Update existing node - NodeStore first, then database
s.nodeStore.UpdateNode(existingNode.ID, func(node *types.Node) {
node.NodeKey = nodeToRegister.NodeKey
node.Hostname = nodeToRegister.Hostname
node.Hostinfo = nodeToRegister.Hostinfo
node.Endpoints = nodeToRegister.Endpoints
node.RegisterMethod = nodeToRegister.RegisterMethod
node.ForcedTags = nodeToRegister.ForcedTags
node.AuthKey = nodeToRegister.AuthKey
node.AuthKeyID = nodeToRegister.AuthKeyID
if nodeToRegister.Expiry != nil {
node.Expiry = nodeToRegister.Expiry
}
node.IsOnline = ptr.To(false)
node.LastSeen = ptr.To(time.Now())
})
log.Trace().
Caller().
Str("node", nodeToRegister.Hostname).
@ -1235,12 +1302,65 @@ func (s *State) HandleNodeFromPreAuthKey(
Str("node_key", regReq.NodeKey.ShortString()).
Str("user", pak.User.Username()).
Msg("Node re-authorized")
// Save to database
savedNode, err = hsdb.Write(s.db.DB, func(tx *gorm.DB) (*types.Node, error) {
if err := tx.Save(&nodeToRegister).Error; err != nil {
return nil, fmt.Errorf("failed to save node: %w", err)
}
// Finalize registration
c, err := s.finalizeNodeRegistration(savedNode)
if !pak.Reusable {
err = hsdb.UsePreAuthKey(tx, pak)
if err != nil {
return savedNode.View(), c, err
return nil, fmt.Errorf("using pre auth key: %w", err)
}
}
return &nodeToRegister, nil
})
if err != nil {
return types.NodeView{}, change.EmptySet, fmt.Errorf("writing node to database: %w", 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(&nodeToRegister).Error; err != nil {
return nil, fmt.Errorf("failed to save node: %w", err)
}
if !pak.Reusable {
err = hsdb.UsePreAuthKey(tx, pak)
if err != nil {
return nil, fmt.Errorf("using pre auth key: %w", err)
}
}
return &nodeToRegister, nil
})
if err != nil {
return types.NodeView{}, change.EmptySet, fmt.Errorf("writing node to database: %w", err)
}
// Add to NodeStore after database creates the ID
s.nodeStore.PutNode(*savedNode)
}
// Update policy managers
usersChange, err := s.updatePolicyManagerUsers()
if err != nil {
return savedNode.View(), change.NodeAdded(savedNode.ID), fmt.Errorf("failed to update policy manager users: %w", err)
}
nodesChange, err := s.updatePolicyManagerNodes()
if err != nil {
return savedNode.View(), change.NodeAdded(savedNode.ID), fmt.Errorf("failed to update policy manager nodes: %w", err)
}
var c change.ChangeSet
if !usersChange.Empty() || !nodesChange.Empty() {
c = change.PolicyChange()
} else {
c = change.NodeAdded(savedNode.ID)
}
return savedNode.View(), c, nil

View File

@ -1317,7 +1317,7 @@ func TestACLAutogroupTagged(t *testing.T) {
require.NoError(t, err)
// Create nodes with proper naming
for i := 0; i < spec.NodesPerUser; i++ {
for i := range spec.NodesPerUser {
var tags []string
var version string
@ -1417,7 +1417,7 @@ func TestACLAutogroupTagged(t *testing.T) {
}
} else {
// This is an untagged node
assert.Len(ct, status.Peers(), 0, "untagged node %s should see 0 peers", hostname)
assert.Empty(ct, status.Peers(), "untagged node %s should see 0 peers", hostname)
// Add to untagged list only once we've verified it
found := false
@ -1431,7 +1431,7 @@ func TestACLAutogroupTagged(t *testing.T) {
untaggedClients = append(untaggedClients, client)
}
}
}, 30*time.Second, 1*time.Second, fmt.Sprintf("verifying peer visibility for node %s", hostname))
}, 30*time.Second, 1*time.Second, "verifying peer visibility for node %s", hostname)
}
// Verify we have the expected number of tagged and untagged nodes
@ -1443,7 +1443,7 @@ func TestACLAutogroupTagged(t *testing.T) {
status, err := client.Status()
require.NoError(t, err)
require.NotNil(t, status.Self.Tags, "tagged node %s should have tags", client.Hostname())
require.Greater(t, status.Self.Tags.Len(), 0, "tagged node %s should have at least one tag", client.Hostname())
require.Positive(t, status.Self.Tags.Len(), "tagged node %s should have at least one tag", client.Hostname())
t.Logf("Tagged node %s has tags: %v", client.Hostname(), status.Self.Tags)
}

View File

@ -124,7 +124,7 @@ func TestOIDCAuthenticationPingAll(t *testing.T) {
//
// Known timing considerations:
// - Nodes may expire at different times due to sequential login processing
// - The test must account for login time spread between first and last node
// - The test must account for login time spread between first and last node.
func TestOIDCExpireNodesBasedOnTokenExpiry(t *testing.T) {
IntegrationSkip(t)

View File

@ -390,7 +390,6 @@ func TestPreAuthKeyCommand(t *testing.T) {
)
assertNoErr(t, err)
assert.True(t, listedPreAuthKeysAfterExpire[1].GetExpiration().AsTime().Before(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[2].GetExpiration().AsTime().After(time.Now()))
assert.True(t, listedPreAuthKeysAfterExpire[3].GetExpiration().AsTime().After(time.Now()))
@ -450,7 +449,6 @@ func TestPreAuthKeyCommandWithoutExpiry(t *testing.T) {
// There is one key created by "scenario.CreateHeadscaleEnv"
assert.Len(t, listedPreAuthKeys, 2)
assert.True(t, listedPreAuthKeys[1].GetExpiration().AsTime().After(time.Now()))
assert.True(
t,

View File

@ -449,6 +449,7 @@ func (s *Scenario) GetOrCreateUser(userStr string) *User {
Clients: make(map[string]TailscaleClient),
}
s.users[userStr] = user
return user
}

View File

@ -626,7 +626,7 @@ func (t *TailscaleInContainer) IPv4() (netip.Addr, error) {
}
}
return netip.Addr{}, fmt.Errorf("no IPv4 address found")
return netip.Addr{}, errors.New("no IPv4 address found")
}
func (t *TailscaleInContainer) MustIPv4() netip.Addr {