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
154 lines
4.6 KiB
Go
154 lines
4.6 KiB
Go
//go:build unit
|
|
|
|
package middleware
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/ip"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSessionBindingContextFollowsForwardedIPSwitch(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
trustForwarded bool
|
|
trustedProxies []string
|
|
wantIP string
|
|
}{
|
|
{name: "enabled switch takes over raw headers", trustForwarded: true, wantIP: "1.2.3.4"},
|
|
{name: "disabled switch ignores untrusted headers", trustForwarded: false, wantIP: "127.0.0.1"},
|
|
{name: "disabled switch uses configured Gin proxy", trustForwarded: false, trustedProxies: []string{"127.0.0.1"}, wantIP: "1.2.3.4"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cfg := &config.Config{}
|
|
cfg.SetTrustForwardedIPForAPIKeyACL(tc.trustForwarded)
|
|
|
|
r := gin.New()
|
|
require.NoError(t, r.SetTrustedProxies(tc.trustedProxies))
|
|
r.Use(SessionBindingContext(cfg))
|
|
r.GET("/t", func(c *gin.Context) {
|
|
binding := service.SessionBindingFromContext(c.Request.Context())
|
|
require.NotNil(t, binding)
|
|
require.Equal(t, tc.wantIP, binding.IP)
|
|
require.Equal(t, "test-agent", binding.UserAgent)
|
|
require.Equal(t, tc.wantIP, SecurityClientIP(c))
|
|
c.Status(200)
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
|
req.RemoteAddr = "127.0.0.1:54321"
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
|
req.Header.Set("User-Agent", "test-agent")
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, 200, w.Code)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSessionBindingContextSnapshotsForwardedModeAndHeaders(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
cfg := &config.Config{}
|
|
cfg.SetForwardedClientIPSettings(true, []string{"X-Initial-IP"})
|
|
|
|
r := gin.New()
|
|
require.NoError(t, r.SetTrustedProxies(nil))
|
|
r.Use(SessionBindingContext(cfg))
|
|
r.GET("/t", func(c *gin.Context) {
|
|
binding := service.SessionBindingFromContext(c.Request.Context())
|
|
require.NotNil(t, binding)
|
|
require.Equal(t, "1.2.3.4", binding.IP)
|
|
|
|
cfg.SetForwardedClientIPSettings(false, []string{"X-Changed-IP"})
|
|
require.Equal(t, "1.2.3.4", ip.GetSecurityClientIP(c, false))
|
|
c.Status(200)
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
|
req.RemoteAddr = "9.9.9.9:12345"
|
|
req.Header.Set("X-Initial-IP", "1.2.3.4")
|
|
req.Header.Set("X-Changed-IP", "4.4.4.4")
|
|
req.Header.Set("X-Real-IP", "8.8.8.8")
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, 200, w.Code)
|
|
runtimeSettings := cfg.ForwardedClientIPSettings()
|
|
require.False(t, runtimeSettings.TrustForwardedIP)
|
|
require.Equal(t, []string{"X-Changed-IP"}, runtimeSettings.Headers)
|
|
}
|
|
|
|
func TestSessionBindingContextBoundsPersistedUserAgent(t *testing.T) {
|
|
cfg := &config.Config{}
|
|
r := gin.New()
|
|
r.Use(SessionBindingContext(cfg))
|
|
r.GET("/t", func(c *gin.Context) {
|
|
binding := service.SessionBindingFromContext(c.Request.Context())
|
|
require.Len(t, binding.UserAgent, maxPersistentUserAgentBytes)
|
|
require.Equal(t, binding.UserAgent, c.Request.UserAgent())
|
|
c.Status(200)
|
|
})
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
|
req.Header.Set("User-Agent", strings.Repeat("u", 2048))
|
|
r.ServeHTTP(w, req)
|
|
require.Equal(t, 200, w.Code)
|
|
}
|
|
|
|
// 未经过 SessionBindingContext 注入时(异常挂载顺序/单测直调),回退 trusted_proxies 链,
|
|
// 等价于开关关闭时的历史行为。
|
|
func TestSecurityClientIPFallsBackWithoutInjectedBinding(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, SecurityClientIP(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, 200, w.Code)
|
|
require.Equal(t, "9.9.9.9", w.Body.String())
|
|
}
|
|
|
|
func TestRequestSessionBindingPrefersInjectedBinding(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
cfg := &config.Config{}
|
|
cfg.SetTrustForwardedIPForAPIKeyACL(true)
|
|
|
|
r := gin.New()
|
|
require.NoError(t, r.SetTrustedProxies([]string{"127.0.0.1"}))
|
|
r.Use(SessionBindingContext(cfg))
|
|
r.GET("/t", func(c *gin.Context) {
|
|
issued := &service.SessionBinding{IP: "1.2.3.4", UserAgent: "test-agent"}
|
|
require.Equal(t, issued.Hash(), requestSessionBinding(c).Hash())
|
|
c.Status(200)
|
|
})
|
|
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/t", nil)
|
|
req.RemoteAddr = "127.0.0.1:54321"
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
|
req.Header.Set("User-Agent", "test-agent")
|
|
r.ServeHTTP(w, req)
|
|
|
|
require.Equal(t, 200, w.Code)
|
|
}
|