Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
//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())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user