servertest: cover broader-dst via grant in filter test
TestGrantViaSubnetFilterRules pins exact-equality dst. Add a sibling for the broader-dst case so the regression sits at the server level alongside the policy-engine unit test. Updates #3267
This commit is contained in:
parent
2cb914df59
commit
b5b786f519
@ -772,6 +772,123 @@ func TestGrantViaSubnetFilterRules(t *testing.T) {
|
|||||||
"without per-node filter compilation for via grants, these rules are missing")
|
"without per-node filter compilation for via grants, these rules are missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGrantViaSubnetBroaderDstFilterRules verifies that a via grant
|
||||||
|
// whose destination is broader than the router's advertised subnet
|
||||||
|
// still produces a filter rule on the router and surfaces the route in
|
||||||
|
// the client's AllowedIPs. Reproduces juanfont/headscale#3267 at the
|
||||||
|
// server level. Pre-fix the policy compiler required exact prefix
|
||||||
|
// equality, so the router's PacketFilter had no rule for the steered
|
||||||
|
// destination and traffic was silently dropped.
|
||||||
|
func TestGrantViaSubnetBroaderDstFilterRules(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
srv := servertest.NewServer(t)
|
||||||
|
routerUser := srv.CreateUser(t, "rt-user")
|
||||||
|
clientUser := srv.CreateUser(t, "cl-user")
|
||||||
|
|
||||||
|
advertised := netip.MustParsePrefix("10.33.0.0/16")
|
||||||
|
broaderDst := netip.MustParsePrefix("10.0.0.0/8")
|
||||||
|
|
||||||
|
changed, err := srv.State().SetPolicy([]byte(`{
|
||||||
|
"tagOwners": {
|
||||||
|
"tag:router-a": ["rt-user@"],
|
||||||
|
"tag:group-a": ["cl-user@"]
|
||||||
|
},
|
||||||
|
"grants": [
|
||||||
|
{
|
||||||
|
"src": ["tag:router-a", "tag:group-a"],
|
||||||
|
"dst": ["tag:router-a", "tag:group-a"],
|
||||||
|
"ip": ["*"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": ["tag:group-a"],
|
||||||
|
"dst": ["10.0.0.0/8"],
|
||||||
|
"ip": ["*"],
|
||||||
|
"via": ["tag:router-a"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoApprovers": {
|
||||||
|
"routes": {
|
||||||
|
"10.33.0.0/16": ["tag:router-a"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
changes, err := srv.State().ReloadPolicy()
|
||||||
|
require.NoError(t, err)
|
||||||
|
srv.App.Change(changes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
routerA := servertest.NewClient(t, srv, "router-a",
|
||||||
|
servertest.WithUser(routerUser),
|
||||||
|
servertest.WithTags("tag:router-a"))
|
||||||
|
clientA := servertest.NewClient(t, srv, "client-a",
|
||||||
|
servertest.WithUser(clientUser),
|
||||||
|
servertest.WithTags("tag:group-a"))
|
||||||
|
|
||||||
|
routerA.WaitForPeers(t, 1, 15*time.Second)
|
||||||
|
clientA.WaitForPeers(t, 1, 15*time.Second)
|
||||||
|
|
||||||
|
routerA.Direct().SetHostinfo(&tailcfg.Hostinfo{
|
||||||
|
BackendLogID: "servertest-router-a",
|
||||||
|
Hostname: "router-a",
|
||||||
|
RoutableIPs: []netip.Prefix{advertised},
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = routerA.Direct().SendUpdate(ctx)
|
||||||
|
|
||||||
|
routerAID := findNodeID(t, srv, "router-a")
|
||||||
|
_, routeChange, err := srv.State().SetApprovedRoutes(
|
||||||
|
routerAID, []netip.Prefix{advertised})
|
||||||
|
require.NoError(t, err)
|
||||||
|
srv.App.Change(routeChange)
|
||||||
|
|
||||||
|
// Client should see the advertised route in router's AllowedIPs.
|
||||||
|
clientA.WaitForCondition(t, "clientA sees advertised route via router-a",
|
||||||
|
15*time.Second,
|
||||||
|
func(nm *netmap.NetworkMap) bool {
|
||||||
|
for _, p := range nm.Peers {
|
||||||
|
hi := p.Hostinfo()
|
||||||
|
if hi.Valid() && hi.Hostname() == "router-a" {
|
||||||
|
for i := range p.AllowedIPs().Len() {
|
||||||
|
if p.AllowedIPs().At(i) == advertised {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Router's PacketFilter must contain the broader grant dst as a
|
||||||
|
// destination — that is what Tailscale SaaS emits for via grants.
|
||||||
|
routerNM := routerA.Netmap()
|
||||||
|
require.NotNil(t, routerNM)
|
||||||
|
require.NotNil(t, routerNM.PacketFilter,
|
||||||
|
"router PacketFilter should not be nil")
|
||||||
|
|
||||||
|
var foundBroaderDst bool
|
||||||
|
|
||||||
|
for _, m := range routerNM.PacketFilter {
|
||||||
|
for _, dst := range m.Dsts {
|
||||||
|
dstPrefix := netip.PrefixFrom(dst.Net.Addr(), dst.Net.Bits())
|
||||||
|
if dstPrefix == broaderDst {
|
||||||
|
foundBroaderDst = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, foundBroaderDst,
|
||||||
|
"router PacketFilter should contain destination rules for the broader grant dst 10.0.0.0/8; "+
|
||||||
|
"the via gate requires advertised-route overlap, and the emitted prefix is the dst")
|
||||||
|
}
|
||||||
|
|
||||||
// TestGrantViaExitNodeNoFilterRules verifies wire-format SaaS compat:
|
// TestGrantViaExitNodeNoFilterRules verifies wire-format SaaS compat:
|
||||||
// the exit node's PacketFilter must not contain literal 0.0.0.0/0 or
|
// the exit node's PacketFilter must not contain literal 0.0.0.0/0 or
|
||||||
// ::/0 destinations. Internally, autogroup:internet via grants emit a
|
// ::/0 destinations. Internally, autogroup:internet via grants emit a
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user