Commit Graph

4352 Commits

Author SHA1 Message Date
Kristoffer Dalby 237dc74e73 state: reuse peer adjacency for payload-only writes
A write that cannot move visibility carries the previous adjacency
forward; policy and user changes rebuild it explicitly.

Updates #3417

(cherry picked from commit e3c4c81b18)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 938c2bd753 policy,state: key the peer map by node ID
Adjacency becomes immutable, so a snapshot can resolve peers through its
own fresh views instead of storing them.

Updates #3417

(cherry picked from commit 95ba787417)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 9fbf7b9b60 mapper: take peer visibility from the peer map only
Fixes #3408

0.29's BuildPeerMap returns node views, not node IDs, so the peer map
assertion compares IDs read off the views.

(cherry picked from commit e48bc46cc6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 5aded15bf2 policy/v2: suggest approved exit nodes by default
Matches SaaS; Apple clients hide the exit-node list without a suggestion.

Fixes #3415

0.29's tailscale has no tailcfg/nodecap package, so the constant keeps its
tailcfg name here and in the test.

(cherry picked from commit c604874dba)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby c7d9d03500 hscontrol: replace tailscale line refs with doc links
Line numbers drift on every upstream bump.

0.29's tailscale has no tailcfg/nodecap package, so the constants keep their
tailcfg names here.

(cherry picked from commit 06fa3075da)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 694bafee0a policy/v2: compare peer CapMap against SaaS route captures
SaaS stamps suggest-exit-node on approved exit peers without nodeAttrs.

Updates #3415

(cherry picked from commit 0e8a3bba54)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 8490631f06 policy/v2: add via exit node capture
Updates #3408

(cherry picked from commit 5861005ef6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby a7f7e6a401 servertest: compare user-owned nodes in via compat tests
Updates #3408

(cherry picked from commit 23f7eedac6)
2026-09-23 21:19:22 +02:00
Andrei Korviakov ebe18cebff hscontrol: keep the ACME error body readable in acmeLogger
acmeLogger drained and closed the body of every ACME error response before
handing the response back to golang.org/x/crypto/acme. The client parses that
body to classify errors, so badNonce was no longer recognised: isBadNonce
returned false, clearNonces was never called and Client.post treated the 400 as
non-retriable.

One badNonce reply therefore stops certificate renewal for good. autocert
retries every 30-60 minutes, each attempt reuses a nonce stored from the
previous failed response, that nonce has already expired, and the loop repeats
until the process is restarted or the certificate expires.

Restore the body with a fresh reader after logging it.

(cherry picked from commit 7472e98f6c)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby aa4c952bca policy: ignore '#' metadata fields across the whole policy
The filter lived in ACL.UnmarshalJSON, so grants, ssh and nodeAttrs still
hit RejectUnknownMembers. Strip the members in the HuJSON AST instead, at
the single decode entrypoint. Grant "app" payloads are left untouched.

Fixes #3479

(cherry picked from commit 5401edb6a8)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 7783e7f679 integration: cover node expiry and recovery for every client
Updates #3470

TestExpireNode is rewritten against 0.29's gRPC node type: GetId, GetName
and GetOnline instead of the HTTP client's string fields.

(cherry picked from commit 932b8174f6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby e6c0a8adee types: regenerate node view
Updates #3470

(cherry picked from commit 804954f5bc)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby a91f708adc state: mark expired nodes offline without ending the session
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

Connect keeps 0.29's NodeOnlineFor peer patch; only the not-online branch
is new here. The updateChanges hunk is dropped: 0.29 has no caller for it.

(cherry picked from commit 80c863b15a)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby cc41593d9d integration: pin which Hostinfo changes reach peers
Updates #3417

(cherry picked from commit 6a9f6c3bd7)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 8f7ee375e9 state: skip node health writes that change nothing
An all-unchanged probe cycle no longer publishes a snapshot.

Updates #3417

(cherry picked from commit 1b820b7ebe)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby b972da3fe9 state: stop broadcasting a whole peer on disconnect
Going offline changes nothing the policy reads, so the row write skips
the policy refresh and peers get only the offline patch.

Updates #3417

(cherry picked from commit dfe0d3f2e5)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 8973347d22 state: classify map requests before broadcasting
Each request is reduced to the narrowest change it justifies, so a
keepalive or endpoint bump no longer resends the whole node to peers.

Updates #3417

0.29 pins an older tailscale where Hostinfo.NetInfo.PreferredDERP is an int,
so the DERPRegionID types are int here.

(cherry picked from commit 59f3ff12a7)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby b8da1c2d6f mapper: drop empty changes before fan-out
An empty change carries no work for any recipient, so it never becomes
a pending entry. Adds headscale_mapper_changes_dropped_total.

Updates #3417

(cherry picked from commit 286f1d5a12)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 4411264ef9 types: detect policy change on user identity and exit routes
Updates #3417

(cherry picked from commit 905ab92fe0)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby bc5b9d56b2 policy,types: skip recompile when the user list is unchanged
SetUsers now also reports whether user-derived peer adjacency moved.

Updates #3417

(cherry picked from commit be322e8ea7)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby fdb782daa5 state: rename persist helpers to say what they do
Updates #3417

(cherry picked from commit 1bbe59b98d)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby bdfa3401ff AGENTS.md: drop stale line numbers
Updates #3417

(cherry picked from commit b10438d8f6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby cbb9357d1c oidc: harden reloadable confirmation flow
Updates #3365

(cherry picked from commit 10dd38fcef)
2026-09-23 21:19:22 +02:00
Sean Reifschneider 2da47a77d6 oidc: serve the registration confirmation page from a reloadable URL
The interstitial was the body of /oidc/callback, the URL carrying the
single-use code, so any reload re-entered the spent exchange. Redirect to
GET /register/confirm/{auth_id}, also missing from the route table.

0.29 lacks the authPathURL helper from main, so it is added here.

(cherry picked from commit 6d377b5348)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2b28f5756b oidc: harden callback CSRF cookies, state reuse, and issuer config
Defence-in-depth fixes to the OIDC login flow.

Set the state/nonce cookie Secure flag from the configured server_url
scheme rather than req.TLS, so the cookies stay Secure behind a
TLS-terminating reverse proxy where the proxy-to-Headscale hop is plain
HTTP. Deriving it from config avoids trusting a spoofable
X-Forwarded-Proto header.

Make the OIDC state single-use: consume it from the cache on the
callback and clear the state/nonce cookies once validated, so a replayed
callback cannot resolve the same session and the cookies do not linger
until expiry.

Bound OIDC discovery to the caller's context so a slow or unreachable
issuer fails startup within the timeout instead of hanging, and validate
the issuer URL and required client_id/client_secret at config load so an
unworkable setup fails fast.

(cherry picked from commit 622e08f5e6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 1e528f7f52 integration: cover deletion across client versions
Updates #3410

(cherry picked from commit 1d6e97c459)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby d388244025 noise: make deleted-node expiry clock independent
Updates #3410

(cherry picked from commit afb3020ef0)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 974becf658 change, mapper: distinguish deleted nodes
Updates #3410

(cherry picked from commit a91c0519c2)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 0fca2bb646 state: preserve committed node deletion changes
Updates #3410

(cherry picked from commit fc6a16fdfc)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 83eff4000a poll: interrupt blocked map writes
Updates #3410

(cherry picked from commit ef456542ce)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 3a2b133a41 .github/workflows: regenerate the integration test matrix 2026-09-23 21:19:22 +02:00
Kristoffer Dalby b9c7b4c44b integration: cover node deletion ending the long poll
A deleted node is served a self node with no StableID, so Status().Self.ID
goes empty; TestACLPolicyPropagationOverTime must match on hostname instead.

Updates #3410

(cherry picked from commit 2cefb7ec06)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby fb197db163 poll, noise: tell a deleted node to re-authenticate
A bare 404 is indistinguishable from any other map-path error to a Tailscale
client: it retries forever, still logged in. Only a self node with a past
KeyExpiry reaches NeedsLogin. Also skip the reconnect grace wait, which a
deleted node can never satisfy.

Fixes #3410

(cherry picked from commit b1fb6ed2e6)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2b6cb3a2cc types/change: drop unused VisibilityChange
It fills PeersRemoved without deleting anything. The batcher now tears down
the session behind every removed id, so a caller would kill a live poll.

Updates #3410

(cherry picked from commit 42bf00523a)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 397149e9be mapper: stop a deleted node's map session
Dropping the batcher entry left serveLongPoll streaming to a node that no
longer exists: Close ranges b.nodes and can no longer reach it, so shutdown
blocks and the client keeps polling instead of re-authenticating.

Updates #3410

(cherry picked from commit 0b69e844f5)
2026-09-23 21:19:22 +02:00
Saleh b4f991b381 types: lowercase DNS extra record names
DNS names are case-insensitive, but clients match extra records against
the lowercased query name, so records with mixed-case names (for example
"Printer.fritz.box" in an extra_records_path file) never resolved and
queries fell through to the global nameserver.

Normalize record names to lowercase where the records enter the tailcfg
DNS config, covering both dns.extra_records and extra_records_path.

Fixes #2782

(cherry picked from commit 95ba1f0566)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2b8dbea412 mapper: assert no map response lists the recipient as its own peer
Covers every change shape under four policy shapes, plus connect churn. The
zero-matcher shape is the gap: buildTailPeers skips ReduceNodes there, so the
peer lookup is the only self filter left.

(cherry picked from commit 8995d8a558)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby d4b073e830 state: exclude self from peers on the named peer-ID path
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.

(cherry picked from commit d9aebf472d)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 2a76860bdb build: bump test image Go to 1.27.1
tailscale HEAD now requires Go 1.27.1, so the test image build failed
and every integration job skipped.

Same change as f3f6c9822 on main, written against 0.29's Dockerfiles.
2026-09-23 21:19:22 +02:00
Kristoffer Dalby b3b55cc472 cli: fix users rename when resolved by name
The rename request sent the raw --identifier flag value, which is 0 when
the user was resolved with --name, so the API answered "user not found".
Send the matched user's ID instead.

Equivalent of #3442 on main, rewritten: that fix targets the v1 HTTP client,
which 0.29 does not have.
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 625fa86e1a integration: pin docker client to daemon API version
dockertest's bundled client builds against API v1.25; Docker Engine 29
rejects it (min 1.40), surfacing as a "broken pipe" that fails every local
image build. Pin to the daemon's reported version so builds use a live path.

Closes #2937

(cherry picked from commit dfc25f06e1)
2026-09-23 21:19:22 +02:00
Sebastien Tardif 46a80ea8f9 dns: cancel extra-records retry on shutdown and close watcher on setup error
After Remove/Rename, the extra-records filewatcher retried with
context.Background and the default 15-minute backoff budget, so Close
could not stop Run. Cancel that retry when closeCh closes. If the file
is still missing after the budget, watch the parent directory so a later
recreate is seen.

Close the fsnotify watcher on NewExtraRecordsManager error paths after
NewWatcher succeeds.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

(cherry picked from commit a48a42baf4)
2026-09-23 21:19:22 +02:00
Igor Serganov fc894aa9da linting issue fix
(cherry picked from commit c26b2fd0a4)
2026-09-23 21:19:22 +02:00
Igor Serganov 032470a5d3 hscontrol: cancel tailsql on graceful shutdown
Serve stored a Background context for tailsql and called
context.Done() during signal shutdown. Done only returns the
done channel and does not cancel, so tailsql never stopped
when Headscale shut down.

Create a cancellable child of the Serve context, run
`runTailSQLService` in the listener errgroup so its error is
surfaced, and call the cancel func on shutdown - tailsql
unblocks on ctx.Done.

(cherry picked from commit a9f80d8802)
2026-09-23 21:19:22 +02:00
Paulo Luna 33db8c6b29 fix(metrics): collect metrics for non-OPTIONS requests
Fix the inverted HTTP metrics skip condition, which caused the collector to ignore all requests except OPTIONS.

Apply the correction to both the main and Noise routers so regular HTTP traffic is included in http_requests_total and http_request_duration_seconds.

(cherry picked from commit 63ce8f2295)
2026-09-23 21:19:22 +02:00
Igor Bernstein 00babf430a docs: reword the tvOS warning to match the new step order
The steps now open the Tailscale app first to install the VPN
configuration, which contradicted the warning against opening the app
after installation. The actual constraint is not signing in before the
headscale URL is set.

(cherry picked from commit cbe30304dc)
2026-09-23 21:19:22 +02:00
Igor Bernstein e3dba1fdf9 templates: apply the tvOS setup reorder to the served /apple page
The instructions are duplicated between the docs and the HTML page
headscale serves at /apple. Reorder the tvOS steps there to match, so
the VPN configuration is installed before the alternate coordination
server URL is set.

(cherry picked from commit efe947507b)
2026-09-23 21:19:22 +02:00
igorbernstein b9b2beb781 Update AppleTV configuration steps
The instructions didn't work for tvOS 26.6 / Tailscale 1.102.2. When the `ALTERNATE COORDINATION SERVER URL` is set, the `Install VPN Configuration` breaks.
Clicking does nothing and the tvOS app logs showed `addUser: backendManager does not exist`.

Instead I first started tailscale and installed the vpn profile and then add the headscale url and then signed. Which seemed to have worked

(cherry picked from commit fad937c062)
2026-09-23 21:19:22 +02:00
Kristoffer Dalby 5aff68b5b9 mkdocs: bump version
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
(cherry picked from commit 565fd254d0)
v0.29.3
2026-07-29 14:35:31 +02:00
Kristoffer Dalby 235a57ec31 CHANGELOG: add 0.29.3
Updates #3385

(cherry picked from commit 8bb26c5967)
2026-07-29 14:35:31 +02:00