Commit Graph
40 Commits
Author SHA1 Message Date
Kristoffer Dalby f453502ef1 hscontrol: name policy, registration and map delivery decisions 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 4e946edb08 api/v2: enforce and document OAuth tag permissions
Retagging requires all current and requested tags; disallowed tags return
400. Token responses list tags only when the token may assign them.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 7d94d189fa api/v2: send empty route lists under fields=all 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 66db32dde0 api/v2: report tagged device creators and join keys
Use the join-key creator as the device's user without changing ownership.
Omit oauthClientId when an OAuth token's source client is not recorded.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby a08daa91fe api/v2: list token-made auth keys to a token without all=true
Keys an OAuth token makes are marked so. No listing holds an invalid
key.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby fe260f5adf api/v2: show userId on admin-made tagged keys
An admin key's user is recorded as the creator of the tagged keys it
makes. A tagged key re-registers a machine by its tags, whoever made it.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 6474ac6dd3 api/v2: return a device name as its FQDN 2026-11-02 19:15:40 +01:00
Kristoffer Dalby 17cbea0f6a api/v2: ignore unknown fields and reject unknown key types
Match SaaS request decoding while refusing unsupported keyType values.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 9979be140e db: keep at most 1000 access tokens per OAuth client
Minting needs only the client secret, whatever its scopes, and
added one row per mint. Each mint now ends tokens past the newest 1000.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 408b9b7797 api/v2: check the createKey scope before reading the body
createKey has no static scope, so any token got its body parsed
and validated before the handler's scope check.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 0bf5b0f0af api/v2: limit request bodies and redact schema-validation details
Bound schema, raw, and token-form bodies before parsing. Omit submitted
values from schema errors while preserving policy reasons and test labels.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 77005fac35 api/v2: keep a tagged all-scope client's tokens inside its tags
Only an untagged all-scope client may mint tokens for any tag, so a
tagged token may not create one.
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 d8c482e4b3 db, api/v1: revoke rather than delete an ephemeral pre-auth key in use
The v1 list now hides revoked keys, so keys revoked through v2 also drop
out of `headscale preauthkeys list`.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 6fe62dec62 state: auto-approve on registration only when the node key rotates
Registration runs the approval itself, once the policy manager knows the
node's owner, for a new node key or a change to the announced routes.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby 6dd0656f17 api, policy/v2: refuse route prefixes with host bits
Routes are matched as written, so such a prefix never equals the one a
client announces. The approval APIs answer 400 and autoApprovers refuses
it at load and write, with Tailscale's text.
2026-11-02 19:15:40 +01:00
Kristoffer Dalby f2df44295a state, db: validate and store the current policy before installing it
Reject empty input. Validate without changing live state, then store and
install under one lock; prune old rows only with a successful write.
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
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 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 a63ee3e032 api/v2: add the acl/validate endpoint
Checks a policy as a write would, without storing it. As on Tailscale,
a refused policy answers 200 with its reason; only an empty body is 400.
Terraform provider 0.29.3 calls it at plan time.
2026-10-29 11:04:05 +01: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 60d42808b8 state: keep node removal apart from policy refresh in DeleteNode
Peers get the deletion as an explicit PeersRemoved change, independent of
their sent-peers tracking.

Updates tailscale/tailscale#15660
2026-09-30 19:03:13 +02:00
Kristoffer Dalby 21f6e46fb8 state: refresh policy nodes inside the peer map build
One peer build per tag/user/IP/route write; callers detect policy moves
via NodesGeneration. Per-node caches only store results for the node
pm holds, so a mapper reading mid-build cannot pin a stale filter.
2026-09-30 17:21:02 +02:00
Kristoffer Dalby 393dd3e2d9 db: store all credentials in one SHA-256-hashed table
API keys, pre-auth keys and OAuth clients/tokens share one table and verify
path. Secrets carry 256 bits of crypto/rand entropy, so a SHA-256 digest
needs no stretching; bcrypt/argon2id rows rehash on use until 0.32.
2026-09-26 00:33:12 +02:00
MsfPablo d60bac5c79 db: reject unknown OAuth client scopes at creation (#3424)
Unknown scopes grant nothing, so clients were silently under-privileged.
Report every invalid scope at once.

Fixes #3406
2026-09-25 21:24:37 +02:00
Kristoffer Dalby eebeab3c1e docs: document joining nodes with an OAuth client
Prefix swap, baseURL, every attribute; examples for tailscale up,
container, tsnet, GitHub Action.
2026-09-25 17:09:19 +02:00
Kristoffer Dalby fc6a16fdfc state: preserve committed node deletion changes
Updates #3410
2026-09-09 18:18:17 +02:00
Kristoffer Dalby 2c76d5c5b7 all: apply golangci-lint autofixes
Go 1.27 allows setting promoted fields of embedded structs directly in
composite literals, so gorm.Model wrappers go away. Plus strings.Cut,
errors.AsType, reflect.TypeAssert and one gofumpt nit.
2026-08-25 21:59:00 +02:00
Kristoffer Dalby af46fe8867 hscontrol, integration: test OAuth scope enforcement and the oauth-clients CLI
Add scope-enforcement coverage and integration tests for the
oauth-clients CLI across the test matrix.
2026-06-26 09:18:06 +02:00
Kristoffer Dalby 84715e976c api/v2: add OAuth client-credentials auth and scope enforcement 2026-06-26 09:18:06 +02:00
Kristoffer Dalby 83d3828ba1 api/v2: simplify endpoint comments 2026-06-24 18:41:06 +02:00
Kristoffer Dalby 475a5ead0c api/v2: add user get and list (Tailscale compat)
Backs the tailscale_user and tailscale_users data sources. getUser maps
gorm/ErrUserNotFound to 404; unmodelled fields are honest constants,
deviceCount/lastSeen/currentlyConnected aggregate the user's nodes.
2026-06-24 18:41:06 +02:00
Kristoffer Dalby e2e9fe3081 api/v2: add read-only tailnet settings endpoint
Report the settings the k8s operator reads, computed from config;
writes return 501.
2026-06-21 04:17:03 +02:00
Kristoffer Dalby 3cddc07026 api/v2: add Tailscale-compatible ACL endpoint
Serve and replace the policy as HuJSON with content-addressed ETags
and If-Match preconditions; writes are rejected in file mode.
2026-06-21 04:17:03 +02:00
Kristoffer Dalby b3472bb8dd api/v2: add Tailscale-compatible devices API
Map the Tailscale device surface — get, list, delete, set name, tags,
key expiry, routes, and authorization — onto Headscale nodes.
2026-06-21 04:17:03 +02:00
Kristoffer Dalby 6b413fe237 api/v2: soft-revoke auth keys with a configurable collector
Tailscale's keys API has no separate expire verb: DELETE is the revoke.
Map it to a soft revoke so the key stays retrievable as invalid afterwards
instead of vanishing, matching the SDK and Terraform's expectations.

Add a revoked timestamp to pre-auth keys (migration plus schema), mark a
key invalid once revoked, and have the keys DELETE handler stamp it rather
than destroy the row. A background collector reaps revoked keys after a
configurable retention window (preauth_keys.revoked_retention, default
168h), so the table does not grow without bound.
2026-06-21 04:17:03 +02:00
Kristoffer Dalby fe08d43978 api/v2: add Tailscale-compatible keys API
Add the v2 foundation — Basic/Bearer auth, scope scaffolding, the
tailnet check, and the Tailscale error shape — plus the auth-key
endpoints mapped onto Headscale pre-auth keys.
2026-06-21 04:17:03 +02:00
Kristoffer Dalby 96d736ec23 api/v1: build responses from view accessors, not AsStruct
Serialize node, user and pre-auth-key responses through the NodeView,
UserView and PreAuthKeyView accessors instead of AsStruct(), which clones
the whole record on every read. Document the convention in AGENTS.md.
2026-06-21 04:17:03 +02:00
Kristoffer Dalby c9bbb5bdec api/v1: add code-first Huma implementation of the v1 API
Reimplement the v1 API as a code-first Huma service in hscontrol/api/v1,
a thin adapter over the state layer. Huma emits the OpenAPI 3.1 document
(openapi/v1/headscale.yaml) from the Go operation and type definitions;
cmd/gen-openapi writes it and the 3.0.3 downgrade the client is generated
from. Responses reproduce the protojson wire contract (string-encoded
64-bit IDs, all fields emitted, RFC3339/null timestamps); errors map to
the correct HTTP status (404/400/409, 500 for server faults) via mapError.

Serve it on the chi router at /api/v1 behind the existing API-key
middleware, and point /swagger at the emitted spec.
2026-06-19 15:21:00 +02:00