check / check (push) Canceled after 0s
internal/app now provides the cache, the fetcher, the processor, the image service, the session manager and the encrypted URL generator, and handlers.New receives them built. database.New opens the database, so these parts can read it while fx builds them; the cache's stop hook still runs before the database's. Each setting's default is declared in internal/config, the default cache_max_bytes included, which internal/app works out before it builds the cache. The cache's MaxBytes is the one value for the disk cache limit, and 0 turns the disk cache off. The fetcher and the processor take explicit values. The server tests build the pipeline themselves, as they cannot import internal/app. Model: opus-5-5
116 lines
3.3 KiB
Go
116 lines
3.3 KiB
Go
package httpfetcher
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// 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()
|
|
|
|
transport := transportOf(t, New(&Config{
|
|
Timeout: 30 * time.Second,
|
|
MaxResponseSize: 50 << 20,
|
|
MaxConnectionsPerHost: 20,
|
|
MaxConnections: 64,
|
|
// 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.
|
|
BlockedNetworks: []netip.Prefix{netip.MustParsePrefix("198.51.100.0/24")},
|
|
}))
|
|
|
|
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)
|
|
}
|
|
})
|
|
}
|