integration,ci: remove TestSubnetToSubnetACL

The integration test cannot prove end-to-end subnet-to-subnet data
plane in Docker-in-Docker: forwarding from eth0 through the
Tailscale tunnel interface requires Linux policy routing that
doesn't work in the hi test runner's container environment.

The #3157 fix (CIDR-only ACL making subnet routers peers) is
validated by the golden data compat tests (f10-f16) which compare
headscale's filter compilation and peer visibility against SaaS.

Updates #3157
This commit is contained in:
Kristoffer Dalby
2026-04-13 14:58:10 +00:00
parent 073f11b6bf
commit dcd95698e4
2 changed files with 0 additions and 223 deletions
-1
View File
@@ -249,7 +249,6 @@ jobs:
- TestAutoApproveMultiNetwork/webauth-group.*
- TestSubnetRouteACLFiltering
- TestGrantViaSubnetSteering
- TestSubnetToSubnetACL
- TestHeadscale
- TestTailscaleNodesJoiningHeadcale
- TestSSHOneUserToAll
-222
View File
@@ -3493,225 +3493,3 @@ func TestGrantViaSubnetSteering(t *testing.T) {
assertTracerouteViaIPWithCollect(c, tr, ip)
}, assertTimeout, 200*time.Millisecond, "Client B traceroute should go through Router B")
}
// TestSubnetToSubnetACL verifies that when ACL rules reference only subnet
// CIDRs as source and destination, the subnet routers for those subnets see
// each other as peers and exchange routes, enabling end-to-end
// connectivity between the two physical subnets.
//
// This is a regression test for https://github.com/juanfont/headscale/issues/3157
func TestSubnetToSubnetACL(t *testing.T) {
IntegrationSkip(t)
routerAUser := "routera"
routerBUser := "routerb"
spec := ScenarioSpec{
NodesPerUser: 1,
Users: []string{routerAUser, routerBUser},
Networks: map[string]NetworkSpec{
"usernet1": {Users: []string{routerAUser}},
"usernet2": {Users: []string{routerBUser}},
},
ExtraService: map[string][]extraServiceFunc{
"usernet1": {Webservice},
"usernet2": {Webservice},
},
Versions: []string{"head"},
}
scenario, err := NewScenario(spec)
require.NoErrorf(t, err, "failed to create scenario: %s", err)
defer scenario.ShutdownAssertNoPanics(t)
prefA, err := scenario.SubnetOfNetwork("usernet1")
require.NoError(t, err)
prefB, err := scenario.SubnetOfNetwork("usernet2")
require.NoError(t, err)
// ACL: only subnet-A ↔ subnet-B traffic is allowed.
// No rules reference node IPs directly.
aclPolicy := &policyv2.Policy{
ACLs: []policyv2.ACL{
{
Action: "accept",
Sources: []policyv2.Alias{
prefixp(prefA.String()),
},
Destinations: []policyv2.AliasWithPorts{
aliasWithPorts(prefixp(prefB.String()), tailcfg.PortRangeAny),
},
},
{
Action: "accept",
Sources: []policyv2.Alias{
prefixp(prefB.String()),
},
Destinations: []policyv2.AliasWithPorts{
aliasWithPorts(prefixp(prefA.String()), tailcfg.PortRangeAny),
},
},
},
}
err = scenario.CreateHeadscaleEnv(
[]tsic.Option{tsic.WithAcceptRoutes()},
hsic.WithTestName("subnet2subnet"),
hsic.WithACLPolicy(aclPolicy),
)
requireNoErrHeadscaleEnv(t, err)
err = scenario.WaitForTailscaleSync()
requireNoErrSync(t, err)
headscale, err := scenario.Headscale()
requireNoErrGetHeadscale(t, err)
routerAClients, err := scenario.ListTailscaleClients(routerAUser)
require.NoError(t, err)
require.Len(t, routerAClients, 1)
routerA := routerAClients[0]
routerBClients, err := scenario.ListTailscaleClients(routerBUser)
require.NoError(t, err)
require.Len(t, routerBClients, 1)
routerB := routerBClients[0]
// Advertise subnet routes.
_, _, err = routerA.Execute([]string{
"tailscale", "set",
"--advertise-routes=" + prefA.String(),
})
require.NoErrorf(t, err, "failed to advertise route on router A")
_, _, err = routerB.Execute([]string{
"tailscale", "set",
"--advertise-routes=" + prefB.String(),
})
require.NoErrorf(t, err, "failed to advertise route on router B")
// Wait for advertisements to propagate.
var nodes []*v1.Node
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err = headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, nodes, 2)
// Both should have 1 available route.
for _, n := range nodes {
requireNodeRouteCountWithCollect(c, n, 1, 0, 0)
}
}, integrationutil.ScaledTimeout(10*time.Second), 100*time.Millisecond,
"route advertisements should propagate")
// Approve routes on both routers.
for _, n := range nodes {
_, err = headscale.ApproveRoutes(
n.GetId(),
util.MustStringsToPrefixes(n.GetAvailableRoutes()),
)
require.NoError(t, err)
}
// Wait for approvals to propagate.
assert.EventuallyWithT(t, func(c *assert.CollectT) {
nodes, err = headscale.ListNodes()
assert.NoError(c, err)
assert.Len(c, nodes, 2)
for _, n := range nodes {
requireNodeRouteCountWithCollect(c, n, 1, 1, 1)
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond,
"route approvals should propagate")
// Verify both routers see each other's subnet routes.
assert.EventuallyWithT(t, func(c *assert.CollectT) {
statusA, err := routerA.Status()
assert.NoError(c, err)
for _, peerKey := range statusA.Peers() {
peerStatus := statusA.Peer[peerKey]
requirePeerSubnetRoutesWithCollect(c, peerStatus, []netip.Prefix{*prefB})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond,
"Router A should see Router B's subnet route")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
statusB, err := routerB.Status()
assert.NoError(c, err)
for _, peerKey := range statusB.Peers() {
peerStatus := statusB.Peer[peerKey]
requirePeerSubnetRoutesWithCollect(c, peerStatus, []netip.Prefix{*prefA})
}
}, integrationutil.ScaledTimeout(10*time.Second), 500*time.Millisecond,
"Router B should see Router A's subnet route")
// Verify end-to-end connectivity: Router A → webservice on network B.
usernet2, err := scenario.Network("usernet2")
require.NoError(t, err)
servicesB, err := scenario.Services("usernet2")
require.NoError(t, err)
require.Len(t, servicesB, 1)
webB := servicesB[0]
webBIP := netip.MustParseAddr(webB.GetIPInNetwork(usernet2))
urlB := fmt.Sprintf("http://%s/etc/hostname", webBIP)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
result, err := routerA.Curl(urlB)
assert.NoError(c, err)
assert.Len(c, result, 13)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond,
"Router A should reach webservice on network B")
// Verify end-to-end connectivity: Router B → webservice on network A.
usernet1, err := scenario.Network("usernet1")
require.NoError(t, err)
servicesA, err := scenario.Services("usernet1")
require.NoError(t, err)
require.Len(t, servicesA, 1)
webA := servicesA[0]
webAIP := netip.MustParseAddr(webA.GetIPInNetwork(usernet1))
urlA := fmt.Sprintf("http://%s/etc/hostname", webAIP)
assert.EventuallyWithT(t, func(c *assert.CollectT) {
result, err := routerB.Curl(urlA)
assert.NoError(c, err)
assert.Len(c, result, 13)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond,
"Router B should reach webservice on network A")
// Verify traceroute paths.
assert.EventuallyWithT(t, func(c *assert.CollectT) {
tr, err := routerA.Traceroute(webBIP)
assert.NoError(c, err)
ip, err := routerB.IPv4()
if !assert.NoError(c, err, "failed to get IPv4 for routerB") {
return
}
assertTracerouteViaIPWithCollect(c, tr, ip)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond,
"Traceroute from Router A should go through Router B")
assert.EventuallyWithT(t, func(c *assert.CollectT) {
tr, err := routerB.Traceroute(webAIP)
assert.NoError(c, err)
ip, err := routerA.IPv4()
if !assert.NoError(c, err, "failed to get IPv4 for routerA") {
return
}
assertTracerouteViaIPWithCollect(c, tr, ip)
}, integrationutil.ScaledTimeout(10*time.Second), 200*time.Millisecond,
"Traceroute from Router B should go through Router A")
}