mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
fix: 统一请求级客户端 IP 模式
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -8,6 +8,24 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const legacyForwardedIPTrustKey = "sub2api.legacy_forwarded_ip_trust"
|
||||
|
||||
// SetLegacyForwardedIPTrust records whether raw forwarding headers override
|
||||
// Gin's server.trusted_proxies chain for this request.
|
||||
func SetLegacyForwardedIPTrust(c *gin.Context, enabled bool) {
|
||||
if c != nil {
|
||||
c.Set(legacyForwardedIPTrustKey, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
func requestUsesLegacyForwardedIPTrust(c *gin.Context) bool {
|
||||
if c == nil {
|
||||
return true
|
||||
}
|
||||
enabled, ok := c.Get(legacyForwardedIPTrustKey)
|
||||
return !ok || enabled == true
|
||||
}
|
||||
|
||||
// GetClientIP resolves the client address using the legacy forwarding-header
|
||||
// precedence used before the trusted-proxy hardening. It remains the
|
||||
// compatibility path for request metadata and usage/error logs; security-
|
||||
@@ -16,6 +34,9 @@ func GetClientIP(c *gin.Context) string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
if !requestUsesLegacyForwardedIPTrust(c) {
|
||||
return GetTrustedClientIP(c)
|
||||
}
|
||||
|
||||
// Preserve the historical precedence used by existing reverse-proxy
|
||||
// deployments, while skipping an internal proxy address when a public XFF
|
||||
@@ -66,9 +87,18 @@ func GetTrustedClientIP(c *gin.Context) string {
|
||||
}
|
||||
|
||||
// GetSecurityClientIP returns the address used by security-sensitive paths.
|
||||
// The legacy toggle remains in the signature for configuration/API
|
||||
// compatibility, but cannot make raw forwarding headers trustworthy by itself.
|
||||
func GetSecurityClientIP(c *gin.Context, _ bool) string {
|
||||
// When legacy forwarded-IP trust is enabled, raw forwarding headers take over
|
||||
// client-IP resolution. When disabled, Gin's server.trusted_proxies chain is
|
||||
// authoritative.
|
||||
func GetSecurityClientIP(c *gin.Context, trustForwarded bool) string {
|
||||
if c != nil {
|
||||
if requestTrust, ok := c.Get(legacyForwardedIPTrustKey); ok {
|
||||
trustForwarded = requestTrust == true
|
||||
}
|
||||
}
|
||||
if trustForwarded {
|
||||
return GetClientIP(c)
|
||||
}
|
||||
return GetTrustedClientIP(c)
|
||||
}
|
||||
|
||||
|
||||
@@ -73,41 +73,30 @@ func TestCheckIPRestrictionWithCompiledRules_InvalidWhitelistStillDenies(t *test
|
||||
require.Equal(t, "access denied", reason)
|
||||
}
|
||||
|
||||
func TestGetSecurityClientIPNeverTrustsHeadersFromUntrustedPeer(t *testing.T) {
|
||||
func TestGetSecurityClientIPSwitchEnabledUsesLegacyHeaders(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
trustForwarded bool
|
||||
want string
|
||||
}{
|
||||
{name: "legacy toggle disabled", trustForwarded: false, want: "9.9.9.9"},
|
||||
{name: "legacy toggle enabled", trustForwarded: true, want: "9.9.9.9"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := gin.New()
|
||||
require.NoError(t, r.SetTrustedProxies(nil))
|
||||
r.GET("/t", func(c *gin.Context) {
|
||||
c.String(200, GetSecurityClientIP(c, tc.trustForwarded))
|
||||
})
|
||||
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)
|
||||
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, tc.want, w.Body.String())
|
||||
})
|
||||
}
|
||||
require.Equal(t, 200, w.Code)
|
||||
require.Equal(t, "1.2.3.4", w.Body.String())
|
||||
}
|
||||
|
||||
func TestGetSecurityClientIPUsesConfiguredTrustedProxy(t *testing.T) {
|
||||
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, true)) })
|
||||
r.GET("/t", func(c *gin.Context) { c.String(200, GetSecurityClientIP(c, false)) })
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/t", nil)
|
||||
@@ -117,3 +106,54 @@ func TestGetSecurityClientIPUsesConfiguredTrustedProxy(t *testing.T) {
|
||||
|
||||
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 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