21 Commits

Author SHA1 Message Date
Kristoffer Dalby
bceac495f9 policy: add NodeNeedsPeerRecompute predicate
Reports whether a node's online/offline transition forces peers to recompute their netmap. True for subnet routers, relay targets (tailscale.com/cap/relay), and via targets; false otherwise.

The relay-target IP set and via-target tag set are precompiled from the grants in updateLocked, alongside the existing filter, so the per-node check is a cheap set lookup. Keyed on the node itself, so an ordinary node in a tailnet that uses relay or via for other nodes is still classified as not needing a recompute.

Updates #3293
2026-06-03 14:51:57 +02:00
Kristoffer Dalby
4cca63155d all: apply godoc [Name] link conventions across comments
Every Go-identifier reference in // and /* */ comments now uses
godoc's [Name] linking syntax so pkg.go.dev and `go doc` render
them as clickable cross-references. No behaviour change.

Pattern applied across the tree:
  In-package         [Foo], [Foo.Bar]
  Cross-package      [pkg.Foo], [pkg.Foo.Bar]
  Stdlib             [netip.Prefix], [errors.Is], [context.Context]
  Tailscale          [tailcfg.MapResponse], [tailcfg.Node.CapMap],
                     [tailcfg.NodeAttrSuggestExitNode]

Skip rules:
  - File:line refs left as plain text
  - HuJSON wire keys inside backtick raw strings untouched
  - ACL/policy syntax tokens (tag:foo, autogroup:self, ...) not Go
    symbols, left as plain text
  - JSON/OIDC wire keys, gorm tags, RFC IPv6 placeholders, markdown
    link tags, decorative dividers — all left as-is
2026-05-19 09:55:22 +02:00
Kristoffer Dalby
a4f05b0962 policy/v2: parse, validate, and compile nodeAttrs
ACL policies now accept a top-level nodeAttrs block. Each entry hands
a list of tailcfg node capabilities to every node matching target.
Accepted target forms are the same as acls.src and grants.src: users,
groups, tags, hosts, prefixes, autogroup:member, autogroup:tagged,
and *. autogroup:self, autogroup:internet, and autogroup:danger-all
are rejected at validate time because none describes a stable
identity set a node-level attribute can attach to.

NodeAttrGrant carries Targets, Attrs, and IPPool. IPPool is parsed
but rejected at validate time -- the allocator that consumes it is
not yet implemented. nodeAttrUnsupportedCaps lists caps SaaS accepts
that headscale cannot act on (funnel today) and rejects them with a
tracking-issue link in the error.

compileNodeAttrs resolves each entry's targets, then maps every
targeted node to a tailcfg.NodeCapMap of the entry's attrs. Per-node
IPs are cached once per call so the inner attr loop is O(grants)
instead of O(grants * nodes) IP allocations.

PolicyManager grows NodeCapMap (per-node), NodeCapMaps (snapshot for
batched callers), and NodesWithChangedCapMap (drain buffer for the
self-broadcast diff). refreshNodeAttrsLocked appends to the drain
rather than overwriting so a SetUsers/SetNodes between SetPolicy and
the drain cannot lose the policy-reload diff.
2026-05-13 14:22:30 +02:00
Kristoffer Dalby
8358017dcf policy/v2,state,mapper: implement per-viewer via route steering
Via grants steer routes to specific nodes per viewer. Until now,
all clients saw the same routes for each peer because route
assembly was viewer-independent. This implements per-viewer route
visibility so that via-designated peers serve routes only to
matching viewers, while non-designated peers have those routes
withdrawn.

Add ViaRouteResult type (Include/Exclude prefix lists) and
ViaRoutesForPeer to the PolicyManager interface. The v2
implementation iterates via grants, resolves sources against the
viewer, matches destinations against the peer's advertised routes
(both subnet and exit), and categorizes prefixes by whether the
peer has the via tag.

Add RoutesForPeer to State which composes global primary election,
via Include/Exclude filtering, exit routes, and ACL reduction.
When no via grants exist, it falls back to existing behavior.

Update the mapper to call RoutesForPeer per-peer instead of using
a single route function for all peers. The route function now
returns all routes (subnet + exit), and TailNode filters exit
routes out of the PrimaryRoutes field for HA tracking.

Updates #2180
2026-04-01 14:10:42 +01:00
Kristoffer Dalby
7bab8da366 state, policy, noise: implement SSH check period auto-approval
Add SSH check period tracking so that recently authenticated users
are auto-approved without requiring manual intervention each time.

Introduce SSHCheckPeriod type with validation (min 1m, max 168h,
"always" for every request) and encode the compiled check period
as URL query parameters in the HoldAndDelegate URL.

The SSHActionHandler checks recorded auth times before creating a
new HoldAndDelegate flow. Auth timestamps are stored in-memory:
- Default period (no explicit checkPeriod): auth covers any
  destination, keyed by source node with Dst=0 sentinel
- Explicit period: auth covers only that specific destination,
  keyed by (source, destination) pair

Auth times are cleared on policy changes.

Updates #1850
2026-02-25 21:28:05 +01:00
Kristoffer Dalby
107c2f2f70 policy, noise: implement SSH check action
Implement the SSH "check" action which requires additional
verification before allowing SSH access. The policy compiler generates
a HoldAndDelegate URL that the Tailscale client calls back to
headscale. The SSHActionHandler creates an auth session and waits for
approval via the generalised auth flow.

Sort check (HoldAndDelegate) rules before accept rules to match
Tailscale's first-match-wins evaluation order.

Updates #1850
2026-02-25 21:28:05 +01:00
Kristoffer Dalby
ce580f8245
all: fix golangci-lint issues (#3064) 2026-02-06 21:45:32 +01:00
Kristoffer Dalby
22ee2bfc9c
tags: process tags on registration, simplify policy (#2931)
This PR investigates, adds tests and aims to correctly implement Tailscale's model for how Tags should be accepted, assigned and used to identify nodes in the Tailscale access and ownership model.

When evaluating in Headscale's policy, Tags are now only checked against a nodes "tags" list, which defines the source of truth for all tags for a given node. This simplifies the code for dealing with tags greatly, and should help us have less access bugs related to nodes belonging to tags or users.

A node can either be owned by a user, or a tag.

Next, to ensure the tags list on the node is correctly implemented, we first add tests for every registration scenario and combination of user, pre auth key and pre auth key with tags with the same registration expectation as observed by trying them all with the Tailscale control server. This should ensure that we implement the correct behaviour and that it does not change or break over time.

Lastly, the missing parts of the auth has been added, or changed in the cases where it was wrong. This has in large parts allowed us to delete and simplify a lot of code.
Now, tags can only be changed when a node authenticates or if set via the CLI/API. Tags can only be fully overwritten/replaced and any use of either auth or CLI will replace the current set if different.

A user owned device can be converted to a tagged device, but it cannot be changed back. A tagged device can never remove the last tag either, it has to have a minimum of one.
2025-12-08 18:51:07 +01:00
Kristoffer Dalby
2bf1200483
policy: fix autogroup:self propagation and optimize cache invalidation (#2807) 2025-10-23 17:57:41 +02:00
Vitalij Dovhanyc
c2a58a304d
feat: add autogroup:self (#2789) 2025-10-16 12:59:52 +02:00
Kristoffer Dalby
c6d7b512bd
integration: replace time.Sleep with assert.EventuallyWithT (#2680) 2025-07-10 23:38:55 +02:00
Kristoffer Dalby
73023c2ec3 all: use immutable node view in read path
This commit changes most of our (*)types.Node to
types.NodeView, which is a readonly version of the
underlying node ensuring that there is no mutations
happening in the read path.

Based on the migration, there didnt seem to be any, but the
idea here is to prevent it in the future and simplify other
new implementations.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-07-07 21:28:59 +01:00
Kristoffer Dalby
a52f1df180
policy: remove v1 code (#2600)
* policy: remove v1 code

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* db: update test with v1 removal

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* integration: start moving to v2 policy

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: add ssh unmarshal tests

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* changelog: add entry

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: remove v1 comment

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* integration: remove comment out case

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* cleanup skipv1

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: remove v1 prefix workaround

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: add all node ips if prefix/host is ts ip

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-05-20 13:57:26 +02:00
Kristoffer Dalby
45e38cb080
policy: reduce routes sent to peers based on packetfilter (#2561)
* notifier: use convenience funcs

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: reduce routes based on policy

Fixes #2365

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* hsic: more helper methods

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: more test cases

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* integration: add route with filter acl integration test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* integration: correct route reduce test, now failing

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* mapper: compare peer routes against node

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* hs: more output to debug strings

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* types/node: slice.ContainsFunc

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy: more reduce route test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* changelog: add entry for route filter

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-05-04 21:52:47 +02:00
aergus-tng
4651d06fa8
Make matchers part of the Policy interface (#2514)
* Make matchers part of the Policy interface

* Prevent race condition between rules and matchers

* Test also matchers in tests for Policy.Filter

* Compute `filterChanged` in v2 policy correctly

* Fix nil vs. empty list issue in v2 policy test

* policy/v2: always clear ssh map

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
Co-authored-by: Aras Ergus <aras.ergus@tngtech.com>
Co-authored-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-05-01 07:06:30 +02:00
Kristoffer Dalby
2b38f7bef7
policy/v2: make default (#2546)
* policy/v2: make default

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* integration: do not run v1 tests

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* policy/v2: fix potential nil pointers

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* mapper: fix test failures in v2

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-04-29 16:27:41 +02:00
Kristoffer Dalby
87326f5c4f
Experimental implementation of Policy v2 (#2214)
* utility iterator for ipset

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* split policy -> policy and v1

This commit split out the common policy logic and policy implementation
into separate packages.

policy contains functions that are independent of the policy implementation,
this typically means logic that works on tailcfg types and generic formats.
In addition, it defines the PolicyManager interface which the v1 implements.

v1 is a subpackage which implements the PolicyManager using the "original"
policy implementation.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use polivyv1 definitions in integration tests

These can be marshalled back into JSON, which the
new format might not be able to.

Also, just dont change it all to JSON strings for now.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* formatter: breaks lines

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove compareprefix, use tsaddr version

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove getacl test, add back autoapprover

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* use policy manager tag handling

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* rename display helper for user

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* introduce policy v2 package

policy v2 is built from the ground up to be stricter
and follow the same pattern for all types of resolvers.

TODO introduce
aliass
resolver

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* wire up policyv2 in integration testing

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* split policy v2 tests into seperate workflow to work around github limit

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add policy manager output to /debug

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update changelog

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-03-10 16:20:29 +01:00
Kristoffer Dalby
7891378f57
Redo route code (#2422)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2025-02-26 16:22:55 +01:00
Kristoffer Dalby
770f3dcb93
fix tags not resolving to username if email is present (#2309)
* ensure valid tags is populated on user gets too

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* ensure forced tags are added

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* remove unused envvar in test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* debug log auth/unauth tags in policy man

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* defer shutdown in tags test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add tag test with groups

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add email, display name, picture to create user

Updates #2166

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add ability to set display and email to cli

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* add email to test users in integration

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix issue where tags were only assigned to email, not username

Fixes #2300
Fixes #2307

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* expand principles to correct login name

and if fix an issue where nodeip principles might not expand to all
relevant IPs instead of taking the first in a prefix.

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix ssh unit test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* update cli and oauth tests for users with email

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* index by test email

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

* fix last test

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>

---------

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-19 13:10:10 +01:00
Kristoffer Dalby
ec8729b772
fix sighup issue with empty acl (#2296)
Fixes #2291

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-12-16 07:48:19 +01:00
Kristoffer Dalby
f7b0cbbbea
wrap policy in policy manager interface (#2255)
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
2024-11-26 15:16:06 +01:00