mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-11 03:27:20 +02:00
Exit nodes handle traffic via AllowedIPs/routing, not packet filter rules. Skip exit routes (0.0.0.0/0, ::/0) when checking RoutableIPs overlap in ReduceFilterRules, matching Tailscale SaaS behavior where exit nodes do not receive filter rules for destinations that only overlap via exit routes. Updates #2180
81 lines
2.4 KiB
Go
81 lines
2.4 KiB
Go
package policyutil
|
|
|
|
import (
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
"tailscale.com/net/tsaddr"
|
|
"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 not removed
|
|
// when the filters are reduced. Exit routes (0.0.0.0/0, ::/0)
|
|
// are skipped here because exit nodes handle traffic via
|
|
// AllowedIPs/routing, not packet filter rules. This matches
|
|
// Tailscale SaaS behavior where exit nodes do not receive
|
|
// filter rules for destinations that only overlap via exit routes.
|
|
if node.Hostinfo().Valid() {
|
|
routableIPs := node.Hostinfo().RoutableIPs()
|
|
if routableIPs.Len() > 0 {
|
|
for _, routableIP := range routableIPs.All() {
|
|
if tsaddr.IsExitRoute(routableIP) {
|
|
continue
|
|
}
|
|
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
|
|
}
|