From 927ce418d2bd936a9e286a3ce0ac54f5997f2d19 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 18 Mar 2026 10:39:55 +0000 Subject: [PATCH] policy/v2: use bare IPs in autogroup:self DstPorts Use ip.String() instead of netip.PrefixFrom(ip, ip.BitLen()).String() when building DstPorts for autogroup:self destinations. This produces bare IPs like "100.90.199.68" instead of CIDR notation like "100.90.199.68/32", matching the Tailscale FilterRule wire format. Updates #2180 --- hscontrol/policy/v2/filter.go | 4 ++-- hscontrol/policy/v2/filter_test.go | 2 +- .../policy/v2/tailscale_grants_compat_test.go | 21 +------------------ 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/hscontrol/policy/v2/filter.go b/hscontrol/policy/v2/filter.go index 3d366334..30e7acbc 100644 --- a/hscontrol/policy/v2/filter.go +++ b/hscontrol/policy/v2/filter.go @@ -3,7 +3,6 @@ package v2 import ( "errors" "fmt" - "net/netip" "slices" "strconv" "strings" @@ -135,6 +134,7 @@ func (pol *Policy) destinationsToNetPortRange( if pref.IsSingleIP() { pr.IP = pref.Addr().String() } + ret = append(ret, pr) } } @@ -260,7 +260,7 @@ func (pol *Policy) compileGrantWithAutogroupSelf( for _, port := range ipp.Ports { for _, ip := range n.IPs() { destPorts = append(destPorts, tailcfg.NetPortRange{ - IP: netip.PrefixFrom(ip, ip.BitLen()).String(), + IP: ip.String(), Ports: port, }) } diff --git a/hscontrol/policy/v2/filter_test.go b/hscontrol/policy/v2/filter_test.go index 6805d9da..17f39e23 100644 --- a/hscontrol/policy/v2/filter_test.go +++ b/hscontrol/policy/v2/filter_test.go @@ -1846,7 +1846,7 @@ func TestAutogroupSelfWithSpecificUserSource(t *testing.T) { actualDestIPs = append(actualDestIPs, dst.IP) } - expectedDestIPs := []string{"100.64.0.1/32", "100.64.0.2/32"} + expectedDestIPs := []string{"100.64.0.1", "100.64.0.2"} assert.ElementsMatch(t, expectedDestIPs, actualDestIPs) node2 := nodes[2].View() diff --git a/hscontrol/policy/v2/tailscale_grants_compat_test.go b/hscontrol/policy/v2/tailscale_grants_compat_test.go index e05f792c..fa7aa5ea 100644 --- a/hscontrol/policy/v2/tailscale_grants_compat_test.go +++ b/hscontrol/policy/v2/tailscale_grants_compat_test.go @@ -259,24 +259,6 @@ var grantSkipReasons = map[string]string{ "GRANT-P15_1": "SUBNET_ROUTE_FILTER_RULES: dst=10.33.1.0/24 port 22 — subnet-router gets no rules", "GRANT-P15_3": "SUBNET_ROUTE_FILTER_RULES: dst=10.32.0.0/14 port 22 — subnet-router gets no rules", - // ======================================================================== - // AUTOGROUP_SELF_CIDR_FORMAT (4 tests) - // - // TODO: Use bare IPs (not CIDR notation) in DstPorts for autogroup:self grants. - // - // When compiling autogroup:self grants, headscale appends /32 to IPv4 - // and /128 to IPv6 DstPort IPs. Tailscale uses bare IPs without a CIDR - // suffix. - // - // Example diff (user1 node, autogroup:member -> autogroup:self): - // DstPorts: tailscale=[{IP:"100.90.199.68"}, {IP:"fd7a:...::2d01:c747"}] - // DstPorts: headscale=[{IP:"100.90.199.68/32"}, {IP:"fd7a:...::2d01:c747/128"}] - // ======================================================================== - "GRANT-P09_4E": "AUTOGROUP_SELF_CIDR_FORMAT: autogroup:member -> autogroup:self — DstPorts IPs have /32 and /128 suffix", - "GRANT-P09_13E": "AUTOGROUP_SELF_CIDR_FORMAT: autogroup:member -> autogroup:self with ip:[*] — DstPorts IPs have CIDR suffix", - "GRANT-P09_13F": "AUTOGROUP_SELF_CIDR_FORMAT: single user -> autogroup:self with ip:[22] — DstPorts IPs have CIDR suffix", - "GRANT-P09_13G": "AUTOGROUP_SELF_CIDR_FORMAT: single user -> autogroup:self with ip:[22,80,443] — DstPorts IPs have CIDR suffix", - // ======================================================================== // USER_PASSKEY_WILDCARD (2 tests) // @@ -567,14 +549,13 @@ var grantSkipReasons = map[string]string{ // CAPGRANT_COMPILATION_AND_SRCIPS - 11 tests: Both CapGrant compilation + SrcIPs format // SUBNET_ROUTE_FILTER_RULES - 11 tests: Generate filter rules for subnet-routed CIDRs // VIA_COMPILATION_AND_SRCIPS_FORMAT - 7 tests: Via route compilation + SrcIPs format -// AUTOGROUP_SELF_CIDR_FORMAT - 4 tests: DstPorts IPs get /32 or /128 suffix for autogroup:self // VIA_COMPILATION - 3 tests: Via route compilation // AUTOGROUP_DANGER_ALL - 3 tests: Implement autogroup:danger-all support // USER_PASSKEY_WILDCARD - 2 tests: user:*@passkey wildcard pattern unresolvable // VALIDATION_STRICTNESS - 2 tests: headscale too strict (rejects what Tailscale accepts) // SRCIPS_WILDCARD_NODE_DEDUP - 1 test: Wildcard+specific source node IP deduplication // -// Total: 113 tests skipped, ~124 tests expected to pass. +// Total: 109 tests skipped, ~128 tests expected to pass. func TestGrantsCompat(t *testing.T) { t.Parallel()