package policy import ( "net/netip" "time" "github.com/juanfont/headscale/hscontrol/policy/matcher" policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2" "github.com/juanfont/headscale/hscontrol/types" "tailscale.com/tailcfg" "tailscale.com/types/views" ) type PolicyManager interface { // Filter returns the current filter rules for the entire tailnet and the associated matchers. Filter() ([]tailcfg.FilterRule, []matcher.Match) // FilterForNode returns filter rules for a specific node, handling autogroup:self FilterForNode(node types.NodeView) ([]tailcfg.FilterRule, error) // MatchersForNode returns matchers for peer relationship determination (unreduced) MatchersForNode(node types.NodeView) ([]matcher.Match, error) // BuildPeerMap constructs peer relationship maps for the given nodes BuildPeerMap(nodes views.Slice[types.NodeView]) map[types.NodeID][]types.NodeID SSHPolicy(baseURL string, node types.NodeView) (*tailcfg.SSHPolicy, error) // SSHCheckParams resolves the SSH check period for src logging in to // dst as localUser from the current policy, avoiding trust of // client-provided URL params. SSHCheckParams(srcNodeID, dstNodeID types.NodeID, localUser string) (time.Duration, bool) SetPolicy(pol []byte) (bool, error) // SetUsers replaces the user list. policyChanged reports whether clients // need a policy refresh; peerMapChanged reports whether user-derived peer // adjacency may have changed. Both are false when the list is unchanged. SetUsers(users []types.User) (policyChanged, peerMapChanged bool, err error) SetNodes(nodes views.Slice[types.NodeView]) (bool, error) // NodesGeneration counts SetNodes calls that reported a change; see // [policyv2.PolicyManager.NodesGeneration]. NodesGeneration() uint64 // NodeCanHaveTag reports whether the given node can have the given tag. NodeCanHaveTag(node types.NodeView, tag string) bool // UserCanHaveTag reports whether the given user owns the given tag, i.e. // is listed (directly or via a group) in the tag's tagOwners. This is the // user half of NodeCanHaveTag, used to authorise re-auth tag changes // against the authenticating user rather than the node's stale ownership. UserCanHaveTag(user types.UserView, tag string) bool // TagExists reports whether the given tag is defined in the policy. TagExists(tag string) bool // TagOwnedByTags reports whether a credential holding ownerTags may apply // tag: true if tag is one of ownerTags, or tag's tag-to-tag ownership chain // transitively includes one of ownerTags. Authorises the tags an OAuth // access token may set on the auth keys it mints. TagOwnedByTags(tag string, ownerTags []string) bool // NodeCanApproveRoute reports whether the given node can approve the given route. NodeCanApproveRoute(node types.NodeView, route netip.Prefix) bool // NodeNeedsPeerRecompute reports whether peers must recompute their // netmap when the node's online state changes. True for subnet // routers, relay targets (tailscale.com/cap/relay), and via targets; // false for ordinary nodes, which only need a lightweight online or // offline peer patch. [State.Connect] and [State.Disconnect] use it to // avoid a tailnet-wide recompute on every ordinary reconnect. NodeNeedsPeerRecompute(node types.NodeView) bool // ViaRoutesForPeer computes via grant effects for a viewer-peer pair. // It returns which routes should be included (peer is via-designated for viewer) // and excluded (steered to a different peer). When no via grants apply, // both fields are empty and the caller falls back to existing behavior. ViaRoutesForPeer(viewer, peer types.NodeView) types.ViaRouteResult // NodeCapMap returns compiled policy capabilities for the given node, // or nil when none target it. The map is a defensive clone; its values // are shared and read-only. It describes the node's own capabilities, // not a per-viewer view. NodeCapMap(id types.NodeID) tailcfg.NodeCapMap // NodeCapMaps snapshots every node's policy CapMap. The outer map is // caller-owned; inner maps are shared with the manager and read-only. // NodeCapMapsForPeers limits copying when fewer peers are requested. NodeCapMaps() map[types.NodeID]tailcfg.NodeCapMap // NodeCapMapsForPeers snapshots requested peer caps under one lock. It may // include unselected entries when copying the whole compiled table is less work. // The outer map is caller-owned; inner maps are shared and read-only. // Empty selections or no compiled policy capabilities return nil; missing IDs read nil. NodeCapMapsForPeers(peers views.Slice[types.NodeView]) map[types.NodeID]tailcfg.NodeCapMap // NodesWithChangedCapMap returns the IDs of nodes whose nodeAttrs // CapMap shifted during recent updateLocked calls. The buffer // drains on read; callers consume it once per update cycle to // decide which nodes need a self-targeted MapResponse. // refreshNodeAttrsLocked appends to the buffer rather than // overwriting, so a SetUsers/SetNodes between SetPolicy and the // drain cannot lose the policy-reload diff. NodesWithChangedCapMap() []types.NodeID Version() int DebugString() string } // NewPolicyManager returns a new [PolicyManager]. func NewPolicyManager(pol []byte, users []types.User, nodes views.Slice[types.NodeView]) (PolicyManager, error) { var ( polMan PolicyManager err error ) polMan, err = policyv2.NewPolicyManager(pol, users, nodes) if err != nil { return nil, err } return polMan, err } // PolicyManagersForTest returns all available [PolicyManager] implementations to // be used in tests to validate them in tests that try to determine that they // behave the same. func PolicyManagersForTest(pol []byte, users []types.User, nodes views.Slice[types.NodeView]) ([]PolicyManager, error) { var polMans []PolicyManager for _, pmf := range PolicyManagerFuncsForTest(pol) { pm, err := pmf(users, nodes) if err != nil { return nil, err } polMans = append(polMans, pm) } return polMans, nil } func PolicyManagerFuncsForTest(pol []byte) []func([]types.User, views.Slice[types.NodeView]) (PolicyManager, error) { polmanFuncs := make([]func([]types.User, views.Slice[types.NodeView]) (PolicyManager, error), 0, 1) polmanFuncs = append(polmanFuncs, func(u []types.User, n views.Slice[types.NodeView]) (PolicyManager, error) { return policyv2.NewPolicyManager(pol, u, n) }) return polmanFuncs }