fix(security): trusted-proxy-scoped login rate limit

This commit is contained in:
2026-09-07 12:44:47 +08:00
parent f7c27272ba
commit 6adbab44c3
5 changed files with 71 additions and 21 deletions
+21
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
@@ -111,6 +112,26 @@ func TestLoginMe(t *testing.T) {
}
}
// 伪造 XFF 换不了限流桶: peer(192.0.2.1)不在可信代理段 → ClientIP 取 peer,XFF 忽略
func TestLoginRateLimitResistsXFFSpoof(t *testing.T) {
if os.Getenv("REDIS_URL") == "" {
t.Skip("REDIS_URL not set (no redis → IncrWindow always allows)")
}
_, _, h, _ := setupAPI(t) // setupAPI 已重置 loginrl:192.0.2.1
for i := 1; i <= 6; i++ {
req := httptest.NewRequest("POST", "/api/auth/login", bytes.NewBufferString(`{"username":"alice","password":"nope"}`))
req.Header.Set("X-Forwarded-For", fmt.Sprintf("203.0.113.%d", i))
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if i < 6 && w.Code != http.StatusUnauthorized {
t.Fatalf("attempt %d: want 401 got %d %s", i, w.Code, w.Body)
}
if i == 6 && w.Code != http.StatusTooManyRequests {
t.Fatalf("attempt 6: spoofed XFF escaped per-peer limit: want 429 got %d", w.Code)
}
}
}
func TestMemberCannotWriteUsers(t *testing.T) {
_, _, h, _ := setupAPI(t)
w := do(h, "POST", "/api/auth/login", "", map[string]string{"username": "bob", "password": testPW})
+3
View File
@@ -15,6 +15,9 @@ func NewRouter(cfg *config.Config, st *store.Store, rdb *redispkg.R, sc *scanner
gin.SetMode(gin.ReleaseMode)
a := &api{cfg: cfg, st: st, rdb: rdb, sc: sc}
r := gin.New()
if e := r.SetTrustedProxies(cfg.TrustedProxies); e != nil {
panic(e)
}
r.Use(gin.Recovery())
g := r.Group("/api")
g.GET("/healthz", func(c *gin.Context) { c.String(http.StatusOK, "ok") })
+2 -1
View File
@@ -18,7 +18,8 @@ import (
)
func testCfg() *config.Config {
return &config.Config{Addr: ":8080", JWTSecret: []byte("s3cret"), ScanInterval: time.Minute, UploadMaxMB: 200}
return &config.Config{Addr: ":8080", JWTSecret: []byte("s3cret"), ScanInterval: time.Minute, UploadMaxMB: 200,
TrustedProxies: []string{"172.16.0.0/12"}} // 与 prod 默认一致: 只有 compose 网段内代理才可信
}
func TestHealthz(t *testing.T) {