Skip to content

Commit 04fd52b

Browse files
committed
state: stop expiring tagged nodes on logout
tailscale logout sends a past-dated RegisterRequest; handleLogout stamped that expiry on the node with no IsTagged guard. Tagged nodes have key expiry permanently disabled (KB 1068), so the stamp left them IsExpired forever: re-registration kept the stale expiry, the client saw NodeKeyExpired, rotated its key and retried with a now-spent key -> authkey already used, forever (or an interactive hang). Guard handleLogout so a tagged node is never stamped. Defensively clear a stale past expiry on re-registration (both the pre-auth-key and auth paths), scoped to IsExpired() so a deliberate future expiry from headscale nodes expire survives, and exclude tagged nodes from the expired-node re-validation gate so a spent key is not re-checked. Fixes #3371
1 parent f20f1f1 commit 04fd52b

5 files changed

Lines changed: 569 additions & 1 deletion

File tree

CHANGELOG.md

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

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)
47+
- A tagged node can re-authenticate after `tailscale logout` again: logout no longer stamps a key expiry on a tagged node, and a stale expiry left by an older version is cleared on re-registration [#3371](https://github.com/juanfont/headscale/issues/3371)
4748

4849
## 0.29.2 (2026-07-01)
4950

hscontrol/auth.go

Lines changed: 14 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
//

0 commit comments

Comments
 (0)