test: failing tests for blocked_networks config and extended SSRF ranges
Table-driven coverage for the CGNAT, IETF-protocol, benchmark, and NAT64 ranges (IPv4, IPv6, and IPv4-mapped forms), an operator-supplied blocked_networks entry enforced by the dialer, and strict parsing that aborts startup naming the key and the offending value. Model: opus-4-8
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package httpfetcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestIsPrivateIPBlocksSpecialRanges covers the internal and special-use
|
||||
// ranges added to the built-in blocklist, in IPv4, IPv6, and IPv4-mapped
|
||||
// forms, alongside public controls that must stay reachable.
|
||||
func TestIsPrivateIPBlocksSpecialRanges(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
want bool
|
||||
}{
|
||||
{"cgnat-low", "100.64.0.1", true},
|
||||
{"cgnat-high", "100.127.255.254", true},
|
||||
{"ietf-protocol", "192.0.0.1", true},
|
||||
{"benchmark-low", "198.18.0.1", true},
|
||||
{"benchmark-high", "198.19.255.254", true},
|
||||
{"nat64", "64:ff9b::1", true},
|
||||
{"nat64-embeds-private", "64:ff9b::a00:1", true}, // maps 10.0.0.1
|
||||
{"ipv4-mapped-private", "::ffff:10.0.0.1", true},
|
||||
{"cloud-metadata", "169.254.169.254", true},
|
||||
{"public-v4", "8.8.8.8", false},
|
||||
{"test-net-1-public", testPublicHost, false}, // TEST-NET-1, stays public
|
||||
{"public-v6", "2001:4860:4860::8888", false},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ip := net.ParseIP(tc.ip)
|
||||
if ip == nil {
|
||||
t.Fatalf("failed to parse IP %q", tc.ip)
|
||||
}
|
||||
|
||||
got := isPrivateIP(ip)
|
||||
if got != tc.want {
|
||||
t.Errorf("isPrivateIP(%q) = %v, want %v", tc.ip, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// transportOf returns the *http.Transport backing a fetcher, so a test can
|
||||
// exercise the SSRF-safe dialer New installed with the operator blocklist.
|
||||
func transportOf(t *testing.T, f *HTTPFetcher) *http.Transport {
|
||||
t.Helper()
|
||||
|
||||
transport, ok := f.client.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
t.Fatalf("transport is %T, want *http.Transport", f.client.Transport)
|
||||
}
|
||||
|
||||
return transport
|
||||
}
|
||||
|
||||
// TestDialerEnforcesBlockedNetworks proves an operator-supplied
|
||||
// blocked_networks entry is enforced by the dialer, in addition to the
|
||||
// built-in ranges, while an address outside both stays dialable.
|
||||
func TestDialerEnforcesBlockedNetworks(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
// TEST-NET-2 (198.51.100.0/24) is public to the built-in check, so
|
||||
// blocking it can only come from the operator-supplied list.
|
||||
cfg.BlockedNetworks = []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")}
|
||||
|
||||
transport := transportOf(t, New(cfg))
|
||||
|
||||
blocked := []string{
|
||||
"198.51.100.5:80", // operator-supplied range
|
||||
"10.0.0.5:80", // built-in RFC 1918, still enforced
|
||||
"100.64.0.1:80", // built-in CGNAT range
|
||||
}
|
||||
|
||||
for _, addr := range blocked {
|
||||
t.Run("blocked/"+addr, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := transport.DialContext(context.Background(), "tcp", addr)
|
||||
if !errors.Is(err, ErrSSRFBlocked) {
|
||||
t.Errorf("DialContext(%q) = %v, want ErrSSRFBlocked", addr, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("public-not-blocked", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// A cancelled context makes the dial fail without touching the
|
||||
// network; the point is only that a public literal outside every
|
||||
// blocked range is not SSRF-blocked.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
_, err := transport.DialContext(ctx, "tcp", testPublicHost+":80")
|
||||
if errors.Is(err, ErrSSRFBlocked) {
|
||||
t.Errorf("public target SSRF-blocked with operator list set: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user