package acl import ( "net/netip" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/client/firewall" "github.com/netbirdio/netbird/client/iface/wgaddr" "github.com/netbirdio/netbird/client/internal/acl/mocks" "github.com/netbirdio/netbird/client/internal/netflow" mgmProto "github.com/netbirdio/netbird/management/proto" ) var flowLogger = netflow.NewManager(nil, []byte{}, nil).GetLogger() func TestDefaultManager(t *testing.T) { networkMap := &mgmProto.NetworkMap{ FirewallRules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "80", }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_DROP, Protocol: mgmProto.RuleProtocol_UDP, Port: "53", }, }, } ctrl := gomock.NewController(t) defer ctrl.Finish() ifaceMock := mocks.NewMockIFaceMapper(ctrl) ifaceMock.EXPECT().IsUserspaceBind().Return(true).AnyTimes() ifaceMock.EXPECT().SetFilter(gomock.Any()) network := netip.MustParsePrefix("172.0.0.1/32") ifaceMock.EXPECT().Name().Return("lo").AnyTimes() ifaceMock.EXPECT().Address().Return(wgaddr.Address{ IP: network.Addr(), Network: network, }).AnyTimes() ifaceMock.EXPECT().GetWGDevice().Return(nil).AnyTimes() fw, err := firewall.NewFirewall(ifaceMock, nil, flowLogger, false) require.NoError(t, err) defer func() { err = fw.Close(nil) require.NoError(t, err) }() acl := NewDefaultManager(fw) t.Run("apply firewall rules", func(t *testing.T) { acl.ApplyFiltering(networkMap, false) if fw.IsStateful() { assert.Equal(t, 0, len(acl.peerRulesPairs)) } else { assert.Equal(t, 2, len(acl.peerRulesPairs)) } }) t.Run("add extra rules", func(t *testing.T) { existedPairs := map[string]struct{}{} for id := range acl.peerRulesPairs { existedPairs[id.ID()] = struct{}{} } // remove first rule networkMap.FirewallRules = networkMap.FirewallRules[1:] networkMap.FirewallRules = append( networkMap.FirewallRules, &mgmProto.FirewallRule{ PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_DROP, Protocol: mgmProto.RuleProtocol_ICMP, }, ) acl.ApplyFiltering(networkMap, false) expectedRules := 2 if fw.IsStateful() { expectedRules = 1 // only the inbound rule } assert.Equal(t, expectedRules, len(acl.peerRulesPairs)) // check that old rule was removed previousCount := 0 for id := range acl.peerRulesPairs { if _, ok := existedPairs[id.ID()]; ok { previousCount++ } } expectedPreviousCount := 0 if !fw.IsStateful() { expectedPreviousCount = 1 } assert.Equal(t, expectedPreviousCount, previousCount) }) t.Run("handle default rules", func(t *testing.T) { networkMap.FirewallRules = networkMap.FirewallRules[:0] networkMap.FirewallRulesIsEmpty = true acl.ApplyFiltering(networkMap, false) assert.Equal(t, 0, len(acl.peerRulesPairs)) networkMap.FirewallRulesIsEmpty = false acl.ApplyFiltering(networkMap, false) expectedRules := 1 if fw.IsStateful() { expectedRules = 1 // only inbound allow-all rule } assert.Equal(t, expectedRules, len(acl.peerRulesPairs)) }) } func TestDefaultManagerStateless(t *testing.T) { // stateless currently only in userspace, so we have to disable kernel t.Setenv("NB_WG_KERNEL_DISABLED", "true") t.Setenv("NB_DISABLE_CONNTRACK", "true") networkMap := &mgmProto.NetworkMap{ FirewallRules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "80", }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, Port: "53", }, }, } ctrl := gomock.NewController(t) defer ctrl.Finish() ifaceMock := mocks.NewMockIFaceMapper(ctrl) ifaceMock.EXPECT().IsUserspaceBind().Return(true).AnyTimes() ifaceMock.EXPECT().SetFilter(gomock.Any()) network := netip.MustParsePrefix("172.0.0.1/32") ifaceMock.EXPECT().Name().Return("lo").AnyTimes() ifaceMock.EXPECT().Address().Return(wgaddr.Address{ IP: network.Addr(), Network: network, }).AnyTimes() ifaceMock.EXPECT().GetWGDevice().Return(nil).AnyTimes() fw, err := firewall.NewFirewall(ifaceMock, nil, flowLogger, false) require.NoError(t, err) defer func() { err = fw.Close(nil) require.NoError(t, err) }() acl := NewDefaultManager(fw) t.Run("stateless firewall creates outbound rules", func(t *testing.T) { acl.ApplyFiltering(networkMap, false) // In stateless mode, we should have both inbound and outbound rules assert.False(t, fw.IsStateful()) assert.Equal(t, 2, len(acl.peerRulesPairs)) }) } func TestDefaultManagerSquashRules(t *testing.T) { networkMap := &mgmProto.NetworkMap{ RemotePeers: []*mgmProto.RemotePeerConfig{ {AllowedIps: []string{"10.93.0.1"}}, {AllowedIps: []string{"10.93.0.2"}}, {AllowedIps: []string{"10.93.0.3"}}, {AllowedIps: []string{"10.93.0.4"}}, }, FirewallRules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, }, } manager := &DefaultManager{} rules, _ := manager.squashAcceptRules(networkMap) assert.Equal(t, 2, len(rules)) r := rules[0] assert.Equal(t, "0.0.0.0", r.PeerIP) assert.Equal(t, mgmProto.RuleDirection_IN, r.Direction) assert.Equal(t, mgmProto.RuleProtocol_ALL, r.Protocol) assert.Equal(t, mgmProto.RuleAction_ACCEPT, r.Action) r = rules[1] assert.Equal(t, "0.0.0.0", r.PeerIP) assert.Equal(t, mgmProto.RuleDirection_OUT, r.Direction) assert.Equal(t, mgmProto.RuleProtocol_ALL, r.Protocol) assert.Equal(t, mgmProto.RuleAction_ACCEPT, r.Action) } func TestDefaultManagerSquashRulesNoAffect(t *testing.T) { networkMap := &mgmProto.NetworkMap{ RemotePeers: []*mgmProto.RemotePeerConfig{ {AllowedIps: []string{"10.93.0.1"}}, {AllowedIps: []string{"10.93.0.2"}}, {AllowedIps: []string{"10.93.0.3"}}, {AllowedIps: []string{"10.93.0.4"}}, }, FirewallRules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_ALL, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, }, }, } manager := &DefaultManager{} rules, _ := manager.squashAcceptRules(networkMap) assert.Equal(t, len(networkMap.FirewallRules), len(rules)) } func TestDefaultManagerSquashRulesWithPortRestrictions(t *testing.T) { tests := []struct { name string rules []*mgmProto.FirewallRule expectedCount int description string }{ { name: "should not squash rules with port ranges", rules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 8080, End: 8090, }, }, }, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 8080, End: 8090, }, }, }, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 8080, End: 8090, }, }, }, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 8080, End: 8090, }, }, }, }, }, expectedCount: 4, description: "Rules with port ranges should not be squashed even if they cover all peers", }, { name: "should not squash rules with specific ports", rules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 80, }, }, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 80, }, }, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 80, }, }, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 80, }, }, }, }, expectedCount: 4, description: "Rules with specific ports should not be squashed even if they cover all peers", }, { name: "should not squash rules with legacy port field", rules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, }, expectedCount: 4, description: "Rules with legacy port field should not be squashed", }, { name: "should not squash rules with DROP action", rules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_DROP, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_DROP, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_DROP, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_DROP, Protocol: mgmProto.RuleProtocol_TCP, }, }, expectedCount: 4, description: "Rules with DROP action should not be squashed", }, { name: "should squash rules without port restrictions", rules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, }, expectedCount: 1, description: "Rules without port restrictions should be squashed into a single 0.0.0.0 rule", }, { name: "mixed rules should not squash protocol with port restrictions", rules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, PortInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 80, }, }, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, }, expectedCount: 4, description: "TCP should not be squashed because one rule has port restrictions", }, { name: "should squash UDP but not TCP when TCP has port restrictions", rules: []*mgmProto.FirewallRule{ // TCP rules with port restrictions - should NOT be squashed { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, Port: "443", }, // UDP rules without port restrictions - SHOULD be squashed { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, }, { PeerIP: "10.93.0.4", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, }, }, expectedCount: 5, // 4 TCP rules + 1 squashed UDP rule (0.0.0.0) description: "UDP should be squashed to 0.0.0.0 rule, but TCP should remain as individual rules due to port restrictions", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { networkMap := &mgmProto.NetworkMap{ RemotePeers: []*mgmProto.RemotePeerConfig{ {AllowedIps: []string{"10.93.0.1"}}, {AllowedIps: []string{"10.93.0.2"}}, {AllowedIps: []string{"10.93.0.3"}}, {AllowedIps: []string{"10.93.0.4"}}, }, FirewallRules: tt.rules, } manager := &DefaultManager{} rules, _ := manager.squashAcceptRules(networkMap) assert.Equal(t, tt.expectedCount, len(rules), tt.description) // For squashed rules, verify we get the expected 0.0.0.0 rule if tt.expectedCount == 1 { assert.Equal(t, "0.0.0.0", rules[0].PeerIP) assert.Equal(t, mgmProto.RuleDirection_IN, rules[0].Direction) assert.Equal(t, mgmProto.RuleAction_ACCEPT, rules[0].Action) } }) } } func TestPortInfoEmpty(t *testing.T) { tests := []struct { name string portInfo *mgmProto.PortInfo expected bool }{ { name: "nil PortInfo should be empty", portInfo: nil, expected: true, }, { name: "PortInfo with zero port should be empty", portInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 0, }, }, expected: true, }, { name: "PortInfo with valid port should not be empty", portInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Port{ Port: 80, }, }, expected: false, }, { name: "PortInfo with nil range should be empty", portInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: nil, }, }, expected: true, }, { name: "PortInfo with zero start range should be empty", portInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 0, End: 100, }, }, }, expected: true, }, { name: "PortInfo with zero end range should be empty", portInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 80, End: 0, }, }, }, expected: true, }, { name: "PortInfo with valid range should not be empty", portInfo: &mgmProto.PortInfo{ PortSelection: &mgmProto.PortInfo_Range_{ Range: &mgmProto.PortInfo_Range{ Start: 8080, End: 8090, }, }, }, expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := portInfoEmpty(tt.portInfo) assert.Equal(t, tt.expected, result) }) } } func TestDefaultManagerEnableSSHRules(t *testing.T) { networkMap := &mgmProto.NetworkMap{ PeerConfig: &mgmProto.PeerConfig{ SshConfig: &mgmProto.SSHConfig{ SshEnabled: true, }, }, RemotePeers: []*mgmProto.RemotePeerConfig{ {AllowedIps: []string{"10.93.0.1"}}, {AllowedIps: []string{"10.93.0.2"}}, {AllowedIps: []string{"10.93.0.3"}}, }, FirewallRules: []*mgmProto.FirewallRule{ { PeerIP: "10.93.0.1", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.2", Direction: mgmProto.RuleDirection_IN, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_TCP, }, { PeerIP: "10.93.0.3", Direction: mgmProto.RuleDirection_OUT, Action: mgmProto.RuleAction_ACCEPT, Protocol: mgmProto.RuleProtocol_UDP, }, }, } ctrl := gomock.NewController(t) defer ctrl.Finish() ifaceMock := mocks.NewMockIFaceMapper(ctrl) ifaceMock.EXPECT().IsUserspaceBind().Return(true).AnyTimes() ifaceMock.EXPECT().SetFilter(gomock.Any()) network := netip.MustParsePrefix("172.0.0.1/32") ifaceMock.EXPECT().Name().Return("lo").AnyTimes() ifaceMock.EXPECT().Address().Return(wgaddr.Address{ IP: network.Addr(), Network: network, }).AnyTimes() ifaceMock.EXPECT().GetWGDevice().Return(nil).AnyTimes() fw, err := firewall.NewFirewall(ifaceMock, nil, flowLogger, false) require.NoError(t, err) defer func() { err = fw.Close(nil) require.NoError(t, err) }() acl := NewDefaultManager(fw) acl.ApplyFiltering(networkMap, false) expectedRules := 3 if fw.IsStateful() { expectedRules = 3 // 2 inbound rules + SSH rule } assert.Equal(t, expectedRules, len(acl.peerRulesPairs)) }