Skip to content

Commit 34d6f20

Browse files
committed
Merge remote-tracking branch 'upstream/main' into kradalby/3385-alpine-test-fail
# Conflicts: # hscontrol/auth.go # hscontrol/auth_test.go
2 parents f2bef17 + cfd845c commit 34d6f20

20 files changed

Lines changed: 2815 additions & 51 deletions

.github/workflows/test-integration.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ jobs:
352352
- TestTagsAuthKeyWithTagCannotAddViaCLI
353353
- TestTagsAuthKeyWithTagCannotChangeViaCLI
354354
- TestTagsAuthKeyWithTagAdminOverrideReauthPreserves
355+
- TestTagsReauthDifferentKeyRetagsNode
356+
- TestTagsReauthDifferentKeyRemovesTag
355357
- TestTagsAuthKeyWithTagCLICannotModifyAdminTags
356358
- TestTagsAuthKeyWithoutTagCannotRequestTags
357359
- TestTagsAuthKeyWithoutTagRegisterNoTags
@@ -379,6 +381,10 @@ jobs:
379381
- TestTagsAuthKeyWithoutUserInheritsTags
380382
- TestTagsAuthKeyWithoutUserRejectsAdvertisedTags
381383
- TestTagsAuthKeyConvertToUserViaCLIRegister
384+
- TestTaggedNodeLogoutReloginSingleUseKeyOnline
385+
- TestTaggedNodeLogoutReloginReusableKeyOnline
386+
- TestTagsOIDCReauthAddOwnedTag
387+
- TestTagsReauthEmptyTagsReturnsToUserSurvives
382388
- TestTS2021WebSocketGET
383389
- TestTS2021WASMClientUnderNode
384390
- TestTailscaleRustAxum

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ keys remain all-access.
4545
- Expiring or deleting a non-existent pre-auth key now returns an error instead of silently succeeding [#3324](https://github.com/juanfont/headscale/pull/3324)
4646
- Improve systemd service file hardening [#3341](https://github.com/juanfont/headscale/pull/3341)
4747

48+
## 0.29.3 (202x-xx-xx)
49+
50+
**Minimum supported Tailscale client version: v1.80.0**
51+
52+
### Changes
53+
54+
- Fix tagged node stuck expired after `tailscale logout`, unable to re-authenticate [#3394](https://github.com/juanfont/headscale/pull/3394)
55+
- 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)
57+
4858
## 0.29.2 (2026-07-01)
4959

5060
**Minimum supported Tailscale client version: v1.80.0**

cmd/headscale/cli/users.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@ var createUserCmd = &cobra.Command{
118118
RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error {
119119
userName := args[0]
120120

121-
log.Trace().Interface(zf.Client, client).Msg("obtained API client")
122-
123121
request := clientv1.CreateUserJSONRequestBody{Name: &userName}
124122

125123
if displayName, _ := cmd.Flags().GetString("display-name"); displayName != "" {

hscontrol/auth.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,20 @@ func (h *Headscale) handleLogout(
233233
Msg("Node is not ephemeral, setting expiry instead of deleting")
234234
}
235235

236+
// Tagged nodes have key expiry permanently disabled (they are owned by
237+
// their tags, not a user, and never expire - KB 1068). Logging one out has
238+
// no expiry semantics, so do not stamp an expiry on it: doing so leaves the
239+
// node IsExpired() forever and it can never re-authenticate (#3371). The
240+
// admin path `headscale nodes expire` remains free to set a deliberate
241+
// expiry via SetNodeExpiry; only the logout path is guarded here.
242+
if node.IsTagged() {
243+
log.Debug().
244+
EmbedObject(node).
245+
Msg("Tagged node logout: not stamping expiry (tagged nodes never expire)")
246+
247+
return nodeToRegisterResponse(node), nil
248+
}
249+
236250
// Update the internal state with the nodes new expiry, meaning it is
237251
// logged out.
238252
//
@@ -322,6 +336,18 @@ func (h *Headscale) waitForFollowup(
322336
return h.reqToNewRegisterResponse(req, machineKey)
323337
}
324338

339+
// The followup poll is only authenticated by the auth ID in the
340+
// URL, so fail closed unless the Noise session asking for the
341+
// result was started with the same machine key that opened the
342+
// registration. [State.HandleNodeFromAuthPath] resolves the node
343+
// from the cached [types.RegistrationData.MachineKey], so the two
344+
// match on the normal path. [Headscale.handleRegister] and
345+
// [Headscale.handleLogout] apply the same check.
346+
err := machineKeyMismatch(verdict.Node, machineKey)
347+
if err != nil {
348+
return nil, err
349+
}
350+
325351
return nodeToRegisterResponse(verdict.Node), nil
326352
}
327353
}

0 commit comments

Comments
 (0)