| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- //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())
- })
- }
- }
|