policy/matcher: include CapGrant.Dsts in match destinations
MatchFromFilterRule only read DstPorts[].IP into the destination IPSet. Cap-grant-only filter rules (e.g. tailscale.com/cap/relay) carry their destinations in CapGrant[].Dsts, so the derived matchers had empty dest sets and BuildPeerMap / ReduceNodes never exposed the cap target to its source nodes. Without a companion IP-level grant the relay node stayed invisible, so clients never tried to use it and connections sat on DERP. Union CapGrant[].Dsts into the destination IPSet alongside DstPorts. Restores peer-visibility for any cap-grant-only relationship; the peer-relay flow is the most visible instance. Fixes #3256
This commit is contained in:
parent
795a1efe9b
commit
c3df84e354
3 changed files with 281 additions and 4 deletions
|
|
@ -44,13 +44,40 @@ func MatchesFromFilterRules(rules []tailcfg.FilterRule) []Match {
|
|||
return matches
|
||||
}
|
||||
|
||||
// MatchFromFilterRule derives a Match from a tailcfg.FilterRule. The
|
||||
// destination IP set is the union of DstPorts[].IP and CapGrant[].Dsts:
|
||||
// cap-grant-only rules (e.g. tailscale.com/cap/relay) carry their
|
||||
// destinations in CapGrant.Dsts and would otherwise contribute nothing
|
||||
// to peer-visibility derivation in BuildPeerMap / ReduceNodes, hiding
|
||||
// the cap target from the source unless a companion IP-level rule
|
||||
// also exists.
|
||||
func MatchFromFilterRule(rule tailcfg.FilterRule) Match {
|
||||
dests := make([]string, 0, len(rule.DstPorts))
|
||||
for _, dest := range rule.DstPorts {
|
||||
dests = append(dests, dest.IP)
|
||||
srcs := new(netipx.IPSetBuilder)
|
||||
dests := new(netipx.IPSetBuilder)
|
||||
|
||||
for _, srcIP := range rule.SrcIPs {
|
||||
set, _ := util.ParseIPSet(srcIP, nil)
|
||||
srcs.AddSet(set)
|
||||
}
|
||||
|
||||
return MatchFromStrings(rule.SrcIPs, dests)
|
||||
for _, dp := range rule.DstPorts {
|
||||
set, _ := util.ParseIPSet(dp.IP, nil)
|
||||
dests.AddSet(set)
|
||||
}
|
||||
|
||||
for _, cg := range rule.CapGrant {
|
||||
for _, pref := range cg.Dsts {
|
||||
dests.AddPrefix(pref)
|
||||
}
|
||||
}
|
||||
|
||||
srcsSet, _ := srcs.IPSet()
|
||||
destsSet, _ := dests.IPSet()
|
||||
|
||||
return Match{
|
||||
srcs: srcsSet,
|
||||
dests: destsSet,
|
||||
}
|
||||
}
|
||||
|
||||
// MatchFromStrings builds a Match from raw source and destination
|
||||
|
|
|
|||
|
|
@ -180,6 +180,78 @@ func TestMatchFromFilterRule(t *testing.T) {
|
|||
srcMatch: true,
|
||||
dstMatch: false,
|
||||
},
|
||||
{
|
||||
// Regression: cap-grant-only rules (e.g. cap/relay)
|
||||
// carry their destinations in CapGrant.Dsts. The
|
||||
// matcher must surface those for peer-visibility
|
||||
// derivation. https://github.com/juanfont/headscale/issues/3256
|
||||
name: "CapGrant Dsts populate destination set",
|
||||
rule: tailcfg.FilterRule{
|
||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32"},
|
||||
CapGrant: []tailcfg.CapGrant{
|
||||
{
|
||||
Dsts: []netip.Prefix{
|
||||
netip.MustParsePrefix("100.64.0.3/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
checkSrc: netip.MustParseAddr("100.64.0.1"),
|
||||
checkDst: netip.MustParseAddr("100.64.0.3"),
|
||||
srcMatch: true,
|
||||
dstMatch: true,
|
||||
},
|
||||
{
|
||||
// Companion cap-grant shape produced by
|
||||
// companionCapGrantRules: SrcIPs are the original
|
||||
// destinations, CapGrant.Dsts are the original sources.
|
||||
name: "companion CapGrant Dsts populate destination set",
|
||||
rule: tailcfg.FilterRule{
|
||||
SrcIPs: []string{"100.64.0.3/32"},
|
||||
CapGrant: []tailcfg.CapGrant{
|
||||
{
|
||||
Dsts: []netip.Prefix{
|
||||
netip.MustParsePrefix("100.64.0.1/32"),
|
||||
netip.MustParsePrefix("100.64.0.2/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelayTarget: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
checkSrc: netip.MustParseAddr("100.64.0.3"),
|
||||
checkDst: netip.MustParseAddr("100.64.0.2"),
|
||||
srcMatch: true,
|
||||
dstMatch: true,
|
||||
},
|
||||
{
|
||||
// Mixed rule: DstPorts and CapGrant both contribute to dests.
|
||||
name: "DstPorts and CapGrant Dsts both contribute",
|
||||
rule: tailcfg.FilterRule{
|
||||
SrcIPs: []string{"100.64.0.1/32"},
|
||||
DstPorts: []tailcfg.NetPortRange{
|
||||
{IP: "10.0.0.0/8"},
|
||||
},
|
||||
CapGrant: []tailcfg.CapGrant{
|
||||
{
|
||||
Dsts: []netip.Prefix{
|
||||
netip.MustParsePrefix("100.64.0.3/32"),
|
||||
},
|
||||
CapMap: tailcfg.PeerCapMap{
|
||||
tailcfg.PeerCapabilityRelay: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
checkSrc: netip.MustParseAddr("100.64.0.1"),
|
||||
checkDst: netip.MustParseAddr("100.64.0.3"),
|
||||
srcMatch: true,
|
||||
dstMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue