From 98736e1f1c1171bdc2188bc8c93a15ea1655fc43 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 3 Apr 2026 16:57:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(security):=20=E5=A2=9E=E5=BC=BA=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E6=8E=A7=E5=88=B6=E5=AE=89=E5=85=A8=E6=80=A7=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8F=AF=E4=BF=A1=E4=BB=A3=E7=90=86=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 TrustedProxies 配置项,安全解析 X-Forwarded-For - 防止 IP 伪造攻击,仅信任来自可信代理的头部 - 使用右侧非可信 IP 作为真实客户端 IP - 改进连接数限制中间件集成 Co-Authored-By: Claude --- internal/config/config.go | 7 +- internal/middleware/security/access.go | 95 ++++++++++++++++++----- internal/middleware/security/auth.go | 8 +- internal/middleware/security/ratelimit.go | 6 +- 4 files changed, 88 insertions(+), 28 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index a80ef43..a4038fe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -146,9 +146,10 @@ type SecurityConfig struct { // AccessConfig IP 访问控制配置。 type AccessConfig struct { - Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表 - Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表 - Default string `yaml:"default"` // 默认动作:allow 或 deny + Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表 + Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表 + Default string `yaml:"default"` // 默认动作:allow 或 deny + TrustedProxies []string `yaml:"trusted_proxies"` // 可信代理 CIDR 列表,用于 X-Forwarded-For 解析 } // RateLimitConfig 速率限制配置。 diff --git a/internal/middleware/security/access.go b/internal/middleware/security/access.go index 7eec1de..102e0cb 100644 --- a/internal/middleware/security/access.go +++ b/internal/middleware/security/access.go @@ -59,6 +59,9 @@ type AccessControl struct { // defaultAction 默认操作,当无规则匹配时执行 defaultAction Action + // trustedProxies 可信代理 CIDR 列表,用于安全解析 X-Forwarded-For + trustedProxies []net.IPNet + // mu 保护并发访问的读写锁 mu sync.RWMutex } @@ -98,6 +101,15 @@ func NewAccessControl(cfg *config.AccessConfig) (*AccessControl, error) { ac.denyList = append(ac.denyList, *network) } + // 解析可信代理列表 + for _, cidr := range cfg.TrustedProxies { + network, err := parseCIDR(cidr) + if err != nil { + return nil, fmt.Errorf("invalid trusted_proxy CIDR %s: %w", cidr, err) + } + ac.trustedProxies = append(ac.trustedProxies, *network) + } + // 设置默认操作 switch strings.ToLower(cfg.Default) { case "allow", "": @@ -130,7 +142,7 @@ func (ac *AccessControl) Name() string { // - fasthttp.RequestHandler: 包装后的处理器 func (ac *AccessControl) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler { return func(ctx *fasthttp.RequestCtx) { - clientIP := getClientIP(ctx) + clientIP := ac.getClientIP(ctx) // 检查访问权限 if !ac.Check(clientIP) { @@ -287,37 +299,83 @@ func parseCIDR(cidr string) (*net.IPNet, error) { return network, nil } -// getClientIP 从请求上下文提取客户端 IP。 +// getClientIP 从请求上下文安全提取客户端 IP。 // -// 按优先级依次检查:X-Forwarded-For、X-Real-IP、RemoteAddr。 +// 仅当请求来自可信代理时,才解析 X-Forwarded-For 头部。 +// 使用右侧(最接近客户端)的非可信 IP 作为真实客户端 IP。 // // 参数: // - ctx: FastHTTP 请求上下文 // // 返回值: // - net.IP: 客户端 IP 地址,无法获取时返回 nil -func getClientIP(ctx *fasthttp.RequestCtx) net.IP { - // 优先检查 X-Forwarded-For 头部 - if xff := ctx.Request.Header.Peek("X-Forwarded-For"); len(xff) > 0 { - ips := strings.Split(string(xff), ",") - if len(ips) > 0 { - ipStr := strings.TrimSpace(ips[0]) - ip := net.ParseIP(ipStr) - if ip != nil { - return ip +func (ac *AccessControl) getClientIP(ctx *fasthttp.RequestCtx) net.IP { + remoteIP := getRemoteAddrIP(ctx) + + // 仅当配置了可信代理且请求来自可信代理时,才解析 X-Forwarded-For + if len(ac.trustedProxies) > 0 && remoteIP != nil { + isTrusted := false + for _, network := range ac.trustedProxies { + if network.Contains(remoteIP) { + isTrusted = true + break + } + } + + if isTrusted { + // 使用右侧(最接近客户端)的非可信 IP + if xff := ctx.Request.Header.Peek("X-Forwarded-For"); len(xff) > 0 { + ips := strings.Split(string(xff), ",") + for i := len(ips) - 1; i >= 0; i-- { + ipStr := strings.TrimSpace(ips[i]) + if ip := net.ParseIP(ipStr); ip != nil { + // 检查该 IP 是否在可信代理列表中 + trusted := false + for _, network := range ac.trustedProxies { + if network.Contains(ip) { + trusted = true + break + } + } + if !trusted { + return ip + } + } + } } } } - // 检查 X-Real-IP 头部 - if xri := ctx.Request.Header.Peek("X-Real-IP"); len(xri) > 0 { - ip := net.ParseIP(string(xri)) - if ip != nil { - return ip + // 检查 X-Real-IP 头部(仅来自可信代理时) + if len(ac.trustedProxies) > 0 && remoteIP != nil { + isTrusted := false + for _, network := range ac.trustedProxies { + if network.Contains(remoteIP) { + isTrusted = true + break + } + } + + if isTrusted { + if xri := ctx.Request.Header.Peek("X-Real-IP"); len(xri) > 0 { + if ip := net.ParseIP(string(xri)); ip != nil { + return ip + } + } } } - // 回退到 RemoteAddr + return remoteIP +} + +// getRemoteAddrIP 从 RemoteAddr 提取 IP。 +// +// 参数: +// - ctx: FastHTTP 请求上下文 +// +// 返回值: +// - net.IP: 客户端 IP 地址,无法获取时返回 nil +func getRemoteAddrIP(ctx *fasthttp.RequestCtx) net.IP { if addr := ctx.RemoteAddr(); addr != nil { if tcpAddr, ok := addr.(*net.TCPAddr); ok { return tcpAddr.IP @@ -331,7 +389,6 @@ func getClientIP(ctx *fasthttp.RequestCtx) net.IP { ipStr = strings.TrimPrefix(strings.TrimSuffix(ipStr, "]"), "[") return net.ParseIP(ipStr) } - return nil } diff --git a/internal/middleware/security/auth.go b/internal/middleware/security/auth.go index e4bfb31..5981b71 100644 --- a/internal/middleware/security/auth.go +++ b/internal/middleware/security/auth.go @@ -28,6 +28,7 @@ package security import ( + "crypto/rand" "encoding/base64" "errors" "fmt" @@ -559,10 +560,11 @@ func HashPasswordBcrypt(password string, cost int) (string, error) { // - string: Argon2id 哈希字符串 // - error: 生成失败时返回错误 func HashPasswordArgon2id(password string, params argon2Params) (string, error) { - // 生成随机盐值 + // 使用加密安全的随机数生成盐值 salt := make([]byte, params.saltLen) - // 注意:生产环境应使用 crypto/rand 生成盐值 - // 此工具函数使用占位符方法 + if _, err := rand.Read(salt); err != nil { + return "", fmt.Errorf("failed to generate salt: %w", err) + } // 生成哈希 hash := argon2.IDKey([]byte(password), salt, diff --git a/internal/middleware/security/ratelimit.go b/internal/middleware/security/ratelimit.go index 74e8490..e95e62b 100644 --- a/internal/middleware/security/ratelimit.go +++ b/internal/middleware/security/ratelimit.go @@ -35,6 +35,7 @@ import ( "net" "strings" "sync" + "sync/atomic" "time" "github.com/valyala/fasthttp" @@ -602,14 +603,13 @@ func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.R } } -// 连接数原子操作辅助函数 // 连接数原子操作辅助函数 func loadInt64(ptr *int64) int64 { - return *ptr // 生产环境应使用 sync/atomic + return atomic.LoadInt64(ptr) } func addInt64(ptr *int64, delta int64) { - *ptr += delta // 简化实现;生产环境应使用 atomic.AddInt64 + atomic.AddInt64(ptr, delta) } // 验证接口实现