251 lines
6.2 KiB
Go
251 lines
6.2 KiB
Go
|
|
package middleware
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"net/http"
|
|||
|
|
"net/http/httptest"
|
|||
|
|
"strings"
|
|||
|
|
"testing"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// TestGetClientIP_HeaderInjection
|
|||
|
|
// TDD: 验证 getClientIP 函数能够处理恶意构造的 X-Forwarded-For 头
|
|||
|
|
func TestGetClientIP_HeaderInjection(t *testing.T) {
|
|||
|
|
// 可信代理配置
|
|||
|
|
trustedProxies := []string{"192.168.0.0/16", "10.0.0.0/8"}
|
|||
|
|
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
headers map[string]string
|
|||
|
|
remoteAddr string
|
|||
|
|
trusted []string
|
|||
|
|
expectedIP string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "IP with port should be cleaned (trusted proxy)",
|
|||
|
|
headers: map[string]string{"X-Forwarded-For": "203.0.113.1:8080"},
|
|||
|
|
remoteAddr: "192.168.1.1:1234",
|
|||
|
|
trusted: trustedProxies,
|
|||
|
|
expectedIP: "203.0.113.1",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Multiple IPs with spaces (trusted proxy)",
|
|||
|
|
headers: map[string]string{"X-Forwarded-For": " 203.0.113.1 , 198.51.100.1 "},
|
|||
|
|
remoteAddr: "192.168.1.1:1234",
|
|||
|
|
trusted: trustedProxies,
|
|||
|
|
expectedIP: "203.0.113.1",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|||
|
|
req.RemoteAddr = tt.remoteAddr
|
|||
|
|
|
|||
|
|
for k, v := range tt.headers {
|
|||
|
|
req.Header.Set(k, v)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ip := getClientIP(req, tt.trusted...)
|
|||
|
|
|
|||
|
|
if ip != tt.expectedIP {
|
|||
|
|
t.Errorf("expected IP %s, got %s", tt.expectedIP, ip)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestGetClientIP_PrivateIPInHeaders
|
|||
|
|
// TDD: 验证来自不可信源的私有IP不应该被信任
|
|||
|
|
func TestGetClientIP_PrivateIPInHeaders(t *testing.T) {
|
|||
|
|
// 当请求来自外部(remoteAddr是公网IP),X-Forwarded-For中的私有IP可能是伪造的
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
headers map[string]string
|
|||
|
|
remoteAddr string
|
|||
|
|
trustedCIDR []string
|
|||
|
|
expectedIP string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "Private IP in X-Forwarded-For from untrusted source",
|
|||
|
|
headers: map[string]string{"X-Forwarded-For": "192.168.1.1"},
|
|||
|
|
remoteAddr: "203.0.113.1:1234", // 公网IP
|
|||
|
|
trustedCIDR: nil, // 未配置可信代理
|
|||
|
|
expectedIP: "203.0.113.1", // 应该拒绝私有IP,使用remoteAddr
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Loopback in X-Forwarded-For",
|
|||
|
|
headers: map[string]string{"X-Forwarded-For": "127.0.0.1"},
|
|||
|
|
remoteAddr: "203.0.113.1:1234",
|
|||
|
|
trustedCIDR: nil,
|
|||
|
|
expectedIP: "203.0.113.1",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|||
|
|
req.RemoteAddr = tt.remoteAddr
|
|||
|
|
|
|||
|
|
for k, v := range tt.headers {
|
|||
|
|
req.Header.Set(k, v)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ip := getClientIP(req, tt.trustedCIDR...)
|
|||
|
|
|
|||
|
|
if ip != tt.expectedIP {
|
|||
|
|
t.Errorf("expected IP %s, got %s", tt.expectedIP, ip)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestGetClientIP_TrustedProxy
|
|||
|
|
// TDD: 配置了可信代理时,应该信任来自该代理的 X-Forwarded-For
|
|||
|
|
func TestGetClientIP_TrustedProxy(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
headers map[string]string
|
|||
|
|
remoteAddr string
|
|||
|
|
trustedCIDR []string
|
|||
|
|
expectedIP string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "Trusted proxy returns first IP",
|
|||
|
|
headers: map[string]string{"X-Forwarded-For": "203.0.113.1, 198.51.100.1"},
|
|||
|
|
remoteAddr: "10.0.0.1:1234",
|
|||
|
|
trustedCIDR: []string{"10.0.0.0/8"},
|
|||
|
|
expectedIP: "203.0.113.1",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Untrusted source ignores X-Forwarded-For",
|
|||
|
|
headers: map[string]string{"X-Forwarded-For": "203.0.113.1"},
|
|||
|
|
remoteAddr: "203.0.113.1:1234", // 公网IP
|
|||
|
|
trustedCIDR: []string{"10.0.0.0/8"}, // 10.0.0.1不在可信范围内
|
|||
|
|
expectedIP: "203.0.113.1",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|||
|
|
req.RemoteAddr = tt.remoteAddr
|
|||
|
|
|
|||
|
|
for k, v := range tt.headers {
|
|||
|
|
req.Header.Set(k, v)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ip := getClientIP(req, tt.trustedCIDR...)
|
|||
|
|
|
|||
|
|
if ip != tt.expectedIP {
|
|||
|
|
t.Errorf("expected IP %s, got %s", tt.expectedIP, ip)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIsTrustedProxy_WithCIDR [SEC-003]
|
|||
|
|
// 验证 isTrustedProxy 正确识别可信代理
|
|||
|
|
func TestIsTrustedProxy_WithCIDR(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
remoteAddr string
|
|||
|
|
trustedCIDR []string
|
|||
|
|
expected bool
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "Private network is trusted",
|
|||
|
|
remoteAddr: "10.0.0.5:1234",
|
|||
|
|
trustedCIDR: []string{"10.0.0.0/8"},
|
|||
|
|
expected: true,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Public IP is not trusted",
|
|||
|
|
remoteAddr: "203.0.113.1:1234",
|
|||
|
|
trustedCIDR: []string{"10.0.0.0/8"},
|
|||
|
|
expected: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "No trusted proxies configured",
|
|||
|
|
remoteAddr: "10.0.0.5:1234",
|
|||
|
|
trustedCIDR: nil,
|
|||
|
|
expected: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "192.168 network is trusted",
|
|||
|
|
remoteAddr: "192.168.1.1:1234",
|
|||
|
|
trustedCIDR: []string{"192.168.0.0/16"},
|
|||
|
|
expected: true,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
result := isTrustedProxy(tt.remoteAddr, tt.trustedCIDR)
|
|||
|
|
if result != tt.expected {
|
|||
|
|
t.Errorf("isTrustedProxy(%s, %v) = %v, expected %v", tt.remoteAddr, tt.trustedCIDR, result, tt.expected)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIsPublicIP [SEC-003]
|
|||
|
|
// 验证 isPublicIP 正确识别公网IP
|
|||
|
|
func TestIsPublicIP(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
ip string
|
|||
|
|
expected bool
|
|||
|
|
}{
|
|||
|
|
{"10.0.0.1", false},
|
|||
|
|
{"192.168.1.1", false},
|
|||
|
|
{"172.16.0.1", false},
|
|||
|
|
{"127.0.0.1", false},
|
|||
|
|
{"169.254.0.1", false},
|
|||
|
|
{"0.0.0.0", false},
|
|||
|
|
{"203.0.113.1", true},
|
|||
|
|
{"198.51.100.1", true},
|
|||
|
|
{"8.8.8.8", true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.ip, func(t *testing.T) {
|
|||
|
|
result := isPublicIP(tt.ip)
|
|||
|
|
if result != tt.expected {
|
|||
|
|
t.Errorf("isPublicIP(%s) = %v, expected %v", tt.ip, result, tt.expected)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isPublicIP 检查是否为公网IP
|
|||
|
|
func isPublicIP(ip string) bool {
|
|||
|
|
// 检查是否为私有IP范围
|
|||
|
|
if strings.HasPrefix(ip, "10.") {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if strings.HasPrefix(ip, "192.168.") {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if strings.HasPrefix(ip, "172.") {
|
|||
|
|
// 172.16.0.0 - 172.31.255.255
|
|||
|
|
parts := strings.Split(ip, ".")
|
|||
|
|
if len(parts) >= 2 {
|
|||
|
|
var secondOctet int
|
|||
|
|
fmt.Sscanf(parts[1], "%d", &secondOctet)
|
|||
|
|
if secondOctet >= 16 && secondOctet <= 31 {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if strings.HasPrefix(ip, "127.") {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if strings.HasPrefix(ip, "169.254.") {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
if strings.HasPrefix(ip, "0.") {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
return true
|
|||
|
|
}
|