mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	* 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>
		
			
				
	
	
		
			111 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			111 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package policy
 | |
| 
 | |
| import (
 | |
| 	"github.com/juanfont/headscale/hscontrol/policy/matcher"
 | |
| 	"net/netip"
 | |
| 	"slices"
 | |
| 
 | |
| 	"github.com/juanfont/headscale/hscontrol/types"
 | |
| 	"github.com/juanfont/headscale/hscontrol/util"
 | |
| 	"github.com/samber/lo"
 | |
| 	"tailscale.com/net/tsaddr"
 | |
| 	"tailscale.com/tailcfg"
 | |
| )
 | |
| 
 | |
| // FilterNodesByACL returns the list of peers authorized to be accessed from a given node.
 | |
| func FilterNodesByACL(
 | |
| 	node *types.Node,
 | |
| 	nodes types.Nodes,
 | |
| 	matchers []matcher.Match,
 | |
| ) types.Nodes {
 | |
| 	var result types.Nodes
 | |
| 
 | |
| 	for index, peer := range nodes {
 | |
| 		if peer.ID == node.ID {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if node.CanAccess(matchers, nodes[index]) || peer.CanAccess(matchers, node) {
 | |
| 			result = append(result, peer)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // 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.Node, 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 != nil {
 | |
| 				if len(node.Hostinfo.RoutableIPs) > 0 {
 | |
| 					for _, routableIP := range node.Hostinfo.RoutableIPs {
 | |
| 						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.
 | |
| func AutoApproveRoutes(pm PolicyManager, node *types.Node) bool {
 | |
| 	if pm == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	var newApproved []netip.Prefix
 | |
| 	for _, route := range node.AnnouncedRoutes() {
 | |
| 		if pm.NodeCanApproveRoute(node, 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
 | |
| }
 |