Commit Graph
318 Commits
Author SHA1 Message Date
Kristoffer Dalby 0cbdf9ecd1 db, types: add a credentials column for keys a token made
No released version made keys from tokens, so existing rows stay false.
2026-10-28 07:15:55 +00:00
Kristoffer Dalby a0031cea03 types: accept only lowercase hex in auth IDs 2026-10-28 07:15:55 +00:00
Kristoffer Dalby 7e43ff529a oidc: approve SSH checks on the confirmation page, not in the callback
Require an explicit click before recording the SSH verdict.
Recheck source ownership on the callback, confirmation page, and POST.
2026-10-28 07:15:55 +00:00
Kristoffer Dalby 1d06c0b566 types: reject non-boolean values for boolean settings 2026-10-28 07:15:55 +00:00
Kristoffer Dalby b86f0042bb types: drop a stored OIDC email the login no longer vouches for 2026-10-28 07:15:55 +00:00
Kristoffer Dalby 372d3ba6d9 oidc: refuse logins whose subject the provider identifier would alter 2026-10-28 07:15:55 +00:00
Kristoffer Dalby b394cc16b8 change: do not echo a node's own report back to it
An endpoint or DERP report came back to the node as a self update;
SaaS sends it to the node's peers only.
2026-10-28 07:15:55 +00:00
Kristoffer Dalby 3f30945056 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-10-28 07:15:55 +00:00
Kristoffer Dalby ccf2250331 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-10-28 07:15:55 +00:00
Kristoffer Dalby 5ddc442ec3 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.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby e747f01b49 db, types: add a nodes column for the ephemeral flag
The migration fills it from the key each node references.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby df58b6d134 state: treat extra DNS record names as taken node names 2026-10-28 07:15:54 +00:00
Kristoffer Dalby 5219939170 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.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby 5aaa4e1d2b auth, state: keep announced routes out of pending registrations
Routes are the one long list; the first MapRequest reports them.
A relogin keeps the node's own routes until then.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby e4e2ea820b state: bound client reports of nodes loaded at startup
Rows stored before the bounds would still fan out unbounded endpoints.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby ebdb5c94fa types, noise: bound the Hostinfo and endpoints a client reports
Cut lists and hostname at decode, refuse >64 KiB of non-route Hostinfo.
The body limit counted wire bytes, not what the cache, NodeStore and
peers keep.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby 89647cac2f 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-10-28 07:15:54 +00:00
Kristoffer Dalby 464dba33e1 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-10-28 07:15:54 +00:00
Kristoffer Dalby cfd065c680 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.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby f192bf05b2 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.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby 664d3477f0 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-10-28 07:15:54 +00:00
Kristoffer Dalby 1c17be4dd8 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, which takes it back with a
new node key. Another user's key or login adds that user's own device.
2026-10-28 07:15:54 +00:00
Kristoffer Dalby 96520f367a state: apply map requests only under the node key that authorised them 2026-10-28 00:51:40 +00:00
Kristoffer Dalby 850c5d3fbb 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-10-28 00:51:40 +00:00
Kristoffer Dalby 9d5d397c1d 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.
2026-10-28 00:51:39 +00:00
Kristoffer Dalby 1dcd8ff4cd policy/matcher, types: compare a node's routes to matchers as ranges 2026-10-28 00:51:39 +00:00
Kristoffer Dalby 21fd9f1366 types, state: reduce a node's routes against the matchers once
Reuse matcher membership and approved subnets across the route list.
Preserve route order and HA co-router visibility.
2026-10-28 00:51:39 +00:00
Kristoffer Dalby 2d2bfe8fd2 types: derive subnet and exit routes in one pass
Emit each approved route once, in announcement order.
Skip derivation when no routes are approved.
2026-10-28 00:51:39 +00:00
Kristoffer Dalby 4d007eb5e9 state: skip route comparison for writes that leave routes alone
Reuse immutable route inputs when classifying NodeStore writes.
Only derive routes when an announcement or approval changes.
2026-10-28 00:51:39 +00:00
Kristoffer Dalby 4cafc80fbd state, policy/v2: stop treating announced-only routes as policy changes
Policy evaluation reads only announced-and-approved routes.
Keep publishing Hostinfo updates without rebuilding peer adjacency.
2026-10-28 00:51:39 +00:00
Kristoffer Dalby e26c0059f3 types: add when a node's run of control sessions began 2026-10-28 00:51:39 +00:00
Kristoffer Dalby 1864e7a284 hscontrol, types: strip trailing slashes from server_url 2026-10-28 00:51:39 +00:00
Kristoffer Dalby c827dc8fdc db: disable GORM's prepared statement cache on SQLite
database.gorm.prepare_stmt is now ignored, with a startup warning.
2026-10-28 00:51:39 +00:00
Kristoffer Dalby d11b876ea1 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-27 14:15:01 +00: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
Kristoffer Dalby dcc01dc2b0 types: move TLS config under tls with legacy fallback
tls.cert_path, tls.key_path and tls.acme.* replace the flat keys, which
still work. Warn on them only when they turn TLS on: NixOS always sets them.

Updates #2527
2026-10-18 20:53:18 +02:00
Kristoffer Dalby eef0d1fe47 dns: add acme-dns provider
_acme-challenge is CNAMEd once to an acme-dns registration, so headscale
needs no DNS credentials. Node certificates need one registration per
name; CNAME chains are followed and writes never evict a live value.

Updates #2137
2026-10-18 20:53:18 +02:00
Kristoffer Dalby f16d5e1857 noise: implement /machine/set-dns
A node may publish only its own _acme-challenge TXT value; any other
request is rejected before the provider is called. Answers once all
authoritative nameservers serve the value; 503 while shutting down.

Fixes #2137
Fixes #1921
Fixes #2527
2026-10-18 20:53:18 +02:00
Kristoffer Dalby 3f6e54cbd5 mapper: send CertDomains and https cap when certificates are enabled
Both gate tailscale cert and serve HTTPS on the client; httpsEnabled
follows, as in Tailscale. No certificate when an operator extra record
sits at or below the name: a wildcard would cover it.

Updates #2137
Updates #1921
2026-10-18 20:53:18 +02:00
Kristoffer Dalby 0efe4b7798 types: add dns.challenge and dns.certificates config
dns.challenge.provider builds a libdns provider at load, so configtest
catches typos. dns.certificates gates node certs; zone defaults to base_domain.

Updates #2137
2026-10-18 20:53:18 +02:00
Martin -nexus- Mlynář a8d6f5be81 state: send a relogin as a whole node unless only its keys changed
A PeerChange patch cannot clear a peer's Expired flag or carry the
Hostinfo the relogin stored.

Fixes #3531
2026-10-09 11:44:37 +02:00
LuoChen aaf4ccd8c9 util, types: parse unix_socket_permission strictly
Default "0o770" failed the base-8 parse and silently became 0700.
Invalid, unquoted or above-0777 values now fail config load.

Fixes #3529
2026-10-09 11:42:53 +02:00
Kristoffer Dalby 85bda7c2c9 types: drop unused Nodes.ContainsNodeKey 2026-10-07 22:55:03 +02:00
Kristoffer Dalby 52a7f8c89c mapper: send the logtail instruction on every full map
Full maps moved to buildFromChange and lost WithDebugConfig, so no
frame told clients to disable log upload. Enabled logtail sends nil.
2026-10-07 18:11:07 +02:00
Kristoffer Dalby 85587e2e29 change: drop untargeted pings when collapsing to a full
The ping ID in the URL is the only authentication on the answer, so
rescuing an untargeted ping for every node would let any node answer
for another. Rescue a ping only for the node it targets.
2026-10-07 15:25:25 +02:00
Kristoffer Dalby d4948da301 mapper: keep pings when a full update collapses pending changes
A full renders state at drain time but cannot carry a one-shot
PingRequest; queue each ping as a ping-only frame after the full.
2026-10-07 15:25:25 +02:00