Commit Graph
316 Commits
Author SHA1 Message Date
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
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
Kristoffer Dalby c4ce3f7d14 state: revalidate pre-auth key reuse when registration applies 2026-10-07 14:03:54 +02:00
Kristoffer Dalby 357f34a778 state: send a node's DNS config with its own refresh
Drained at dispatch for nodeAttrs changes from any policy, user or node
update, and for hostname/OS changes feeding NextDNS device metadata.
2026-09-30 19:03:13 +02:00