mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-25 01:59:07 +02:00
policy/v2: implement CapGrant compilation with companion capabilities
Compile grant app fields into CapGrant FilterRules matching Tailscale SaaS behavior. Key changes: - Generate CapGrant rules in compileFilterRules and compileGrantWithAutogroupSelf, with node-specific /32 and /128 Dsts for autogroup:self grants - Add reversed companion rules for drive→drive-sharer and relay→relay-target capabilities, ordered by original cap name - Narrow broad CapGrant Dsts to node-specific prefixes in ReduceFilterRules via new reduceCapGrantRule helper - Skip merging CapGrant rules in mergeFilterRules to preserve per-capability structure - Remove ip+app mutual exclusivity validation (Tailscale accepts both) - Add semantic JSON comparison for RawMessage types and netip.Prefix comparators in test infrastructure Reduces grant compat test skips from 99 to 41 (58 newly passing). Updates #2180
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package policyutil
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"tailscale.com/net/tsaddr"
|
||||
@@ -17,6 +19,17 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
|
||||
ret := []tailcfg.FilterRule{}
|
||||
|
||||
for _, rule := range rules {
|
||||
// Handle CapGrant rules separately — they use CapGrant[].Dsts
|
||||
// instead of DstPorts for destination matching.
|
||||
if len(rule.CapGrant) > 0 {
|
||||
reduced := reduceCapGrantRule(node, rule)
|
||||
if reduced != nil {
|
||||
ret = append(ret, *reduced)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// record if the rule is actually relevant for the given node.
|
||||
var dests []tailcfg.NetPortRange
|
||||
|
||||
@@ -78,3 +91,77 @@ func ReduceFilterRules(node types.NodeView, rules []tailcfg.FilterRule) []tailcf
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// reduceCapGrantRule filters a CapGrant rule to only include CapGrant
|
||||
// entries whose Dsts match the given node's IPs. When a broad prefix
|
||||
// (e.g. 100.64.0.0/10 from dst:*) contains a node's IP, it is
|
||||
// narrowed to the node's specific /32 or /128 prefix. Returns nil if
|
||||
// no CapGrant entries are relevant to this node.
|
||||
func reduceCapGrantRule(
|
||||
node types.NodeView,
|
||||
rule tailcfg.FilterRule,
|
||||
) *tailcfg.FilterRule {
|
||||
var capGrants []tailcfg.CapGrant
|
||||
|
||||
nodeIPs := node.IPs()
|
||||
|
||||
for _, cg := range rule.CapGrant {
|
||||
// Collect the node's IPs that fall within any of this
|
||||
// CapGrant's Dsts. Broad prefixes are narrowed to specific
|
||||
// /32 and /128 entries for the node.
|
||||
var matchingDsts []netip.Prefix
|
||||
|
||||
for _, dst := range cg.Dsts {
|
||||
if dst.IsSingleIP() {
|
||||
// Already a specific IP — keep it if it matches.
|
||||
if dst.Addr() == nodeIPs[0] || (len(nodeIPs) > 1 && dst.Addr() == nodeIPs[1]) {
|
||||
matchingDsts = append(matchingDsts, dst)
|
||||
}
|
||||
} else {
|
||||
// Broad prefix — narrow to node's specific IPs.
|
||||
for _, ip := range nodeIPs {
|
||||
if dst.Contains(ip) {
|
||||
matchingDsts = append(matchingDsts, netip.PrefixFrom(ip, ip.BitLen()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check routable IPs (subnet routes) — nodes that
|
||||
// advertise routes should receive CapGrant rules for
|
||||
// destinations that overlap their routes.
|
||||
if node.Hostinfo().Valid() {
|
||||
routableIPs := node.Hostinfo().RoutableIPs()
|
||||
if routableIPs.Len() > 0 {
|
||||
for _, dst := range cg.Dsts {
|
||||
for _, routableIP := range routableIPs.All() {
|
||||
if tsaddr.IsExitRoute(routableIP) {
|
||||
continue
|
||||
}
|
||||
|
||||
if dst.Overlaps(routableIP) {
|
||||
// For route overlaps, keep the original prefix.
|
||||
matchingDsts = append(matchingDsts, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchingDsts) > 0 {
|
||||
capGrants = append(capGrants, tailcfg.CapGrant{
|
||||
Dsts: matchingDsts,
|
||||
CapMap: cg.CapMap,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(capGrants) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tailcfg.FilterRule{
|
||||
SrcIPs: rule.SrcIPs,
|
||||
CapGrant: capGrants,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user