mirror of
https://github.com/juanfont/headscale.git
synced 2025-07-16 20:31:57 -04:00
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>
160 lines
4.2 KiB
Go
160 lines
4.2 KiB
Go
package policy
|
|
|
|
import (
|
|
"net/netip"
|
|
"slices"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/policy/matcher"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
"github.com/samber/lo"
|
|
"tailscale.com/net/tsaddr"
|
|
"tailscale.com/tailcfg"
|
|
"tailscale.com/types/views"
|
|
)
|
|
|
|
// ReduceNodes returns the list of peers authorized to be accessed from a given node.
|
|
func ReduceNodes(
|
|
node types.NodeView,
|
|
nodes views.Slice[types.NodeView],
|
|
matchers []matcher.Match,
|
|
) views.Slice[types.NodeView] {
|
|
var result []types.NodeView
|
|
|
|
for _, peer := range nodes.All() {
|
|
if peer.ID() == node.ID() {
|
|
continue
|
|
}
|
|
|
|
if node.CanAccess(matchers, peer) || peer.CanAccess(matchers, node) {
|
|
result = append(result, peer)
|
|
}
|
|
}
|
|
|
|
return views.SliceOf(result)
|
|
}
|
|
|
|
// ReduceRoutes returns a reduced list of routes for a given node that it can access.
|
|
func ReduceRoutes(
|
|
node types.NodeView,
|
|
routes []netip.Prefix,
|
|
matchers []matcher.Match,
|
|
) []netip.Prefix {
|
|
var result []netip.Prefix
|
|
|
|
for _, route := range routes {
|
|
if node.CanAccessRoute(matchers, route) {
|
|
result = append(result, route)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// BuildPeerMap builds a map of all peers that can be accessed by each node.
|
|
func BuildPeerMap(
|
|
nodes views.Slice[types.NodeView],
|
|
matchers []matcher.Match,
|
|
) map[types.NodeID][]types.NodeView {
|
|
ret := make(map[types.NodeID][]types.NodeView, nodes.Len())
|
|
|
|
// Build the map of all peers according to the matchers.
|
|
// Compared to ReduceNodes, which builds the list per node, we end up with doing
|
|
// the full work for every node (On^2), while this will reduce the list as we see
|
|
// relationships while building the map, making it O(n^2/2) in the end, but with less work per node.
|
|
for i := range nodes.Len() {
|
|
for j := i + 1; j < nodes.Len(); j++ {
|
|
if nodes.At(i).ID() == nodes.At(j).ID() {
|
|
continue
|
|
}
|
|
|
|
if nodes.At(i).CanAccess(matchers, nodes.At(j)) || nodes.At(j).CanAccess(matchers, nodes.At(i)) {
|
|
ret[nodes.At(i).ID()] = append(ret[nodes.At(i).ID()], nodes.At(j))
|
|
ret[nodes.At(j).ID()] = append(ret[nodes.At(j).ID()], nodes.At(i))
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// ReduceFilterRules takes a node and a set of rules and removes all rules and destinations
|
|
// that are not relevant to that particular node.
|
|
func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcfg.FilterRule {
|
|
ret := []tailcfg.FilterRule{}
|
|
|
|
for _, rule := range rules {
|
|
// record if the rule is actually relevant for the given node.
|
|
var dests []tailcfg.NetPortRange
|
|
DEST_LOOP:
|
|
for _, dest := range rule.DstPorts {
|
|
expanded, err := util.ParseIPSet(dest.IP, nil)
|
|
// Fail closed, if we can't parse it, then we should not allow
|
|
// access.
|
|
if err != nil {
|
|
continue DEST_LOOP
|
|
}
|
|
|
|
if node.InIPSet(expanded) {
|
|
dests = append(dests, dest)
|
|
continue DEST_LOOP
|
|
}
|
|
|
|
// If the node exposes routes, ensure they are note removed
|
|
// when the filters are reduced.
|
|
if node.Hostinfo().Valid() {
|
|
routableIPs := node.Hostinfo().RoutableIPs()
|
|
if routableIPs.Len() > 0 {
|
|
for _, routableIP := range routableIPs.All() {
|
|
if expanded.OverlapsPrefix(routableIP) {
|
|
dests = append(dests, dest)
|
|
continue DEST_LOOP
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(dests) > 0 {
|
|
ret = append(ret, tailcfg.FilterRule{
|
|
SrcIPs: rule.SrcIPs,
|
|
DstPorts: dests,
|
|
IPProto: rule.IPProto,
|
|
})
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// AutoApproveRoutes approves any route that can be autoapproved from
|
|
// the nodes perspective according to the given policy.
|
|
// It reports true if any routes were approved.
|
|
// Note: This function now takes a pointer to the actual node to modify ApprovedRoutes
|
|
func AutoApproveRoutes(pm PolicyManager, node *types.Node) bool {
|
|
if pm == nil {
|
|
return false
|
|
}
|
|
nodeView := node.View()
|
|
var newApproved []netip.Prefix
|
|
for _, route := range nodeView.AnnouncedRoutes() {
|
|
if pm.NodeCanApproveRoute(nodeView, route) {
|
|
newApproved = append(newApproved, route)
|
|
}
|
|
}
|
|
if newApproved != nil {
|
|
newApproved = append(newApproved, node.ApprovedRoutes...)
|
|
tsaddr.SortPrefixes(newApproved)
|
|
newApproved = slices.Compact(newApproved)
|
|
newApproved = lo.Filter(newApproved, func(route netip.Prefix, index int) bool {
|
|
return route.IsValid()
|
|
})
|
|
node.ApprovedRoutes = newApproved
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|