178 lines
6.2 KiB
Go
178 lines
6.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"proxy-pool/internal/config"
|
|
"proxy-pool/internal/gateway/policy"
|
|
"proxy-pool/internal/platform/httpsecurity"
|
|
)
|
|
|
|
func TestBuildProtectionFromListenerConfig(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
protection, err := BuildProtection(config.Listener{
|
|
Access: config.Access{AllowCIDRs: []string{"198.51.100.0/24"}},
|
|
Auth: config.Auth{
|
|
Mode: "usernamePassword",
|
|
Username: "client",
|
|
Password: "secret",
|
|
},
|
|
Limits: config.Limits{RequestsPerMinute: 10, RequestsPerMinutePerClient: 2},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildProtection() error = %v", err)
|
|
}
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.test", nil)
|
|
request.RemoteAddr = "198.51.100.8:1234"
|
|
request.Header.Set("Proxy-Authorization", "Basic Y2xpZW50OnNlY3JldA==")
|
|
for name, guard := range map[string]Guard{
|
|
"auth": protection.Auth, "access": protection.Access, "admission": protection.Admission,
|
|
} {
|
|
if err := guard.Check(context.Background(), request); err != nil {
|
|
t.Fatalf("%s guard error = %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildProtectionSupportsBearerProxyAuthentication(t *testing.T) {
|
|
t.Parallel()
|
|
protection, err := BuildProtection(config.Listener{Auth: config.Auth{Mode: "bearer", Token: "proxy-token"}})
|
|
if err != nil {
|
|
t.Fatalf("BuildProtection() error = %v", err)
|
|
}
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.test", nil)
|
|
request.RemoteAddr = "198.51.100.8:1234"
|
|
request.Header.Set("Proxy-Authorization", "Bearer proxy-token")
|
|
if err := protection.Auth.Check(context.Background(), request); err != nil {
|
|
t.Fatalf("auth.Check() error = %v", err)
|
|
}
|
|
|
|
request.Header.Set("Proxy-Authorization", "Bearer wrong")
|
|
err = protection.Auth.Check(context.Background(), request)
|
|
var securityError *httpsecurity.HTTPError
|
|
if !errors.As(err, &securityError) || securityError.StatusCode != http.StatusProxyAuthRequired {
|
|
t.Fatalf("auth.Check(wrong) error = %T %v", err, err)
|
|
}
|
|
recorder := httptest.NewRecorder()
|
|
writeGatewayError(recorder, err)
|
|
if recorder.Code != http.StatusProxyAuthRequired || recorder.Header().Get("Proxy-Authenticate") == "" {
|
|
t.Fatalf("gateway response = status %d headers %v", recorder.Code, recorder.Header())
|
|
}
|
|
}
|
|
|
|
func TestBuildProtectionAnyPreservesIPWhitelistRejectionIndependentOfOrder(t *testing.T) {
|
|
t.Parallel()
|
|
methods := [][]config.AuthMethod{
|
|
{
|
|
{Mode: "ipWhitelist", CIDRs: []string{"10.0.0.0/8"}},
|
|
{Mode: "apiKey", Header: "X-Proxy-Key", Value: "secret"},
|
|
},
|
|
{
|
|
{Mode: "apiKey", Header: "X-Proxy-Key", Value: "secret"},
|
|
{Mode: "ipWhitelist", CIDRs: []string{"10.0.0.0/8"}},
|
|
},
|
|
}
|
|
for index, configuredMethods := range methods {
|
|
protection, err := BuildProtection(config.Listener{
|
|
Auth: config.Auth{Mode: "any", Methods: configuredMethods},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildProtection(%d) error = %v", index, err)
|
|
}
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.test", nil)
|
|
request.RemoteAddr = "198.51.100.8:1234"
|
|
err = protection.Auth.Check(context.Background(), request)
|
|
recorder := httptest.NewRecorder()
|
|
writeGatewayError(recorder, err)
|
|
if recorder.Code != http.StatusForbidden {
|
|
t.Fatalf("method order %d status = %d, want 403", index, recorder.Code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTargetPolicyFromOmittedConfigDefaultsToDeny(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
targets, err := TargetPolicyFromListener(config.Listener{})
|
|
if err != nil {
|
|
t.Fatalf("TargetPolicyFromListener() error = %v", err)
|
|
}
|
|
if _, err := targets.EvaluateURL(context.Background(), "http://127.0.0.1/"); !errors.Is(err, policy.ErrTargetDenied) {
|
|
t.Fatalf("EvaluateURL(loopback) error = %v, want ErrTargetDenied", err)
|
|
}
|
|
if _, err := targets.EvaluateConnectAuthority(context.Background(), "8.8.8.8:22"); !errors.Is(err, policy.ErrTargetDenied) {
|
|
t.Fatalf("EvaluateConnectAuthority(port 22) error = %v, want ErrTargetDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestTargetPolicyFromListenerMapsAllowedPorts(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
targets, err := TargetPolicyFromListener(config.Listener{
|
|
DestinationPolicy: config.DestinationPolicy{AllowedPorts: []uint16{8443}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("TargetPolicyFromListener() error = %v", err)
|
|
}
|
|
if _, err := targets.EvaluateConnectAuthority(context.Background(), "8.8.8.8:8443"); err != nil {
|
|
t.Fatalf("EvaluateConnectAuthority(port 8443) error = %v", err)
|
|
}
|
|
if _, err := targets.EvaluateConnectAuthority(context.Background(), "8.8.8.8:443"); !errors.Is(err, policy.ErrTargetDenied) {
|
|
t.Fatalf("EvaluateConnectAuthority(port 443) error = %v, want ErrTargetDenied", err)
|
|
}
|
|
}
|
|
|
|
func TestTargetPolicyFromPartialConfigKeepsOmittedCategoriesDenied(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
deny := true
|
|
targets, err := TargetPolicyFromListener(config.Listener{
|
|
DestinationPolicy: config.DestinationPolicy{DenyPrivateNetworks: &deny},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("TargetPolicyFromListener() error = %v", err)
|
|
}
|
|
for _, target := range []string{"http://127.0.0.1/", "http://169.254.10.20/"} {
|
|
if _, err := targets.EvaluateURL(context.Background(), target); !errors.Is(err, policy.ErrTargetDenied) {
|
|
t.Fatalf("EvaluateURL(%q) error = %v, want ErrTargetDenied", target, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTargetPolicyFromListenerAllowsOnlyExplicitlyFalseCategory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
allow := false
|
|
targets, err := TargetPolicyFromListener(config.Listener{
|
|
DestinationPolicy: config.DestinationPolicy{DenyLoopback: &allow},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("TargetPolicyFromListener() error = %v", err)
|
|
}
|
|
if _, err := targets.EvaluateURL(context.Background(), "http://127.0.0.1/"); err != nil {
|
|
t.Fatalf("EvaluateURL(loopback) error = %v", err)
|
|
}
|
|
for _, target := range []string{"http://10.0.0.1/", "http://169.254.10.20/"} {
|
|
if _, err := targets.EvaluateURL(context.Background(), target); !errors.Is(err, policy.ErrTargetDenied) {
|
|
t.Fatalf("EvaluateURL(%q) error = %v, want ErrTargetDenied", target, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConfigFromListenerMapsRetryAndConcurrency(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
result := ConfigFromListener(config.Listener{
|
|
Limits: config.Limits{MaxConcurrentConnections: 123},
|
|
Retry: config.Retry{MaxAttempts: 2, RetryMethods: []string{"GET", "HEAD"}},
|
|
})
|
|
if result.MaxConcurrentRequests != 123 || result.MaxAttempts != 2 || len(result.RetryMethods) != 2 {
|
|
t.Fatalf("handler config = %+v", result)
|
|
}
|
|
}
|