SEC-001: 移除硬编码"123456"测试码 - 修改DefaultSMSVerifier.Verify返回错误,强制要求配置真实SMS服务 - 添加ErrSMSServiceNotConfigured错误定义 - 更新相关测试使用mock SMS verifier SEC-003: 添加IP欺骗防护 - AuthConfig添加TrustedProxies配置项 - getClientIP添加可信代理验证参数 - 仅在请求来自可信代理时信任X-Forwarded-For头 - 添加isTrustedProxy和containsCIDR辅助函数 架构重构: - 创建internal/adapter包,包含存储适配器 - 创建internal/outbox包,包含OutboxProcessorRunner - 创建internal/compensation包,包含补偿执行器 - main.go从891行减少到349行 TDD方法: - 为每个修复编写测试用例 - 测试通过后再提交代码
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
|
||
}
|