mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-09-02 20:28:50 +02:00
✅ cidr: cover the invariant the range matching rests on
The helpers were tested in isolation but nothing tied them together, and what actually has to hold is that the key SetCIDR writes for a decision is one of the keys GetCIDR looks up for an IP that decision covers. A formatting change on either side would have silently stopped every range decision from matching with all tests green. TestCIDRKeys_MatchNormalizeCIDR pins that both ways, including the cases worth being explicit about: a decision that is not on a network address, IPv4 mapped clients against an IPv4 range, and that the two families do not mix. Dropping the mask in cidrKey fails 9 of its cases. Also covers CIDRLookupKeys against CIDRKeys length by length, the IPv6 side of the network address test, and CIDRPrefixLen.
This commit is contained in:
@@ -100,6 +100,181 @@ func TestCIDRKeys_VerifyNetworkAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCIDRKeys_VerifyNetworkAddressIPv6(t *testing.T) {
|
||||||
|
keys := CIDRKeys("2001:db8:1:2:3:4:5:6")
|
||||||
|
tests := []struct {
|
||||||
|
bits int
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{64, "2001:db8:1:2::/64"},
|
||||||
|
{48, "2001:db8:1::/48"},
|
||||||
|
{32, "2001:db8::/32"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if got := keys[128-tt.bits]; got != tt.want {
|
||||||
|
t.Errorf("/%d network should be %s, got %s", tt.bits, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCIDRKeys_MatchNormalizeCIDR covers the invariant range support rests on: the key
|
||||||
|
// written for a decision is a key looked up for the IPs it covers, and only those.
|
||||||
|
func TestCIDRKeys_MatchNormalizeCIDR(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
cidr string
|
||||||
|
ip string
|
||||||
|
match bool
|
||||||
|
}{
|
||||||
|
{cidr: "10.0.0.0/8", ip: "10.1.2.3", match: true},
|
||||||
|
{cidr: "10.0.0.0/24", ip: "10.0.0.1", match: true},
|
||||||
|
{cidr: "10.0.0.0/24", ip: "10.0.1.1", match: false},
|
||||||
|
{cidr: "1.2.3.4/32", ip: "1.2.3.4", match: true},
|
||||||
|
{cidr: "1.2.3.4/32", ip: "1.2.3.5", match: false},
|
||||||
|
{cidr: "0.0.0.0/0", ip: "8.8.8.8", match: true},
|
||||||
|
// LAPI does not have to send a network address, the host bits are dropped.
|
||||||
|
{cidr: "10.0.0.5/24", ip: "10.0.0.9", match: true},
|
||||||
|
{cidr: " 192.168.1.0/24 ", ip: "192.168.1.42", match: true},
|
||||||
|
{cidr: "2001:db8::/32", ip: "2001:db8::1", match: true},
|
||||||
|
{cidr: "2001:db8::/32", ip: "2001:db9::1", match: false},
|
||||||
|
{cidr: "::/0", ip: "2001:db8::1", match: true},
|
||||||
|
// An IPv4 range and an IPv4 mapped client still have to meet.
|
||||||
|
{cidr: "10.0.0.0/8", ip: "::ffff:10.1.2.3", match: true},
|
||||||
|
{cidr: "::ffff:10.0.0.0/104", ip: "10.1.2.3", match: true},
|
||||||
|
// Families do not mix.
|
||||||
|
{cidr: "::/0", ip: "8.8.8.8", match: false},
|
||||||
|
{cidr: "2001:db8::/32", ip: "::ffff:10.0.0.1", match: false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.cidr+"_"+tt.ip, func(t *testing.T) {
|
||||||
|
key := NormalizeCIDR(tt.cidr)
|
||||||
|
if key == "" {
|
||||||
|
t.Fatalf("NormalizeCIDR(%q) returned nothing", tt.cidr)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, candidate := range CIDRKeys(tt.ip) {
|
||||||
|
if candidate == key {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found != tt.match {
|
||||||
|
t.Errorf("key %q of %q found in CIDRKeys(%q) = %v, want %v", key, tt.cidr, tt.ip, found, tt.match)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCIDRLookupKeys(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ip string
|
||||||
|
prefixLens []int
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "most specific first",
|
||||||
|
ip: "10.1.2.3",
|
||||||
|
prefixLens: []int{8, 32, 16},
|
||||||
|
want: []string{"10.1.2.3/32", "10.1.0.0/16", "10.0.0.0/8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicates are dropped",
|
||||||
|
ip: "10.1.2.3",
|
||||||
|
prefixLens: []int{24, 24, 24},
|
||||||
|
want: []string{"10.1.2.0/24"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lengths of the other family are skipped",
|
||||||
|
ip: "10.1.2.3",
|
||||||
|
prefixLens: []int{48, 64, 24},
|
||||||
|
want: []string{"10.1.2.0/24"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "out of range lengths are skipped",
|
||||||
|
ip: "10.1.2.3",
|
||||||
|
prefixLens: []int{-1, 33, 129, 8},
|
||||||
|
want: []string{"10.0.0.0/8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipv6 keeps its own lengths",
|
||||||
|
ip: "2001:db8::1",
|
||||||
|
prefixLens: []int{32, 64},
|
||||||
|
want: []string{"2001:db8::/64", "2001:db8::/32"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no length gives no key",
|
||||||
|
ip: "10.1.2.3",
|
||||||
|
prefixLens: []int{},
|
||||||
|
want: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid ip gives no key",
|
||||||
|
ip: "invalid",
|
||||||
|
prefixLens: []int{24},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := CIDRLookupKeys(tt.ip, tt.prefixLens)
|
||||||
|
if len(got) != len(tt.want) {
|
||||||
|
t.Fatalf("CIDRLookupKeys(%q, %v) = %v, want %v", tt.ip, tt.prefixLens, got, tt.want)
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
if got[i] != tt.want[i] {
|
||||||
|
t.Errorf("CIDRLookupKeys(%q, %v)[%d] = %q, want %q", tt.ip, tt.prefixLens, i, got[i], tt.want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCIDRLookupKeys_SubsetOfCIDRKeys: restricting the lengths only removes candidates,
|
||||||
|
// it never changes the key of a length that is kept.
|
||||||
|
func TestCIDRLookupKeys_SubsetOfCIDRKeys(t *testing.T) {
|
||||||
|
for _, ipStr := range []string{"10.1.2.3", "2001:db8::1", "::ffff:10.1.2.3"} {
|
||||||
|
t.Run(ipStr, func(t *testing.T) {
|
||||||
|
all := CIDRKeys(ipStr)
|
||||||
|
maxBits := len(all) - 1
|
||||||
|
for bits := 0; bits <= maxBits; bits++ {
|
||||||
|
got := CIDRLookupKeys(ipStr, []int{bits})
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Fatalf("CIDRLookupKeys(%q, [%d]) returned %d keys", ipStr, bits, len(got))
|
||||||
|
}
|
||||||
|
if want := all[maxBits-bits]; got[0] != want {
|
||||||
|
t.Errorf("CIDRLookupKeys(%q, [%d]) = %q, want %q", ipStr, bits, got[0], want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCIDRPrefixLen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{"10.0.0.0/8", 8},
|
||||||
|
{"10.0.0.0/32", 32},
|
||||||
|
{"0.0.0.0/0", 0},
|
||||||
|
{"10.0.0.5/24", 24},
|
||||||
|
{" 10.0.0.0/16 ", 16},
|
||||||
|
{"2001:db8::/32", 32},
|
||||||
|
{"2001:db8::/128", 128},
|
||||||
|
{"::/0", 0},
|
||||||
|
{"10.0.0.1", -1},
|
||||||
|
{"invalid", -1},
|
||||||
|
{"", -1},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.input, func(t *testing.T) {
|
||||||
|
if got := CIDRPrefixLen(tt.input); got != tt.want {
|
||||||
|
t.Errorf("CIDRPrefixLen(%q) = %d, want %d", tt.input, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNormalizeCIDR(t *testing.T) {
|
func TestNormalizeCIDR(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
|
|||||||
Reference in New Issue
Block a user