Commit Graph
329 Commits
Author SHA1 Message Date
Kristoffer Dalby f786ae33cd state: send the whole node when a relogin removes a key expiry
A patch omits a zero expiry, so peers kept the old one and would cut the
node off when it passed.
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 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 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 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 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 060f145ac6 db, types: add a credentials column for keys a token made
No released version made keys from tokens, so existing rows stay false.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 87c9659b20 types: accept only lowercase hex in auth IDs 2026-11-02 19:15:40 +01:00
Kristoffer Dalby c8e97384d7 oidc: confirm SSH checks and display registration details
Approve SSH checks only after a click and recheck source ownership.
Show registration state and requested tags on the confirmation page.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby f2ecf949a7 types: reject non-boolean values for boolean settings 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 14baf55421 state, oidc: persist user changes and refresh nodes and clients
Write cleared user columns, discard OIDC email the provider no longer
vouches for, refresh NodeStore users, and notify clients from the callback.

Fixes #3471
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 499b58e77c oidc: refuse logins whose subject the provider identifier would alter 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 65a4f0d6fc state, change: notify clients only of effective node changes
Send endpoint and DERP reports only to peers. Suppress redundant online,
policy, and key updates on reconnect or unchanged-key login.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 27bed8f612 state, change: send a deleted node's removal only to its peers
The removal went to every node, also those the policy hid the node from.
The delete now returns the node's peers from the snapshot it replaces.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 14d752cf04 change: coalesce policy recomputes that carry an origin node
The recompute render ignores the origin and always carries the node's
own entry, so a burst of tag writes cost every node one per write.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby e99ad44064 db, state: keep the ephemeral flag a node was created with
The flag moves from the key to the node, so a login with another key no
longer changes it. A user claiming a tagged node still clears it. v1
nodes carry the flag.

Fixes #3640
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 59f9f78bdd db, types: add a nodes column for the ephemeral flag
The migration fills it from the key each node references.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 8b93e6a074 state: assign and persist unique node names in the NodeStore
Reserve extra DNS names, fit collision suffixes within 63 bytes, and repair
duplicate startup names in memory. Persist registration names without
losing a created node on rename failure; remove unused DB renaming.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 3038aa907a mapper: show an expired peer online, without endpoints or DERP
A connected expired node shows online to its peers. Neither its map
entry nor endpoint, DERP or disco patches carry the paths to reach it.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 99e8a9228a mapper: trim the Hostinfo sent to peers and to the node itself
As on Tailscale SaaS, the node's own entry keeps Hostname, RequestTags,
RoutableIPs and Services; peers get Hostname, OS, Services without
peerapi-dns-proxy, SSH host keys and AppConnector, and resend on those.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby bdfe6fc380 db, types: add a column for auto-approved routes
It starts empty: the current policy cannot tell which earlier approvals
were automatic, so all of them stay as an admin's.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 221a387b2b state: apply map requests only under the node key that authorised them 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 256a67e86b policy: offer exit routes only to viewers whose rules reach the internet
Every destination overlaps /0, so any rule offered a peer's exit routes.
They now need a rule to the internet, the test exit-node visibility
uses; via steering defers them to a regular grant only on that test.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 836d03dee2 policy/matcher: compare cached policy and node route ranges
Binary-search immutable policy ranges and normalize node routes once.
Large misses and self/via scans improve; small or early matches can slow,
and global policies pay extra normalization and allocation costs.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 71dc0c1c58 types, state: derive and reduce approved routes once
Derive subnet and exit routes together, once each in announcement order.
Reuse matcher membership while preserving HA co-router visibility.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 0e7515eab9 state, policy/v2: classify only effective route changes
Compare announced-and-approved routes only when immutable route inputs
change. Publish Hostinfo without unnecessary peer adjacency rebuilds.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 555caf7c2b types: add when a node's run of control sessions began 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 8e876eef32 hscontrol, types: strip trailing slashes from server_url 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 34653a1bd9 db: disable GORM's prepared statement cache on SQLite
database.gorm.prepare_stmt is now ignored, with a startup warning.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby c0925962c2 hscontrol: bound pending authentication without evicting sessions 2026-10-29 20:51:50 +01:00
arkie 8cf43952af types: emit SSH environment capability by default
Fixes #3559
2026-10-29 20:51:36 +01:00
Kristoffer Dalby 729464091e testcapture: refuse golden captures that name no tool commit
It walks every testdata folder, so a new folder is covered too. Captures
taken since tscap refuses such builds are listed until taken again.
2026-10-29 14:00:02 +01:00
Kristoffer Dalby b833be283c types, auth, noise: let one request wait on each auth session
One verdict per session, so one waiter: the newest. A displaced one
returns at once (register error, SSH re-delegate), not at session end.
A waiter keeps only the follow-up URL and the cached registration.

Fixes ANT-2026-NM50ZP4N
2026-10-29 14:00:02 +01:00
Kristoffer Dalby 5a4d366ce1 auth, state: keep only hostname, OS and tags in pending registrations
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
2026-10-29 14:00:02 +01:00
Kristoffer Dalby 2e563635d0 types, noise: bound the Hostinfo and endpoints a client reports
Keep the first 256 endpoints and 4096 routes; refuse longer hostnames,
too many tags, zoned endpoints and >64 KiB NetInfo or other Hostinfo.
The body limit counted wire bytes, not what NodeStore and peers keep.

Fixes ANT-2026-RQS7JAPP
Fixes ANT-2026-5B6KE87K
Fixes ANT-2026-KFCFFM34
2026-10-29 14:00:02 +01:00
Kristoffer Dalby 15a8c7584e hscontrol: refuse a key expiry that cannot be encoded
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
2026-10-29 14:00:02 +01:00
Kristoffer Dalby e3c1bfd68e mapper: skip peers whose key expiry cannot be encoded
json.Marshal refuses a year outside [0,9999], so one stored expiry
failed every map with that node. TailNode and key patches send UTC, the
builder drops what fails and the boot scan names the node.

Fixes ANT-2026-QMCAFJ1Q
2026-10-29 14:00:02 +01:00
Kristoffer Dalby e1de83cbdf state: give another user's key on a tagged machine a new device
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
2026-10-29 14:00:02 +01:00
Kristoffer Dalby 47d82023e7 state: never serve a single address inside the node ranges
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
2026-10-29 14:00:02 +01:00
Kristoffer Dalby 1e397e8cd5 testcapture: record multi-step captures and API error detail
Optional Steps, NodeIDs, error data and login state; v1 files unchanged.
Register log and raw map frame parsers; Write refuses auth keys and
login URLs.
2026-10-29 14:00:02 +01:00
Kristoffer Dalby 51b420865e state: own request memory before storing nodes
A reused RegisterRequest.Hostinfo was written and stored while NodeStore readers cloned it.
2026-10-23 18:23:53 +02:00
Kristoffer Dalby a5d5e8384a change: fold a tick's node deletions into one removal
Under delete churn each removal still rendered its own frame and split
the coalesced peer runs.
2026-10-23 18:23:53 +02:00
Kristoffer Dalby 7a2335dc2b mapper: render each tick's peer deltas as one response
One frame per change let a join storm overflow the session channel in a
single tick, and the batcher then dropped live clients as stale.
2026-10-23 18:23:53 +02:00
Michael LopezandClaude Fable 5.1 494e2a6808 db: quote postgres connection parameters (#3477)
* db: quote postgres connection parameters

The DSN was built by string concatenation, so a password containing a
space, quote or backslash truncated it or injected extra parameters.
Build it with libpq keyword/value quoting and keep the password out of
the startup log line.

* db: mark the DSN test fixture password as not a credential

gosec G101 flags the literal assigned to Pass in TestBuildPostgresDSN.
The value exists to exercise quoting of spaces, quotes and backslashes
and is not a secret.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* types, db: build the postgres DSN as a URL from PostgresConfig

Review feedback: move the DSN construction onto types.PostgresConfig as
DSN(), so the database layer calls cfg.Postgres.DSN() and logs
redacted.DSN(), and build it as a postgres:// URL with net/url instead
of hand-rolled libpq quoting. Unix socket directories travel as the
host query parameter; bare IPv6 literals are bracketed. Tests parse the
URL back and check each component rather than the encoded string.

* types: silence gosec on the whole DSN test fixtures

gosec reports G101 on the composite literal that holds a Pass value, not
on the Pass line itself, so the previous nolint did not cover it. Place
the directive before each fixture element.

* db: drop imports left unused by the merge with main

The merge of main reintroduced the slices and strconv imports in db.go,
but the postgres DSN is now built by PostgresConfig.DSN and nothing in
the file uses them, so the package failed to compile and every CI check
failed on typecheck.

Also link the CHANGELOG entry to the PR.

* db: restore tests and formatting lost in the rebase onto main

Resolving the rebase conflicts in db_test.go kept this branch's side and
dropped the credential tests main had added, including the
requireCredentialConstraints helper another test still calls, so the db
test package no longer compiled. Restore the file from main; this branch
does not change it.

The resolution also indented the package clause in db.go and removed the
blank line between the standard library and module imports.

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 22:36:04 +02:00
Kristoffer Dalby 5d15cc930d hscontrol: replace autocert with certmagic
Adds DNS-01 for headscale's own certificate through dns.challenge.provider,
with the same propagation check as node certificates. Storage format
changes: first start gets a new account and certificate.

Updates #2527
Updates #504
2026-10-18 20:53:18 +02:00