hscontrol: enforce that tagged nodes never have user_id

Tagged nodes are owned by their tags, not a user. Enforce this
invariant at every write path:

- createAndSaveNewNode: do not set UserID for tagged PreAuthKey
  registration; clear UserID when advertise-tags are applied
  during OIDC/CLI registration
- SetNodeTags: clear UserID/User when tags are assigned
- processReauthTags: clear UserID/User when tags are applied
  during re-authentication
- validateNodeOwnership: reject tagged nodes with non-nil UserID
- NodeStore: skip nodesByUser indexing for tagged nodes since
  they have no owning user
- HandleNodeFromPreAuthKey: add fallback lookup for tagged PAK
  re-registration (tagged nodes indexed under UserID(0)); guard
  against nil User deref for tagged nodes in different-user check

Since tagged nodes now have user_id = NULL, ListNodesByUser
will not return them and DestroyUser naturally allows deleting
users whose nodes have all been tagged. The ON DELETE CASCADE
FK cannot reach tagged nodes through a NULL foreign key.

Also tone down shouty comments throughout state.go.

Fixes #3077
This commit is contained in:
Kristoffer Dalby
2026-02-20 09:27:11 +00:00
parent 52d454d0c8
commit 75e56df9e4
5 changed files with 73 additions and 66 deletions

View File

@@ -105,10 +105,8 @@ type Node struct {
// parts of headscale.
GivenName string `gorm:"type:varchar(63);unique_index"`
// UserID is set for ALL nodes (tagged and user-owned) to track "created by".
// For tagged nodes, this is informational only - the tag is the owner.
// For user-owned nodes, this identifies the owner.
// Only nil for orphaned nodes (should not happen in normal operation).
// UserID identifies the owning user for user-owned nodes.
// Nil for tagged nodes, which are owned by their tags.
UserID *uint
User *User `gorm:"constraint:OnDelete:CASCADE;"`