test(06-14): fetchguard IANA boundary and zoned dial tests, surf edge coverage
This commit is contained in:
@@ -306,10 +306,43 @@ func withTestLoopback(srv *httptest.Server, p Policy) Policy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDialControlRejectsZonedAndSpecialUse(t *testing.T) {
|
func TestDialControlRejectsZonedAndSpecialUse(t *testing.T) {
|
||||||
ctl := dialControl(Policy{})
|
probes := []string{"[fe80::1%eth0]:443", "198.18.0.1:443", "192.0.0.1:443", "240.0.0.1:443"}
|
||||||
for _, a := range []string{"[fe80::1%eth0]:443", "198.18.0.1:443"} {
|
ctl := dialControl(Policy{Mode: PublicOnlyMode})
|
||||||
if err := ctl("tcp", a, nil); !errors.Is(err, errPrivateIP) {
|
for _, a := range probes {
|
||||||
t.Errorf("%s: got %v", a, err)
|
t.Run(a, func(t *testing.T) {
|
||||||
|
err := ctl("tcp", a, nil)
|
||||||
|
if !errors.Is(err, errPrivateIP) {
|
||||||
|
t.Fatalf("dialControl(%s) = %v, want errPrivateIP", a, err)
|
||||||
|
}
|
||||||
|
if got := mapTransportError(err).Reason; got != ReasonPrivateIP {
|
||||||
|
t.Fatalf("reason = %q, want %q", got, ReasonPrivateIP)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := ctl("tcp", "8.8.8.8:443", nil); err != nil {
|
||||||
|
t.Fatalf("public address rejected: %v", err)
|
||||||
|
}
|
||||||
|
if err := ctl("tcp", "[2606:4700:4700::1111]:443", nil); err != nil {
|
||||||
|
t.Fatalf("public v6 rejected: %v", err)
|
||||||
|
}
|
||||||
|
if err := ctl("tcp", "no-port", nil); err == nil {
|
||||||
|
t.Fatal("address without port accepted")
|
||||||
|
}
|
||||||
|
if err := ctl("tcp", "example.com:443", nil); err == nil || errors.Is(err, errPrivateIP) {
|
||||||
|
t.Fatalf("hostname dial target: err = %v, want unparseable error", err)
|
||||||
|
}
|
||||||
|
if err := dialControl(Policy{skipReservedCheck: true})("tcp", "127.0.0.1:1", nil); err != nil {
|
||||||
|
t.Fatalf("skipReservedCheck: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFetchPublicOnlyMapsSpecialUseToPrivateIP(t *testing.T) {
|
||||||
|
for _, u := range []string{"https://[fe80::1%25eth0]/", "https://198.18.0.1/", "https://192.0.0.1/", "https://240.0.0.1/"} {
|
||||||
|
t.Run(u, func(t *testing.T) {
|
||||||
|
_, err := Fetch(t.Context(), u, Policy{Mode: PublicOnlyMode, Timeout: 2 * time.Second, MaxBytes: 1024}, nil)
|
||||||
|
if got := reasonFrom(t, err); got != ReasonPrivateIP {
|
||||||
|
t.Fatalf("reason = %q, want %s (err %v)", got, ReasonPrivateIP, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,3 +90,89 @@ func TestIsReservedOrPrivateSpecialUseSmoke(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prefixBounds(p netip.Prefix) (first, last netip.Addr) {
|
||||||
|
first = p.Masked().Addr()
|
||||||
|
b := first.AsSlice()
|
||||||
|
for bit := p.Bits(); bit < len(b)*8; bit++ {
|
||||||
|
b[bit/8] |= 1 << (7 - bit%8)
|
||||||
|
}
|
||||||
|
last, _ = netip.AddrFromSlice(b)
|
||||||
|
return first, last
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsReservedOrPrivateIANABoundaries(t *testing.T) {
|
||||||
|
var all []netip.Prefix
|
||||||
|
all = append(all, privateV4...)
|
||||||
|
all = append(all, privateV6...)
|
||||||
|
inAny := func(a netip.Addr) bool {
|
||||||
|
for _, p := range all {
|
||||||
|
if p.Contains(a) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, p := range all {
|
||||||
|
t.Run(p.String(), func(t *testing.T) {
|
||||||
|
first, last := prefixBounds(p)
|
||||||
|
interior := first.Next()
|
||||||
|
for _, a := range []netip.Addr{first, last, interior} {
|
||||||
|
if !p.Contains(a) {
|
||||||
|
t.Fatalf("test bug: %s not in %s", a, p)
|
||||||
|
}
|
||||||
|
if !isReservedOrPrivate(a) {
|
||||||
|
t.Errorf("%s (in %s) classified public", a, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, a := range []netip.Addr{first.Prev(), last.Next()} {
|
||||||
|
if !a.IsValid() || inAny(a) || a.IsMulticast() || a.IsUnspecified() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := embeddedTransitionIPv4(a); ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isReservedOrPrivate(a) {
|
||||||
|
t.Errorf("neighbour %s of %s classified non-public", a, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit := []struct {
|
||||||
|
ip string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"198.17.255.255", false}, {"198.18.0.0", true}, {"198.19.255.255", true}, {"198.20.0.0", false},
|
||||||
|
{"191.255.255.255", false}, {"192.0.0.0", true}, {"192.0.1.0", false},
|
||||||
|
{"239.255.255.255", true}, {"240.0.0.0", true}, {"255.255.255.255", true},
|
||||||
|
{"100.63.255.255", false}, {"100.64.0.0", true}, {"100.127.255.255", true}, {"100.128.0.0", false},
|
||||||
|
{"172.15.255.255", false}, {"172.32.0.0", false},
|
||||||
|
{"2001:200::1", false}, {"2001:db8::", true}, {"2001:db9::", false},
|
||||||
|
{"3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff", true}, {"3fff:1000::", false},
|
||||||
|
{"8.8.8.8", false}, {"1.1.1.1", false}, {"2606:4700:4700::1111", false},
|
||||||
|
{"::ffff:127.0.0.1", true}, {"::ffff:10.0.0.1", true}, {"::ffff:198.18.0.1", true}, {"::ffff:8.8.8.8", false},
|
||||||
|
}
|
||||||
|
for _, tc := range explicit {
|
||||||
|
if got := isReservedOrPrivate(netip.MustParseAddr(tc.ip)); got != tc.want {
|
||||||
|
t.Errorf("isReservedOrPrivate(%s) = %v, want %v", tc.ip, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isReservedOrPrivate(netip.Addr{}) {
|
||||||
|
t.Error("invalid addr must fail closed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsReservedOrPrivateIgnoresZone(t *testing.T) {
|
||||||
|
for _, s := range []string{"fe80::1%eth0", "fe80::1%1", "fe80::1", "fc00::1%eth0"} {
|
||||||
|
if !isReservedOrPrivate(netip.MustParseAddr(s)) {
|
||||||
|
t.Errorf("%s should be non-public", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []string{"2606:4700:4700::1111%eth0", "2606:4700:4700::1111%2"} {
|
||||||
|
a := netip.MustParseAddr(s)
|
||||||
|
if isReservedOrPrivate(a) != isReservedOrPrivate(a.WithZone("")) || isReservedOrPrivate(a) {
|
||||||
|
t.Errorf("%s should classify as its unzoned public form", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -267,3 +267,17 @@ func TestBodyLimitBoundsBodyConsumingMiddleware(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBodyLimitInvalidParamFailsBoot(t *testing.T) {
|
||||||
|
for _, param := range []string{"abc", "0", "-5", ""} {
|
||||||
|
t.Run(param, func(t *testing.T) {
|
||||||
|
cfg := writeHTTPConfig(t, "body_limits:\n default_bytes: 4\n upload_bytes: 8\n")
|
||||||
|
_, err := Assemble(backpack.New(cfg), []party.Plugin{bodyProbePlugin{
|
||||||
|
id: "golem15.probe", use: []string{"body.limit:" + param},
|
||||||
|
}})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "body.limit") {
|
||||||
|
t.Fatalf("err = %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -471,3 +471,22 @@ func TestMiddlewareFailsClosed(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRegisterBucketRejectsNilLimiterEmptyNameAndDuplicate(t *testing.T) {
|
||||||
|
key := func(*http.Request) string { return "k" }
|
||||||
|
good := Bucket{Max: 1, Decay: time.Minute, Key: key}
|
||||||
|
var nilLim *FixedWindowLimiter
|
||||||
|
if err := nilLim.RegisterBucket("p", "n", good); err == nil {
|
||||||
|
t.Fatal("nil limiter accepted")
|
||||||
|
}
|
||||||
|
l := NewFixedWindowLimiter(NewMemoryStore(time.Minute), nil)
|
||||||
|
if err := l.RegisterBucket("p", "", good); err == nil {
|
||||||
|
t.Fatal("empty name accepted")
|
||||||
|
}
|
||||||
|
if err := l.RegisterBucket("p", "n", good); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := l.RegisterBucket("q", "n", good); err == nil || !strings.Contains(err.Error(), "p") {
|
||||||
|
t.Fatalf("duplicate: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user