policy: autogroup:internet does not generate packet filters
According to Tailscale SaaS behavior, autogroup:internet is handled by exit node routing via AllowedIPs, not by packet filtering. ACL rules with autogroup:internet as destination should produce no filter rules for any node. Previously, Headscale expanded autogroup:internet to public CIDR ranges and distributed filters to exit nodes (because 0.0.0.0/0 "covers" internet destinations). This was incorrect. Add detection for AutoGroupInternet in filter compilation to skip filter generation for this autogroup. Update test expectations accordingly.
This commit is contained in:
parent
95b1fd636e
commit
835b7eb960
@ -891,9 +891,11 @@ func TestReduceNodesFromPolicy(t *testing.T) {
|
|||||||
]
|
]
|
||||||
}`,
|
}`,
|
||||||
node: n(1, "100.64.0.1", "mobile", "mobile"),
|
node: n(1, "100.64.0.1", "mobile", "mobile"),
|
||||||
|
// autogroup:internet does not generate packet filters - it's handled
|
||||||
|
// by exit node routing via AllowedIPs, not by packet filtering.
|
||||||
|
// Only server is visible through the mobile -> server:80 rule.
|
||||||
want: types.Nodes{
|
want: types.Nodes{
|
||||||
n(2, "100.64.0.2", "server", "server"),
|
n(2, "100.64.0.2", "server", "server"),
|
||||||
n(3, "100.64.0.3", "exit", "server", "0.0.0.0/0", "::/0"),
|
|
||||||
},
|
},
|
||||||
wantMatchers: 1,
|
wantMatchers: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -353,10 +353,12 @@ func TestReduceFilterRules(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []tailcfg.FilterRule{
|
want: []tailcfg.FilterRule{
|
||||||
// Merged: Both ACL rules combined (same SrcIPs and IPProto)
|
// Only the internal:* rule generates filters.
|
||||||
|
// autogroup:internet does NOT generate packet filters - it's handled
|
||||||
|
// by exit node routing via AllowedIPs, not by packet filtering.
|
||||||
{
|
{
|
||||||
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
SrcIPs: []string{"100.64.0.1/32", "100.64.0.2/32", "fd7a:115c:a1e0::1/128", "fd7a:115c:a1e0::2/128"},
|
||||||
DstPorts: append([]tailcfg.NetPortRange{
|
DstPorts: []tailcfg.NetPortRange{
|
||||||
{
|
{
|
||||||
IP: "100.64.0.100/32",
|
IP: "100.64.0.100/32",
|
||||||
Ports: tailcfg.PortRangeAny,
|
Ports: tailcfg.PortRangeAny,
|
||||||
@ -365,7 +367,7 @@ func TestReduceFilterRules(t *testing.T) {
|
|||||||
IP: "fd7a:115c:a1e0::100/128",
|
IP: "fd7a:115c:a1e0::100/128",
|
||||||
Ports: tailcfg.PortRangeAny,
|
Ports: tailcfg.PortRangeAny,
|
||||||
},
|
},
|
||||||
}, hsExitNodeDestForTest...),
|
},
|
||||||
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
IPProto: []int{v2.ProtocolTCP, v2.ProtocolUDP, v2.ProtocolICMP, v2.ProtocolIPv6ICMP},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -61,6 +61,12 @@ func (pol *Policy) compileFilterRules(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// autogroup:internet does not generate packet filters - it's handled
|
||||||
|
// by exit node routing via AllowedIPs, not by packet filtering.
|
||||||
|
if ag, isAutoGroup := dest.Alias.(*AutoGroup); isAutoGroup && ag.Is(AutoGroupInternet) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
ips, err := dest.Resolve(pol, users, nodes)
|
ips, err := dest.Resolve(pol, users, nodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace().Caller().Err(err).Msgf("resolving destination ips")
|
log.Trace().Caller().Err(err).Msgf("resolving destination ips")
|
||||||
@ -259,6 +265,12 @@ func (pol *Policy) compileACLWithAutogroupSelf(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// autogroup:internet does not generate packet filters - it's handled
|
||||||
|
// by exit node routing via AllowedIPs, not by packet filtering.
|
||||||
|
if ag, isAutoGroup := dest.Alias.(*AutoGroup); isAutoGroup && ag.Is(AutoGroupInternet) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
ips, err := dest.Resolve(pol, users, nodes)
|
ips, err := dest.Resolve(pol, users, nodes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Trace().Caller().Err(err).Msgf("resolving destination ips")
|
log.Trace().Caller().Err(err).Msgf("resolving destination ips")
|
||||||
|
|||||||
@ -1124,31 +1124,15 @@ func TestTailscaleRoutesCompatExitNodes(t *testing.T) {
|
|||||||
"user1": wildcardFilter,
|
"user1": wildcardFilter,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: Fix autogroup:internet to not generate filters
|
|
||||||
//
|
|
||||||
// B8: autogroup:internet generates no filters
|
// B8: autogroup:internet generates no filters
|
||||||
//
|
//
|
||||||
// TAILSCALE BEHAVIOR:
|
// autogroup:internet is handled by exit node routing via AllowedIPs,
|
||||||
// - autogroup:internet is handled by exit node routing, not packet filters
|
// not by packet filtering. ALL nodes should get null/empty filters.
|
||||||
// - ALL nodes should get null/empty filters for autogroup:internet destination
|
|
||||||
// - Traffic is routed through exit nodes via AllowedIPs, not filtered
|
|
||||||
//
|
|
||||||
// HEADSCALE BEHAVIOR:
|
|
||||||
// - Exit nodes (exit-node, multi-router) incorrectly receive filters
|
|
||||||
// - Because 0.0.0.0/0 "covers" autogroup:internet destinations
|
|
||||||
//
|
|
||||||
// ROOT CAUSE:
|
|
||||||
// Headscale treats autogroup:internet like a regular destination and
|
|
||||||
// distributes filters to nodes whose routes cover it (exit nodes)
|
|
||||||
//
|
|
||||||
// FIX REQUIRED:
|
|
||||||
// autogroup:internet should never generate packet filters
|
|
||||||
{
|
{
|
||||||
name: "B8_autogroup_internet_no_filters",
|
name: "B8_autogroup_internet_no_filters",
|
||||||
policy: makeRoutesPolicy(`
|
policy: makeRoutesPolicy(`
|
||||||
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:internet:*"]}
|
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:internet:*"]}
|
||||||
`),
|
`),
|
||||||
/* EXPECTED (Tailscale):
|
|
||||||
wantFilters: map[string][]tailcfg.FilterRule{
|
wantFilters: map[string][]tailcfg.FilterRule{
|
||||||
"client1": nil,
|
"client1": nil,
|
||||||
"client2": nil,
|
"client2": nil,
|
||||||
@ -1160,26 +1144,6 @@ func TestTailscaleRoutesCompatExitNodes(t *testing.T) {
|
|||||||
"big-router": nil,
|
"big-router": nil,
|
||||||
"user1": nil,
|
"user1": nil,
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
// ACTUAL (Headscale):
|
|
||||||
// Non-exit nodes correctly get nil.
|
|
||||||
// INCORRECT: exit-node and multi-router get filters with expanded public
|
|
||||||
// CIDR ranges from util.TheInternet() (all public IPs excluding CGNAT,
|
|
||||||
// private ranges, and Tailscale ULA). The exact CIDRs are complex and
|
|
||||||
// impractical to list here. Skipping comparison for exit nodes but
|
|
||||||
// documenting the difference: Tailscale returns nil, Headscale returns
|
|
||||||
// filters with SrcIPs=member IPs and DstPorts=expanded public CIDRs.
|
|
||||||
wantFilters: map[string][]tailcfg.FilterRule{
|
|
||||||
"client1": nil,
|
|
||||||
"client2": nil,
|
|
||||||
"subnet-router": nil,
|
|
||||||
"ha-router1": nil,
|
|
||||||
"ha-router2": nil,
|
|
||||||
"big-router": nil,
|
|
||||||
"user1": nil,
|
|
||||||
// exit-node and multi-router omitted - they incorrectly receive filters
|
|
||||||
// with expanded autogroup:internet CIDRs in Headscale (Tailscale: nil)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// B3: Exit node advertises exit routes (verify RoutableIPs)
|
// B3: Exit node advertises exit routes (verify RoutableIPs)
|
||||||
//
|
//
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user