297 lines
8.3 KiB
Go
297 lines
8.3 KiB
Go
//go:build unit
|
|||
|
|
|
||
|
|
package ip
|
||
|
|
|
||
|
|
import (
|
||
|
|
"net/http/httptest"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/gin-gonic/gin"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestGetTrustedClientIPUsesGinClientIP(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
c.String(200, GetTrustedClientIP(c))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
req.Header.Set("X-Forwarded-For", "1.2.3.4")
|
||
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
||
|
|
req.Header.Set("CF-Connecting-IP", "1.2.3.4")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, 200, w.Code)
|
||
|
|
require.Equal(t, "9.9.9.9", w.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetClientIPPreservesLegacyDockerForwardedHeaders(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
c.String(200, GetClientIP(c))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "192.168.32.1:12345"
|
||
|
|
req.Header.Set("X-Forwarded-For", "10.0.0.2, 203.0.113.42")
|
||
|
|
req.Header.Set("X-Real-IP", "192.168.32.1")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, 200, w.Code)
|
||
|
|
require.Equal(t, "203.0.113.42", w.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckIPRestrictionWithCompiledRules(t *testing.T) {
|
||
|
|
whitelist := CompileIPRules([]string{"10.0.0.0/8", "192.168.1.2"})
|
||
|
|
blacklist := CompileIPRules([]string{"10.1.1.1"})
|
||
|
|
|
||
|
|
allowed, reason := CheckIPRestrictionWithCompiledRules("10.2.3.4", whitelist, blacklist)
|
||
|
|
require.True(t, allowed)
|
||
|
|
require.Equal(t, "", reason)
|
||
|
|
|
||
|
|
allowed, reason = CheckIPRestrictionWithCompiledRules("10.1.1.1", whitelist, blacklist)
|
||
|
|
require.False(t, allowed)
|
||
|
|
require.Equal(t, "access denied", reason)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCheckIPRestrictionWithCompiledRules_InvalidWhitelistStillDenies(t *testing.T) {
|
||
|
|
// 与旧实现保持一致:白名单有配置但全无效时,最终应拒绝访问。
|
||
|
|
invalidWhitelist := CompileIPRules([]string{"not-a-valid-pattern"})
|
||
|
|
allowed, reason := CheckIPRestrictionWithCompiledRules("8.8.8.8", invalidWhitelist, nil)
|
||
|
|
require.False(t, allowed)
|
||
|
|
require.Equal(t, "access denied", reason)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSecurityClientIPSwitchEnabledUsesLegacyHeaders(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
c.String(200, GetSecurityClientIP(c, true))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, 200, w.Code)
|
||
|
|
require.Equal(t, "1.2.3.4", w.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSecurityClientIPCustomHeaderPrecedenceAndFallback(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
trustForward bool
|
||
|
|
headers []string
|
||
|
|
requestHeaders map[string]string
|
||
|
|
want string
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "configured order precedes built-ins",
|
||
|
|
trustForward: true,
|
||
|
|
headers: []string{"X-CDN-First", "X-CDN-Second"},
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"X-CDN-First": "198.51.100.10",
|
||
|
|
"X-CDN-Second": "203.0.113.20",
|
||
|
|
"CF-Connecting-IP": "8.8.8.8",
|
||
|
|
},
|
||
|
|
want: "198.51.100.10",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "comma candidates skip invalid and private values",
|
||
|
|
trustForward: true,
|
||
|
|
headers: []string{"X-CDN-First", "X-CDN-Second"},
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"X-CDN-First": "not-an-ip, 10.0.0.8",
|
||
|
|
"X-CDN-Second": "also-bad, 203.0.113.9",
|
||
|
|
},
|
||
|
|
want: "203.0.113.9",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "legacy public header wins over custom private fallback",
|
||
|
|
trustForward: true,
|
||
|
|
headers: []string{"X-CDN-IP"},
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"X-CDN-IP": "10.0.0.8",
|
||
|
|
"X-Real-IP": "1.2.3.4",
|
||
|
|
},
|
||
|
|
want: "1.2.3.4",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "custom private fallback retains configured precedence",
|
||
|
|
trustForward: true,
|
||
|
|
headers: []string{"X-CDN-IP"},
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"X-CDN-IP": "10.0.0.8",
|
||
|
|
"X-Real-IP": "192.168.1.4",
|
||
|
|
},
|
||
|
|
want: "10.0.0.8",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "invalid custom value continues to built-ins",
|
||
|
|
trustForward: true,
|
||
|
|
headers: []string{"X-CDN-IP"},
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"X-CDN-IP": "1.2.3.4:443",
|
||
|
|
"CF-Connecting-IP": "4.4.4.4",
|
||
|
|
},
|
||
|
|
want: "4.4.4.4",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "invalid legacy values continue to a valid forwarded address",
|
||
|
|
trustForward: true,
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"CF-Connecting-IP": "unknown",
|
||
|
|
"X-Real-IP": "proxy.internal",
|
||
|
|
"X-Forwarded-For": "also-invalid, 203.0.113.50",
|
||
|
|
},
|
||
|
|
want: "203.0.113.50",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "all invalid legacy values fall back to the connection address",
|
||
|
|
trustForward: true,
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"CF-Connecting-IP": "unknown",
|
||
|
|
"X-Real-IP": "proxy.internal",
|
||
|
|
"X-Forwarded-For": "also-invalid",
|
||
|
|
},
|
||
|
|
want: "9.9.9.9",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "disabled mode ignores custom and legacy headers",
|
||
|
|
trustForward: false,
|
||
|
|
headers: []string{"X-CDN-IP"},
|
||
|
|
requestHeaders: map[string]string{
|
||
|
|
"X-CDN-IP": "1.2.3.4",
|
||
|
|
"X-Real-IP": "4.4.4.4",
|
||
|
|
},
|
||
|
|
want: "9.9.9.9",
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, test := range tests {
|
||
|
|
t.Run(test.name, func(t *testing.T) {
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
SetForwardedIPSettings(c, test.trustForward, test.headers)
|
||
|
|
c.String(200, GetSecurityClientIP(c, !test.trustForward))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
for name, value := range test.requestHeaders {
|
||
|
|
req.Header.Set(name, value)
|
||
|
|
}
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, test.want, w.Body.String())
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSecurityClientIPSwitchDisabledUsesConfiguredTrustedProxy(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies([]string{"9.9.9.9"}))
|
||
|
|
r.GET("/t", func(c *gin.Context) { c.String(200, GetSecurityClientIP(c, false)) })
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
req.Header.Set("X-Forwarded-For", "1.2.3.4")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, "1.2.3.4", w.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetClientIPSwitchDisabledUsesTrustedProxyChain(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
SetLegacyForwardedIPTrust(c, false)
|
||
|
|
c.String(200, GetClientIP(c))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, "9.9.9.9", w.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSecurityClientIPRequestSnapshotCopiesCustomHeaders(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
headers := []string{"X-Original-IP"}
|
||
|
|
SetForwardedIPSettings(c, true, headers)
|
||
|
|
headers[0] = "X-Mutated-IP"
|
||
|
|
c.String(200, GetSecurityClientIP(c, false))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
req.Header.Set("X-Original-IP", "1.2.3.4")
|
||
|
|
req.Header.Set("X-Mutated-IP", "4.4.4.4")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, "1.2.3.4", w.Body.String())
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGetSecurityClientIPRequestSnapshotOverridesLiveFallback(t *testing.T) {
|
||
|
|
gin.SetMode(gin.TestMode)
|
||
|
|
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
requestTrust bool
|
||
|
|
fallbackTrust bool
|
||
|
|
want string
|
||
|
|
}{
|
||
|
|
{name: "captured secure mode wins", requestTrust: false, fallbackTrust: true, want: "9.9.9.9"},
|
||
|
|
{name: "captured compatibility mode wins", requestTrust: true, fallbackTrust: false, want: "1.2.3.4"},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, test := range tests {
|
||
|
|
t.Run(test.name, func(t *testing.T) {
|
||
|
|
r := gin.New()
|
||
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
||
|
|
r.GET("/t", func(c *gin.Context) {
|
||
|
|
SetLegacyForwardedIPTrust(c, test.requestTrust)
|
||
|
|
c.String(200, GetSecurityClientIP(c, test.fallbackTrust))
|
||
|
|
})
|
||
|
|
|
||
|
|
w := httptest.NewRecorder()
|
||
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
||
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
||
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
||
|
|
r.ServeHTTP(w, req)
|
||
|
|
|
||
|
|
require.Equal(t, test.want, w.Body.String())
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|