package policy import ( "context" "errors" "net/netip" "reflect" "testing" ) func TestParseURLAuthorityUsesDefaultPortAndPreservesHost(t *testing.T) { authority, err := ParseURLAuthority("https://example.com/path?q=1") if err != nil { t.Fatalf("ParseURLAuthority(): %v", err) } if authority.Host != "example.com" { t.Fatalf("host = %q, want example.com", authority.Host) } if authority.Port != 443 { t.Fatalf("port = %d, want 443", authority.Port) } if authority.LiteralIP.IsValid() { t.Fatalf("literal ip = %v, want invalid", authority.LiteralIP) } } func TestParseConnectAuthoritySupportsIPv6Literal(t *testing.T) { authority, err := ParseConnectAuthority("[2001:db8::1]:8443") if err != nil { t.Fatalf("ParseConnectAuthority(): %v", err) } if authority.Host != "2001:db8::1" { t.Fatalf("host = %q, want 2001:db8::1", authority.Host) } if authority.Port != 8443 { t.Fatalf("port = %d, want 8443", authority.Port) } if got := authority.LiteralIP.String(); got != "2001:db8::1" { t.Fatalf("literal ip = %q, want 2001:db8::1", got) } } func TestEvaluateURLRejectsLiteralMetadataAddressWithoutDNSLookup(t *testing.T) { resolver := &stubResolver{} policy, err := NewTargetPolicy(Config{Resolver: resolver}) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } _, err = policy.EvaluateURL(context.Background(), "http://169.254.169.254/latest/meta-data") if !errors.Is(err, ErrTargetDenied) { t.Fatalf("EvaluateURL() error = %v, want ErrTargetDenied", err) } if resolver.calls != nil { t.Fatalf("resolver calls = %v, want nil", resolver.calls) } } func TestEvaluateConnectAuthorityRejectsResolvedMixedResults(t *testing.T) { resolver := &stubResolver{ results: map[string][]netip.Addr{ "example.com": { netip.MustParseAddr("93.184.216.34"), netip.MustParseAddr("10.0.0.8"), }, }, } policy, err := NewTargetPolicy(Config{Resolver: resolver}) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } _, err = policy.EvaluateConnectAuthority(context.Background(), "example.com:443") if !errors.Is(err, ErrTargetDenied) { t.Fatalf("EvaluateConnectAuthority() error = %v, want ErrTargetDenied", err) } if got, want := resolver.calls, []string{"example.com"}; !reflect.DeepEqual(got, want) { t.Fatalf("resolver calls = %v, want %v", got, want) } } func TestEvaluateURLPinsTheValidatedResolutionForTransport(t *testing.T) { t.Parallel() policy, err := NewTargetPolicy(Config{Resolver: &stubResolver{results: map[string][]netip.Addr{ "example.test": { netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("2001:4860:4860::8888"), }, }}}) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } authority, err := policy.EvaluateURL(context.Background(), "http://example.test/path") if err != nil { t.Fatalf("EvaluateURL() error = %v", err) } if got := authority.DialAddress(); got != "8.8.8.8:80" { t.Fatalf("DialAddress() = %q, want 8.8.8.8:80", got) } } func TestEvaluateAuthorityRejectsReservedAndDisallowedPortsBeforeDNS(t *testing.T) { t.Parallel() resolver := &stubResolver{} targets, err := NewTargetPolicy(Config{Resolver: resolver}) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } for _, target := range []string{ "http://100.64.0.1/", "http://198.51.100.10/", "http://[2001:db8::1]/", "http://[3fff::1]/", "http://168.63.129.16/", "http://example.test:25/", } { if _, err := targets.EvaluateURL(context.Background(), target); !errors.Is(err, ErrTargetDenied) { t.Fatalf("EvaluateURL(%q) error = %v, want ErrTargetDenied", target, err) } } if resolver.calls != nil { t.Fatalf("resolver calls = %v, want nil for disallowed port", resolver.calls) } } func TestEvaluateAuthorityAllowsExplicitPort(t *testing.T) { t.Parallel() targets, err := NewTargetPolicy(Config{ AllowedPorts: []uint16{8443}, Resolver: &stubResolver{results: map[string][]netip.Addr{ "example.test": {netip.MustParseAddr("8.8.8.8")}, }}, }) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } if _, err := targets.EvaluateConnectAuthority(context.Background(), "example.test:8443"); err != nil { t.Fatalf("EvaluateConnectAuthority() error = %v", err) } } func TestEvaluateConnectAuthorityRejectsIPv6LoopbackLiteral(t *testing.T) { policy, err := NewTargetPolicy(Config{Resolver: &stubResolver{}}) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } _, err = policy.EvaluateConnectAuthority(context.Background(), "[::1]:443") if !errors.Is(err, ErrTargetDenied) { t.Fatalf("EvaluateConnectAuthority() error = %v, want ErrTargetDenied", err) } } func TestEvaluateURLAllowsPrivateNetworksWhenExplicitlyEnabled(t *testing.T) { resolver := &stubResolver{ results: map[string][]netip.Addr{ "internal.example": { netip.MustParseAddr("10.1.2.3"), }, }, } policy, err := NewTargetPolicy(Config{ Resolver: resolver, AllowPrivateNetworks: true, }) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } authority, err := policy.EvaluateURL(context.Background(), "http://internal.example") if err != nil { t.Fatalf("EvaluateURL(): %v", err) } if authority.Host != "internal.example" || authority.Port != 80 { t.Fatalf("authority = %+v, want internal.example:80", authority) } } func TestEvaluateURLCanConfigureLoopbackAndLinkLocalIndependently(t *testing.T) { t.Parallel() loopback, err := NewTargetPolicy(Config{AllowLoopback: true}) if err != nil { t.Fatalf("NewTargetPolicy(loopback): %v", err) } if _, err := loopback.EvaluateURL(context.Background(), "http://127.0.0.1/"); err != nil { t.Fatalf("EvaluateURL(loopback) error = %v", err) } linkLocal, err := NewTargetPolicy(Config{AllowLinkLocal: true}) if err != nil { t.Fatalf("NewTargetPolicy(link-local): %v", err) } if _, err := linkLocal.EvaluateURL(context.Background(), "http://169.254.10.20/"); err != nil { t.Fatalf("EvaluateURL(link-local) error = %v", err) } if _, err := linkLocal.EvaluateURL(context.Background(), "http://127.0.0.1/"); !errors.Is(err, ErrTargetDenied) { t.Fatalf("link-local policy loopback error = %v, want ErrTargetDenied", err) } if _, err := linkLocal.EvaluateURL(context.Background(), "http://169.254.169.254/"); !errors.Is(err, ErrTargetDenied) { t.Fatalf("link-local policy metadata error = %v, want ErrTargetDenied", err) } } func TestEvaluateURLAllowsPrivateWhenEnabledButStillHonorsDenyCIDRs(t *testing.T) { resolver := &stubResolver{ results: map[string][]netip.Addr{ "internal.example": { netip.MustParseAddr("10.1.2.3"), netip.MustParseAddr("169.254.169.254"), }, }, } policy, err := NewTargetPolicy(Config{ Resolver: resolver, AllowPrivateNetworks: true, DenyCIDRs: []string{"169.254.169.254/32"}, }) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } _, err = policy.EvaluateURL(context.Background(), "http://internal.example") if !errors.Is(err, ErrTargetDenied) { t.Fatalf("EvaluateURL() error = %v, want ErrTargetDenied", err) } } func TestEvaluateConnectAuthorityPropagatesContextCancellation(t *testing.T) { resolver := &stubResolver{ err: context.Canceled, } policy, err := NewTargetPolicy(Config{Resolver: resolver}) if err != nil { t.Fatalf("NewTargetPolicy(): %v", err) } ctx, cancel := context.WithCancel(context.Background()) cancel() _, err = policy.EvaluateConnectAuthority(ctx, "example.com:443") if !errors.Is(err, context.Canceled) { t.Fatalf("EvaluateConnectAuthority() error = %v, want context.Canceled", err) } } type stubResolver struct { results map[string][]netip.Addr err error calls []string } func (s *stubResolver) LookupNetIP(ctx context.Context, host string) ([]netip.Addr, error) { s.calls = append(s.calls, host) if s.err != nil { return nil, s.err } return append([]netip.Addr(nil), s.results[host]...), nil }