mirror of
https://github.com/juanfont/headscale.git
synced 2025-07-16 12:21:56 -04:00
159 lines
4.2 KiB
Go
159 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
|
|
}
|