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) } }) }