Skip to content

Commit e61b748

Browse files
committed
policy,state: authorize reauth tags against the authenticating user
Re-authenticating a tagged node with --advertise-tags checked the tag-owned node, not the authenticating user, so every tag was rejected. Fixes #3374
1 parent d3e79be commit e61b748

5 files changed

Lines changed: 550 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ keys remain all-access.
5353

5454
- Fix tagged node stuck expired after `tailscale logout`, unable to re-authenticate [#3394](https://github.com/juanfont/headscale/pull/3394)
5555
- Re-registering a tagged node with a different pre-auth key now applies the new key's tags instead of silently keeping the old ones [#3394](https://github.com/juanfont/headscale/pull/3394)
56+
- Fix re-authenticating an already-tagged node with `--advertise-tags` being rejected when the authenticating user owns the tags [#3394](https://github.com/juanfont/headscale/pull/3394)
5657

5758
## 0.29.2 (2026-07-01)
5859

hscontrol/policy/pm.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ type PolicyManager interface {
3030
// NodeCanHaveTag reports whether the given node can have the given tag.
3131
NodeCanHaveTag(node types.NodeView, tag string) bool
3232

33+
// UserCanHaveTag reports whether the given user owns the given tag, i.e.
34+
// is listed (directly or via a group) in the tag's tagOwners. This is the
35+
// user half of NodeCanHaveTag, used to authorise re-auth tag changes
36+
// against the authenticating user rather than the node's stale ownership.
37+
UserCanHaveTag(user types.UserView, tag string) bool
38+
3339
// TagExists reports whether the given tag is defined in the policy.
3440
TagExists(tag string) bool
3541

hscontrol/policy/v2/policy.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,37 @@ func (pm *PolicyManager) NodeCanHaveTag(node types.NodeView, tag string) bool {
972972
return false
973973
}
974974

975+
// UserCanHaveTag reports whether the given user is one of the tag's owners
976+
// (directly or via a group). It is the user half of [PolicyManager.NodeCanHaveTag]:
977+
// re-authentication authorises requested tags against the authenticating user,
978+
// because a tag-owned node carries no user and its IP is not in any owner set,
979+
// so only the user presenting the credential can prove ownership.
980+
func (pm *PolicyManager) UserCanHaveTag(user types.UserView, tag string) bool {
981+
if pm == nil || !user.Valid() {
982+
return false
983+
}
984+
985+
pm.mu.RLock()
986+
defer pm.mu.RUnlock()
987+
988+
if pm.pol == nil {
989+
return false
990+
}
991+
992+
owners, exists := pm.pol.TagOwners[Tag(tag)]
993+
if !exists {
994+
return false
995+
}
996+
997+
for _, owner := range owners {
998+
if pm.userMatchesOwner(user, owner) {
999+
return true
1000+
}
1001+
}
1002+
1003+
return false
1004+
}
1005+
9751006
// TagOwnedByTags reports whether a credential holding ownerTags is authorised to
9761007
// apply tag. It is true when tag is one of ownerTags, or when tag's tagOwners
9771008
// chain (tag-to-tag ownership) transitively includes one of ownerTags. This is

0 commit comments

Comments
 (0)