Commit Graph

4501 Commits

Author SHA1 Message Date
Kristoffer Dalby 284a4891d7 types: validate listener address collisions
Reject configurations where two configured TCP listeners would bind
the same kernel socket. Covers every pair of listen_addr,
grpc_listen_addr, metrics_listen_addr, and tls_letsencrypt_listen
(when HTTP-01 ACME is in use). Each violation renders as a structured
ConfigError naming both YAML keys, the values the operator wrote, and
a hint pointing at the canonical setup.

Closes the symptom in #3227: the misconfiguration is now rejected at
config-load time with a self-explaining error, instead of failing at
runtime with a misleading "address already in use" log because the
ACME challenge listener and the main HTTP listener competed for the
same port inside the same process.

Fixes #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 6031194c36 types: add listener address helpers
portFromAddr resolves numeric and named ports (":http", ":https")
without touching /etc/services or the resolver. listenersOverlap
follows kernel rules: same port plus a wildcard host on either side,
or same port plus identical specific host, both count as collision;
different specific hosts on the same port do not. Set an explicit
viper default for tls_letsencrypt_listen so a minimal config still
resolves to ":http".

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby 9f9fd0d885 types: add structured ConfigError and validator
Foundation for restructuring config validation feedback. Typed error
with errors.Is/As/Join hooks, structured fields rendered as a
multi-line operator-facing block (current, conflicts with, allowed,
minimum, maximum, why, hint, see), and a configValidator that joins
violations via errors.Join. ConfigErrors walks the tree to collect
every *ConfigError, and Cause keeps existing sentinel identities
reachable through errors.Is.

Updates #3227
2026-09-23 15:18:34 +02:00
Kristoffer Dalby c604874dba policy/v2: suggest approved exit nodes by default
Matches SaaS; Apple clients hide the exit-node list without a suggestion.

Fixes #3415
2026-09-23 14:48:45 +02:00
Kristoffer Dalby 06fa3075da hscontrol: replace tailscale line refs with doc links
Line numbers drift on every upstream bump.
2026-09-23 14:48:45 +02:00
Kristoffer Dalby 0e8a3bba54 policy/v2: compare peer CapMap against SaaS route captures
SaaS stamps suggest-exit-node on approved exit peers without nodeAttrs.

Updates #3415
2026-09-23 14:48:45 +02:00
Kristoffer Dalby e48bc46cc6 mapper: take peer visibility from the peer map only
Fixes #3408
2026-09-23 14:48:35 +02:00
Kristoffer Dalby 5861005ef6 policy/v2: add via exit node capture
Updates #3408
2026-09-23 14:48:35 +02:00
Kristoffer Dalby 23f7eedac6 servertest: compare user-owned nodes in via compat tests
Updates #3408
2026-09-23 14:48:35 +02:00
Kristoffer Dalby 5401edb6a8 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
2026-09-23 09:30:22 +02:00
Kristoffer Dalby 932b8174f6 integration: cover node expiry and recovery for every client
Updates #3470
2026-09-23 09:23:51 +02:00
Kristoffer Dalby 804954f5bc types: regenerate node view
Updates #3470
2026-09-23 09:23:51 +02:00
Kristoffer Dalby 80c863b15a 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
2026-09-23 09:23:51 +02:00
Andrei Korviakov c90ba0f0d6 changelog: note the acmeLogger renewal fix 2026-09-15 14:37:20 +02:00
Andrei Korviakov 7472e98f6c 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.
2026-09-15 14:37:20 +02:00
Florian Preinstorfer 4087d1fee9 Use tmpfs for /tmp
This is relevant when docker is used as container runtime as it does not
set /tmp as tmpfs. With podman /tmp is mounted as tmpfs due to
`--read-only-tmpfs` (enabled by default).

Fixes: #3463
2026-09-10 14:12:53 +02:00
Kristoffer Dalby f91702d7ec CHANGELOG: note the peer map reuse
Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 4c6a2dff52 state: resolve changed peers through adjacency
ListPeers now filters named peers against the recipient's adjacency, so
a node the policy hides is never delivered.

Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby e3c4c81b18 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
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 95ba787417 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
2026-09-10 13:06:13 +02:00
Kristoffer Dalby f9f31c5e08 CHANGELOG: note narrower map request handling
Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 6a9f6c3bd7 integration: pin which Hostinfo changes reach peers
Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 1b820b7ebe state: skip node health writes that change nothing
An all-unchanged probe cycle no longer publishes a snapshot.

Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby dfe0d3f2e5 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
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 59f3ff12a7 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
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 286f1d5a12 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
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 905ab92fe0 types: detect policy change on user identity and exit routes
Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby be322e8ea7 policy,types: skip recompile when the user list is unchanged
SetUsers now also reports whether user-derived peer adjacency moved.

Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 1bbe59b98d state: rename persist helpers to say what they do
Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby b10438d8f6 AGENTS.md: drop stale line numbers
Updates #3417
2026-09-10 13:06:13 +02:00
Kristoffer Dalby 67d018258b CHANGELOG: merge the duplicate 0.29.4 sections
Two 0.29.4 headings had appeared. Move the node deletion, OIDC reload and
OIDC callback hardening entries into it, since all three ship in 0.29.4.
2026-09-10 09:37:09 +02:00
Kristoffer Dalby 9c9686eacd CHANGELOG: note OIDC confirmation reload fix
Updates #3365
2026-09-09 19:01:43 +02:00
Kristoffer Dalby 10dd38fcef oidc: harden reloadable confirmation flow
Updates #3365
2026-09-09 19:01:43 +02:00
Sean Reifschneider 6d377b5348 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.
2026-09-09 19:01:43 +02:00
Kristoffer Dalby 475d3ae82c CHANGELOG: note the self-as-peer map fix 2026-09-09 18:18:53 +02:00
Kristoffer Dalby 8995d8a558 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.
2026-09-09 18:18:53 +02:00
Kristoffer Dalby d9aebf472d 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.
2026-09-09 18:18:53 +02:00
Kristoffer Dalby 1d6e97c459 integration: cover deletion across client versions
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby afb3020ef0 noise: make deleted-node expiry clock independent
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby a91c0519c2 change, mapper: distinguish deleted nodes
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby fc6a16fdfc state: preserve committed node deletion changes
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby ef456542ce poll: interrupt blocked map writes
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby 754327dd6b CHANGELOG: node deletion now ends the client's session
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby cea9ebd105 .github/workflows: regenerate the integration test matrix
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby 2cefb7ec06 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
2026-09-09 18:18:17 +02:00
Kristoffer Dalby b1fb6ed2e6 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
2026-09-09 18:18:17 +02:00
Kristoffer Dalby 42bf00523a 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
2026-09-09 18:18:17 +02:00
Kristoffer Dalby 0b69e844f5 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
2026-09-09 18:18:17 +02:00
Saleh 95ba1f0566 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
2026-09-09 12:09:52 +02:00
Florian Preinstorfer 38722e5eeb Document automatic exit node selection and related node attributes
- Add "Automatic exit node selection" to route docs
- Add suggest-exit-node and suggest-exit-node-ui as supported nodeAttrs

Fixes: #3444
2026-09-08 10:32:27 +02:00