mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-05 13:09:25 +09:00
A user claims only the tags it owns directly, and a tag owns only the tags that list it, as on Tailscale SaaS. With no walk left, validate names an undefined owner once, with the tag that lists it.
144 lines
6.2 KiB
Go
144 lines
6.2 KiB
Go
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 tagOwners list one of
|
|
// them (one hop). 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
|
|
}
|