package policyutil import ( "github.com/juanfont/headscale/hscontrol/types" "github.com/juanfont/headscale/hscontrol/util" "tailscale.com/tailcfg" ) // ReduceFilterRules takes a node and a set of global filter rules and removes all rules // and destinations that are not relevant to that particular node. // // IMPORTANT: This function is designed for global filters only. Per-node filters // (from autogroup:self policies) are already node-specific and should not be passed // to this function. Use PolicyManager.FilterForNode() instead, which handles both cases. 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 } } } } // Also check approved subnet routes - nodes should have access // to subnets they're approved to route traffic for. subnetRoutes := node.SubnetRoutes() for _, subnetRoute := range subnetRoutes { if expanded.OverlapsPrefix(subnetRoute) { 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 }