Commit Graph
5013 Commits
Author SHA1 Message Date
Kristoffer Dalby 2d094f2243 state: send nothing for a policy write that changes nothing
As SaaS. Every accepted write sent all nodes a map and cleared SSH check
approvals, so a tool that re-posts the same file prompted users again.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 93aef86908 mapper: give a tagged node its own user in its own entry
As SaaS. Clients take a peer with their own user ID for a device of the same
owner, so every tagged peer was a file target and could read debug endpoints.
Updates #2462, #2970
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 490305417c policy/v2: make a user's own devices peers under any policy
As SaaS. Without the peer, sending a file to your own device failed
with 404 under a restrictive policy. The packet filter is unchanged.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 420293cecf policy/v2: keep withdrawn approved routes in wildcard sources
As SaaS, which holds them until an admin removes the approval.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby d2a85b1d16 policy/v2: compile a policy without rules as explicit allow-all
As SaaS, sources are the tailnet ranges and approved routes. They were
"*", so a host on an exit node's network could reach its clients.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 5741dba87d policy/v2: sort approved routes in wildcard sources
They followed NodeStore map order, so the same policy read as changed.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 8ba627c3f4 types: leave the base domain out of search domains without MagicDNS
As SaaS. Alone, it left a client that writes resolv.conf with no nameserver.
Updates #2029
2026-11-03 17:05:00 +01:00
Kristoffer Dalby b04bbd4317 mapper: send a disable-ipv4 holder its peers over IPv6 only
As SaaS. Peers' 100.x addresses routed into tailscale0 and hid a local
host with the same address, which is what the attr is meant to avoid.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 68b0274694 types: lead AllowedIPs with the node's own addresses
As SaaS. tailscale whois prints AllowedIPs[2:] as routes, so sorted
entries showed an own address in place of a subnet route.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby edad4ff324 types: do not stamp is-admin on tagged nodes
A tagged node has no user to hold the role. SaaS leaves it out unless policy
grants it. Stop stripping the cap, so captures now compare it.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 581ec5b3a0 derp: keep node order across map refreshes
Clients close their home relay when its order changes. SaaS's never moved.
2026-11-03 17:05:00 +01:00
Kristoffer Dalby 6fe0df64e1 CHANGELOG: note the map field changes
Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby a7f2c85cfd mapper: send base_domain as the exit node filtered set
SaaS sends its own domains, which name nothing here. Otherwise an exit node
proxies tailnet names, and where its OS resolver reaches MagicDNS a client
reads its view of the tailnet. MagicDNS only: without it, base_domain can be
public, and clients ask the exit node for its names. Reverse lookups stay
open, as on SaaS.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby ef6d033d19 state, change: send the last seen time in the offline patch
As SaaS, on every offline patch and no online one. Peers otherwise show no
last-seen time until the next full map. SaaS sends a coarser time; the exact
one here is deliberate.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby 5083d77731 mapper: leave online state off a node's own entry
As SaaS. Clients take their own state from the map poll.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby 50be190482 mapper: leave Cap off a node's own entry
As SaaS. Clients read it from peers only; a client knows its own version.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby 7d50f33e32 types: leave MachineAuthorized off peer entries
SaaS sets it only on a node's own entry, the only one clients read.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby 0d5e4f28e9 types: stop sending machine keys in map entries
Stable identity that outlives node key rotation. SaaS sends it on no entry; no
client from our minimum version on acts on it.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby 12de511271 servertest: compare map entry fields with SaaS
Wire rules from frames, entry fields from a replay, exit node filter by the
names it covers. Known differences listed as skips.

Updates #3626
2026-11-03 16:29:03 +01:00
Kristoffer Dalby 1986b52983 CHANGELOG: note via applying only to the ip part of a grant
Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby 398223c640 servertest: replay the via grant shapes through the mapper
Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby f88a24c509 policy/v2: record via grant shapes
Narrow and subnet sources, app with and without ip, an empty app, and a non-via
primary.

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby 2c70845bda policy/v2: count a router in a subnet source as a via viewer
SaaS sends it the via route (via-grant-v83).

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby 9fff03f4b5 policy/v2: apply via only to the ip part of a grant
SaaS treats app as if via were absent: v80 and v84 peer the same, v87 peers a
non-via router, v88 and v91 send the route on the primary. Dropping a via grant
with no ip at compile left grant-k12's nodes with no peers.

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby aa7b2ab404 policyutil: send a router no capability for its subnet
via-grant-v84 and v86 show SaaS sending none.

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby 3fdedefbf9 policy/v2: link a via grant's sources to its router only
A rule naming the route also linked a router that is a source to all others
serving it. SaaS does not (via-grant-v81).

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby 5642d976b2 policy, state: correct MatchersForNode's doc
It filters routes. Peers come from BuildPeerMap.

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby 5be981ca95 servertest: replay the member-to-subnet routes capture
Same shape as #3626: the router is in no rule.

Updates #3626
2026-11-03 15:53:04 +01:00
Kristoffer Dalby aeceef9cce policy/v2: anonymize a public endpoint in older captures
Recordings since hold only documentation addresses.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby 52d6dea601 hscontrol: drop user profiles no node in a capture uses
A client keeps every profile it was sent; these were never SaaS state.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby ec2e76e7cd servertest: replay ACL captures with auto-approved routes
Only the policy approves their routes, so broken approval fails the replay.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby 7a47710d64 servertest: keep filter sources that name no peer
They stopped a replay before any comparison; a side lacking one still differs.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby db0b6c9d5d policy/v2: hold source form skips to the same addresses
A listed check hides any difference, so both filter paths must first match as ranges.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby c59e19be21 policy/v2: check that captures agree on routes
Parts of a capture are read seconds apart, so an approval can split them.
Also refuses tool failures, lost nodes, unknown peers, lagging viewers and approvals that never landed.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby 4671143b8f policy/v2: record again captures split by a route approval
tscap read approved_routes before the netmaps, and five captures lost a node.
Contiguous new addresses make SaaS and headscale source forms differ, so those checks are listed.
Also moved by recording now: capver 131 to 142, default-auto-update, self RoutableIPs.
2026-11-03 15:52:58 +01:00
Kristoffer Dalby 8b41925a1f nix: support systemctl reload in the headscale service
Fixes #3027

Claude-Session: https://claude.ai/code/session_01RHEZM1qJ2hgndiXio7P56h
2026-11-03 15:52:52 +01:00
Kristoffer Dalby c7daf6d923 .github: regenerate integration test workflow
Fixes #3027

Claude-Session: https://claude.ai/code/session_01RHEZM1qJ2hgndiXio7P56h
2026-11-03 15:52:52 +01:00
Kristoffer Dalby 021f0294f8 integration: test TLS certificate reload on SIGHUP
Fixes #3027

Claude-Session: https://claude.ai/code/session_01RHEZM1qJ2hgndiXio7P56h
2026-11-03 15:52:52 +01:00
Kristoffer Dalby b3ffec4269 hscontrol: reload manual TLS certificate on SIGHUP
Files that fail to load or are not valid now keep the previous certificate.

Fixes #3027

Claude-Session: https://claude.ai/code/session_01RHEZM1qJ2hgndiXio7P56h
2026-11-03 15:52:52 +01:00
Kristoffer Dalby 7bd88aee79 .github: exempt milestoned issues from stale bot, stale after 45 days (#3652)
Milestone means triaged. Close message said 14 days; config is 7.
2026-11-03 15:52:47 +01:00
github-actions[bot] 2456ba1f6b Update VOUCHED list (#3651)
https://github.com/juanfont/headscale/issues/3650#issuecomment-6601738938

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-11-03 13:15:31 +00:00
github-actions[bot] 152c7f262b Update VOUCHED list
https://github.com/juanfont/headscale/issues/3643#issuecomment-6581488242
2026-11-03 13:51:44 +01:00
Igor Serganov d0e10f2d46 util: read .json files as HuJSON in UnmarshalByExt (#3634)
dns.extra_records_path and derp.paths files ending in .json were decoded
with encoding/json, so a comment or trailing comma in otherwise valid
JSON made headscale refuse to load the file. Policy files already accept
HuJSON regardless of extension. HuJSON is a superset of JSON, so
standardizing .json input the same way as .hujson keeps every valid JSON
file decoding exactly as before.

Add tests for comments and trailing commas in extra records, in generic
.json decoding and in ACL policies, and for comment markers inside
string values.

Fixes #3627
2026-11-02 20:35:24 +01:00
Igor Serganov c6723fa103 state: keep collision-bumped GivenName within the DNS label limit (#3635)
`dnsname.SanitizeHostname` truncates a hostname to 63 characters. When
that label was already taken, resolveGivenName appended "-N" to the
full base, producing a 65+ character GivenName. Such a name is not a
valid DNS label, so the node cannot be rendered into its own netmap
or any peer's, and on PostgreSQL the varchar(63) given_name column
rejects the write.

Trim the base so base plus suffix fits in a DNS label and strip a
hyphen left at the cut. isAutoDerivedGivenName now compares against
the same bump, so a trimmed name still follows hostname changes
instead of being treated as an admin rename.
2026-11-02 20:28:28 +01:00
Michael Lopez 42a97be25f state: validate the new name and refuse OIDC users in RenameUser (#3642)
State.RenameUser went through UpdateUser and skipped the checks
db.RenameUser applies, so the API could rename an OIDC user, whose
name the identity provider owns, or rename any user to an invalid
name. Apply both checks in the update function and map the username
validation errors to 400.
2026-11-02 20:22:23 +01:00
Kristoffer Dalby 697eff7474 CHANGELOG: enumerate policy, lifetime and API compatibility breaks 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 4cb74dfe04 policy, servertest: derive exit routes from allowed public destinations
A public IP permits exit defaults; private-only rules do not. Replay six
accepted and two refused SaaS captures with range-equivalence checks,
preserving provenance and the literal-default-route ACL validation gap.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby f453502ef1 hscontrol: name policy, registration and map delivery decisions 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 9cc0d01026 hscontrol: preserve stale GC timer coverage after ephemeral ownership changes 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 4a00488869 mapper: read tracked peers under workMu in batcher tests
A full map replaces the set with Clear then Store; an unlocked read
between them failed TestOlderInitialMapLeavesNewerStreamsPeers.
2026-11-02 19:15:40 +01:00