The lock is taken before any credential check, so client-chosen machine
keys grew the map forever. Ref-count entries; delete at zero.
Fixes ANT-2026-ZC18AF82
The rest arrives with the first MapRequest. A relogin keeps the node's
current Hostinfo, endpoints and disco key; an oversized merge is a 400.
Fixes ANT-2026-RQS7JAPP
Rows stored before the bounds are trimmed the same way at load; a
Hostinfo still over budget is dropped until the node reports again.
Fixes ANT-2026-RQS7JAPP
Fixes ANT-2026-5B6KE87K
Fixes ANT-2026-KFCFFM34
handleRegister, the OIDC auth path and SetNodeExpiry refuse it. Expiries
are stored in UTC, which SQLite can read back, and loaded in UTC, as
PostgreSQL returns the server's zone. The v1 API answers 400.
Fixes ANT-2026-QMCAFJ1Q
Only a key that speaks for a tagged node re-authenticates it: a tagged
key, or one of the user it was tagged from. Another user's key or login
adds that user's own device, or only wakes the node under its node key.
Fixes ANT-2026-AWGRB7O1
Such a route names a node, not a subnet: it stays approved and listed
but no viewer gets it. Via inclusions skip the election, so the rule
sits on everything RoutesForPeer returns.
Fixes ANT-2026-MLP8ZR5T
Checks a policy as a write would, without storing it. As on Tailscale,
a refused policy answers 200 with its reason; only an empty body is 400.
Terraform provider 0.29.3 calls it at plan time.
The expirable LRU ran its sweeper forever, one goroutine per State. Purge
first so pending waiters get ErrRegistrationExpired instead of hanging.
The OIDC state cache is closed at server shutdown for the same reason.
"Foo" and "foo" could share one DNS name; collisions now ignore case.
A rename never resent the node its own DNS config, so CertDomains lagged.
Updates #2137
The SetPolicy API handler calls State.SetPolicy, which already rebuilds the peer map when the policy changed, and then ReloadPolicy, which rebuilt it again even when nothing changed. So every policy write built the peer map twice.
ReloadPolicy now rebuilds only when the policy really changed. A test checks one build per changed write and none for an unchanged reload.
Part of #3549
The client picks the check rule by login user; the server took the
first rule for the node pair, so a root login could get a 12h
localpart period instead of "always", or be approved after its rule
was removed while another user's rule remained. Hold URLs now carry
the concrete user: tailssh never expanded the encoded $LOCAL_USER.
Updates #3508
One peer build per tag/user/IP/route write; callers detect policy moves
via NodesGeneration. Per-node caches only store results for the node
pm holds, so a mapper reading mid-build cannot pin a stale filter.
API keys, pre-auth keys and OAuth clients/tokens share one table and verify
path. Secrets carry 256 bits of crypto/rand entropy, so a SHA-256 digest
needs no stretching; bcrypt/argon2id rows rehash on use until 0.32.
Online now requires a live session and an unexpired key, derived in one
place by Node.ShouldBeOnline so every writer agrees what online means.
Fixes#3470
ListPeers with explicit IDs filtered every node, not every peer, so a change
batch naming the recipient returned it as its own peer. db.ListPeers keeps
this out with `id <> nodeID`; the NodeStore rewrite dropped it.
With node.ephemeral.inactivity_timeout set, ephemeral nodes are
usually deleted after they go offline, but under reconnect churn some
departed nodes stayed in the node list as disconnected indefinitely
until removed manually or until Headscale restarted.
Ephemeral cleanup is timer-based via EphemeralGarbageCollector, not a
periodic LastSeen scan. serveLongPoll cancelled any pending GC timer
at the very start of a long-poll attempt and only rescheduled on a
clean disconnect after Connect. If a reconnect cancelled the timer and
then failed before Connect (for example an UpdateNodeFromMapRequest
error), the deferred cleanup saw connectGen == 0 and returned without
Schedule. The node remained offline with no deletion timer and no
reconciler to recover it.
Cancel the ephemeral GC timer only after a successful Connect, so a
failed reconnect leaves an already-armed inactivity timer intact.
Successful reconnects still cancel GC once the node is online, and a
later disconnect reschedules as before.
Add TestFailedReconnectDoesNotCancelEphemeralGC to lock in the
ordering, plus IsScheduled and DeleteNodeFromStoreForTest helpers for
the test.
Fixes#3382
Co-authored-by: Cursor <cursoragent@cursor.com>
Re-registering a tagged node with a different key discarded the new key's
tags and left a stale auth-key reference; retag on key change and persist it.
Fixes#3370