Skip to content

Commit 73cfa7b

Browse files
authored
add firewall tests for ipv6 (#1451)
Test things like cidr and local_cidr with ipv6 addresses, to ensure everything is working correctly.
1 parent 768325c commit 73cfa7b

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed

firewall_test.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ func TestFirewall_AddRule(t *testing.T) {
6868
ti, err := netip.ParsePrefix("1.2.3.4/32")
6969
require.NoError(t, err)
7070

71+
ti6, err := netip.ParsePrefix("fd12::34/128")
72+
require.NoError(t, err)
73+
7174
require.NoError(t, fw.AddRule(true, firewall.ProtoTCP, 1, 1, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
7275
// An empty rule is any
7376
assert.True(t, fw.InRules.TCP[1].Any.Any.Any)
@@ -92,12 +95,24 @@ func TestFirewall_AddRule(t *testing.T) {
9295
_, ok := fw.OutRules.AnyProto[1].Any.CIDR.Get(ti)
9396
assert.True(t, ok)
9497

98+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
99+
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti6, netip.Prefix{}, "", ""))
100+
assert.Nil(t, fw.OutRules.AnyProto[1].Any.Any)
101+
_, ok = fw.OutRules.AnyProto[1].Any.CIDR.Get(ti6)
102+
assert.True(t, ok)
103+
95104
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
96105
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, ti, "", ""))
97106
assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
98107
_, ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti)
99108
assert.True(t, ok)
100109

110+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
111+
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", netip.Prefix{}, ti6, "", ""))
112+
assert.NotNil(t, fw.OutRules.AnyProto[1].Any.Any)
113+
_, ok = fw.OutRules.AnyProto[1].Any.Any.LocalCIDR.Get(ti6)
114+
assert.True(t, ok)
115+
101116
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
102117
require.NoError(t, fw.AddRule(true, firewall.ProtoUDP, 1, 1, []string{"g1"}, "", netip.Prefix{}, netip.Prefix{}, "ca-name", ""))
103118
assert.Contains(t, fw.InRules.UDP[1].CANames, "ca-name")
@@ -117,6 +132,13 @@ func TestFirewall_AddRule(t *testing.T) {
117132
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp, netip.Prefix{}, "", ""))
118133
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
119134

135+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
136+
anyIp6, err := netip.ParsePrefix("::/0")
137+
require.NoError(t, err)
138+
139+
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 0, 0, []string{}, "", anyIp6, netip.Prefix{}, "", ""))
140+
assert.True(t, fw.OutRules.AnyProto[0].Any.Any.Any)
141+
120142
// Test error conditions
121143
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
122144
require.Error(t, fw.AddRule(true, math.MaxUint8, 0, 0, []string{}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
@@ -199,6 +221,82 @@ func TestFirewall_Drop(t *testing.T) {
199221
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
200222
}
201223

224+
func TestFirewall_DropV6(t *testing.T) {
225+
l := test.NewLogger()
226+
ob := &bytes.Buffer{}
227+
l.SetOutput(ob)
228+
229+
p := firewall.Packet{
230+
LocalAddr: netip.MustParseAddr("fd12::34"),
231+
RemoteAddr: netip.MustParseAddr("fd12::34"),
232+
LocalPort: 10,
233+
RemotePort: 90,
234+
Protocol: firewall.ProtoUDP,
235+
Fragment: false,
236+
}
237+
238+
c := dummyCert{
239+
name: "host1",
240+
networks: []netip.Prefix{netip.MustParsePrefix("fd12::34/120")},
241+
groups: []string{"default-group"},
242+
issuer: "signer-shasum",
243+
}
244+
h := HostInfo{
245+
ConnectionState: &ConnectionState{
246+
peerCert: &cert.CachedCertificate{
247+
Certificate: &c,
248+
InvertedGroups: map[string]struct{}{"default-group": {}},
249+
},
250+
},
251+
vpnAddrs: []netip.Addr{netip.MustParseAddr("fd12::34")},
252+
}
253+
h.buildNetworks(c.networks, c.unsafeNetworks)
254+
255+
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
256+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", netip.Prefix{}, netip.Prefix{}, "", ""))
257+
cp := cert.NewCAPool()
258+
259+
// Drop outbound
260+
assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))
261+
// Allow inbound
262+
resetConntrack(fw)
263+
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
264+
// Allow outbound because conntrack
265+
require.NoError(t, fw.Drop(p, false, &h, cp, nil))
266+
267+
// test remote mismatch
268+
oldRemote := p.RemoteAddr
269+
p.RemoteAddr = netip.MustParseAddr("fd12::56")
270+
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)
271+
p.RemoteAddr = oldRemote
272+
273+
// ensure signer doesn't get in the way of group checks
274+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
275+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum"))
276+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum-bad"))
277+
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
278+
279+
// test caSha doesn't drop on match
280+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
281+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum-bad"))
282+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "", "signer-shasum"))
283+
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
284+
285+
// ensure ca name doesn't get in the way of group checks
286+
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
287+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
288+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
289+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
290+
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
291+
292+
// test caName doesn't drop on match
293+
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
294+
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
295+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good-bad", ""))
296+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", netip.Prefix{}, netip.Prefix{}, "ca-good", ""))
297+
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
298+
}
299+
202300
func BenchmarkFirewallTable_match(b *testing.B) {
203301
f := &Firewall{}
204302
ft := FirewallTable{
@@ -208,6 +306,10 @@ func BenchmarkFirewallTable_match(b *testing.B) {
208306
pfix := netip.MustParsePrefix("172.1.1.1/32")
209307
_ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix, netip.Prefix{}, "", "")
210308
_ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", netip.Prefix{}, pfix, "", "")
309+
310+
pfix6 := netip.MustParsePrefix("fd11::11/128")
311+
_ = ft.TCP.addRule(f, 10, 10, []string{"good-group"}, "good-host", pfix6, netip.Prefix{}, "", "")
312+
_ = ft.TCP.addRule(f, 100, 100, []string{"good-group"}, "good-host", netip.Prefix{}, pfix6, "", "")
211313
cp := cert.NewCAPool()
212314

213315
b.Run("fail on proto", func(b *testing.B) {
@@ -239,6 +341,15 @@ func BenchmarkFirewallTable_match(b *testing.B) {
239341
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: ip.Addr()}, true, c, cp))
240342
}
241343
})
344+
b.Run("pass proto, port, fail on local CIDRv6", func(b *testing.B) {
345+
c := &cert.CachedCertificate{
346+
Certificate: &dummyCert{},
347+
}
348+
ip := netip.MustParsePrefix("fd99::99/128")
349+
for n := 0; n < b.N; n++ {
350+
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: ip.Addr()}, true, c, cp))
351+
}
352+
})
242353

243354
b.Run("pass proto, port, any local CIDR, fail all group, name, and cidr", func(b *testing.B) {
244355
c := &cert.CachedCertificate{
@@ -252,6 +363,18 @@ func BenchmarkFirewallTable_match(b *testing.B) {
252363
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
253364
}
254365
})
366+
b.Run("pass proto, port, any local CIDRv6, fail all group, name, and cidr", func(b *testing.B) {
367+
c := &cert.CachedCertificate{
368+
Certificate: &dummyCert{
369+
name: "nope",
370+
networks: []netip.Prefix{netip.MustParsePrefix("fd99::99/128")},
371+
},
372+
InvertedGroups: map[string]struct{}{"nope": {}},
373+
}
374+
for n := 0; n < b.N; n++ {
375+
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 10}, true, c, cp))
376+
}
377+
})
255378

256379
b.Run("pass proto, port, specific local CIDR, fail all group, name, and cidr", func(b *testing.B) {
257380
c := &cert.CachedCertificate{
@@ -265,6 +388,18 @@ func BenchmarkFirewallTable_match(b *testing.B) {
265388
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix.Addr()}, true, c, cp))
266389
}
267390
})
391+
b.Run("pass proto, port, specific local CIDRv6, fail all group, name, and cidr", func(b *testing.B) {
392+
c := &cert.CachedCertificate{
393+
Certificate: &dummyCert{
394+
name: "nope",
395+
networks: []netip.Prefix{netip.MustParsePrefix("fd99:99/128")},
396+
},
397+
InvertedGroups: map[string]struct{}{"nope": {}},
398+
}
399+
for n := 0; n < b.N; n++ {
400+
assert.False(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix6.Addr()}, true, c, cp))
401+
}
402+
})
268403

269404
b.Run("pass on group on any local cidr", func(b *testing.B) {
270405
c := &cert.CachedCertificate{
@@ -289,6 +424,17 @@ func BenchmarkFirewallTable_match(b *testing.B) {
289424
assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix.Addr()}, true, c, cp))
290425
}
291426
})
427+
b.Run("pass on group on specific local cidr6", func(b *testing.B) {
428+
c := &cert.CachedCertificate{
429+
Certificate: &dummyCert{
430+
name: "nope",
431+
},
432+
InvertedGroups: map[string]struct{}{"good-group": {}},
433+
}
434+
for n := 0; n < b.N; n++ {
435+
assert.True(b, ft.match(firewall.Packet{Protocol: firewall.ProtoTCP, LocalPort: 100, LocalAddr: pfix6.Addr()}, true, c, cp))
436+
}
437+
})
292438

293439
b.Run("pass on name", func(b *testing.B) {
294440
c := &cert.CachedCertificate{
@@ -447,6 +593,42 @@ func TestFirewall_Drop3(t *testing.T) {
447593
require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
448594
}
449595

596+
func TestFirewall_Drop3V6(t *testing.T) {
597+
l := test.NewLogger()
598+
ob := &bytes.Buffer{}
599+
l.SetOutput(ob)
600+
601+
p := firewall.Packet{
602+
LocalAddr: netip.MustParseAddr("fd12::34"),
603+
RemoteAddr: netip.MustParseAddr("fd12::34"),
604+
LocalPort: 1,
605+
RemotePort: 1,
606+
Protocol: firewall.ProtoUDP,
607+
Fragment: false,
608+
}
609+
610+
network := netip.MustParsePrefix("fd12::34/120")
611+
c := cert.CachedCertificate{
612+
Certificate: &dummyCert{
613+
name: "host-owner",
614+
networks: []netip.Prefix{network},
615+
},
616+
}
617+
h := HostInfo{
618+
ConnectionState: &ConnectionState{
619+
peerCert: &c,
620+
},
621+
vpnAddrs: []netip.Addr{network.Addr()},
622+
}
623+
h.buildNetworks(c.Certificate.Networks(), c.Certificate.UnsafeNetworks())
624+
625+
// Test a remote address match
626+
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
627+
cp := cert.NewCAPool()
628+
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", netip.MustParsePrefix("fd12::34/120"), netip.Prefix{}, "", ""))
629+
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
630+
}
631+
450632
func TestFirewall_DropConntrackReload(t *testing.T) {
451633
l := test.NewLogger()
452634
ob := &bytes.Buffer{}
@@ -727,6 +909,21 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
727909
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
728910
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: netip.Prefix{}, localIp: cidr}, mf.lastCall)
729911

912+
// Test adding rule with cidr ipv6
913+
cidr6 := netip.MustParsePrefix("fd00::/8")
914+
conf = config.NewC(l)
915+
mf = &mockFirewall{}
916+
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "cidr": cidr6.String()}}}
917+
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
918+
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: cidr6, localIp: netip.Prefix{}}, mf.lastCall)
919+
920+
// Test adding rule with local_cidr ipv6
921+
conf = config.NewC(l)
922+
mf = &mockFirewall{}
923+
conf.Settings["firewall"] = map[string]any{"inbound": []any{map[string]any{"port": "1", "proto": "any", "local_cidr": cidr6.String()}}}
924+
require.NoError(t, AddFirewallRulesFromConfig(l, true, conf, mf))
925+
assert.Equal(t, addRuleCall{incoming: true, proto: firewall.ProtoAny, startPort: 1, endPort: 1, groups: nil, ip: netip.Prefix{}, localIp: cidr6}, mf.lastCall)
926+
730927
// Test adding rule with ca_sha
731928
conf = config.NewC(l)
732929
mf = &mockFirewall{}

0 commit comments

Comments
 (0)