feat(security): 增强访问控制安全性,支持可信代理配置
- 新增 TrustedProxies 配置项,安全解析 X-Forwarded-For - 防止 IP 伪造攻击,仅信任来自可信代理的头部 - 使用右侧非可信 IP 作为真实客户端 IP - 改进连接数限制中间件集成 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ae8ea1ce0c
commit
98736e1f1c
@ -149,6 +149,7 @@ type AccessConfig struct {
|
|||||||
Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表
|
Allow []string `yaml:"allow"` // 允许的 IP/CIDR 列表
|
||||||
Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表
|
Deny []string `yaml:"deny"` // 拒绝的 IP/CIDR 列表
|
||||||
Default string `yaml:"default"` // 默认动作:allow 或 deny
|
Default string `yaml:"default"` // 默认动作:allow 或 deny
|
||||||
|
TrustedProxies []string `yaml:"trusted_proxies"` // 可信代理 CIDR 列表,用于 X-Forwarded-For 解析
|
||||||
}
|
}
|
||||||
|
|
||||||
// RateLimitConfig 速率限制配置。
|
// RateLimitConfig 速率限制配置。
|
||||||
|
|||||||
@ -59,6 +59,9 @@ type AccessControl struct {
|
|||||||
// defaultAction 默认操作,当无规则匹配时执行
|
// defaultAction 默认操作,当无规则匹配时执行
|
||||||
defaultAction Action
|
defaultAction Action
|
||||||
|
|
||||||
|
// trustedProxies 可信代理 CIDR 列表,用于安全解析 X-Forwarded-For
|
||||||
|
trustedProxies []net.IPNet
|
||||||
|
|
||||||
// mu 保护并发访问的读写锁
|
// mu 保护并发访问的读写锁
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
@ -98,6 +101,15 @@ func NewAccessControl(cfg *config.AccessConfig) (*AccessControl, error) {
|
|||||||
ac.denyList = append(ac.denyList, *network)
|
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) {
|
switch strings.ToLower(cfg.Default) {
|
||||||
case "allow", "":
|
case "allow", "":
|
||||||
@ -130,7 +142,7 @@ func (ac *AccessControl) Name() string {
|
|||||||
// - fasthttp.RequestHandler: 包装后的处理器
|
// - fasthttp.RequestHandler: 包装后的处理器
|
||||||
func (ac *AccessControl) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
func (ac *AccessControl) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
|
||||||
return func(ctx *fasthttp.RequestCtx) {
|
return func(ctx *fasthttp.RequestCtx) {
|
||||||
clientIP := getClientIP(ctx)
|
clientIP := ac.getClientIP(ctx)
|
||||||
|
|
||||||
// 检查访问权限
|
// 检查访问权限
|
||||||
if !ac.Check(clientIP) {
|
if !ac.Check(clientIP) {
|
||||||
@ -287,37 +299,83 @@ func parseCIDR(cidr string) (*net.IPNet, error) {
|
|||||||
return network, nil
|
return network, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClientIP 从请求上下文提取客户端 IP。
|
// getClientIP 从请求上下文安全提取客户端 IP。
|
||||||
//
|
//
|
||||||
// 按优先级依次检查:X-Forwarded-For、X-Real-IP、RemoteAddr。
|
// 仅当请求来自可信代理时,才解析 X-Forwarded-For 头部。
|
||||||
|
// 使用右侧(最接近客户端)的非可信 IP 作为真实客户端 IP。
|
||||||
//
|
//
|
||||||
// 参数:
|
// 参数:
|
||||||
// - ctx: FastHTTP 请求上下文
|
// - ctx: FastHTTP 请求上下文
|
||||||
//
|
//
|
||||||
// 返回值:
|
// 返回值:
|
||||||
// - net.IP: 客户端 IP 地址,无法获取时返回 nil
|
// - net.IP: 客户端 IP 地址,无法获取时返回 nil
|
||||||
func getClientIP(ctx *fasthttp.RequestCtx) net.IP {
|
func (ac *AccessControl) getClientIP(ctx *fasthttp.RequestCtx) net.IP {
|
||||||
// 优先检查 X-Forwarded-For 头部
|
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 {
|
if xff := ctx.Request.Header.Peek("X-Forwarded-For"); len(xff) > 0 {
|
||||||
ips := strings.Split(string(xff), ",")
|
ips := strings.Split(string(xff), ",")
|
||||||
if len(ips) > 0 {
|
for i := len(ips) - 1; i >= 0; i-- {
|
||||||
ipStr := strings.TrimSpace(ips[0])
|
ipStr := strings.TrimSpace(ips[i])
|
||||||
ip := net.ParseIP(ipStr)
|
if ip := net.ParseIP(ipStr); ip != nil {
|
||||||
if ip != nil {
|
// 检查该 IP 是否在可信代理列表中
|
||||||
|
trusted := false
|
||||||
|
for _, network := range ac.trustedProxies {
|
||||||
|
if network.Contains(ip) {
|
||||||
|
trusted = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !trusted {
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查 X-Real-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 xri := ctx.Request.Header.Peek("X-Real-IP"); len(xri) > 0 {
|
||||||
ip := net.ParseIP(string(xri))
|
if ip := net.ParseIP(string(xri)); ip != nil {
|
||||||
if ip != nil {
|
|
||||||
return ip
|
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 addr := ctx.RemoteAddr(); addr != nil {
|
||||||
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
||||||
return tcpAddr.IP
|
return tcpAddr.IP
|
||||||
@ -331,7 +389,6 @@ func getClientIP(ctx *fasthttp.RequestCtx) net.IP {
|
|||||||
ipStr = strings.TrimPrefix(strings.TrimSuffix(ipStr, "]"), "[")
|
ipStr = strings.TrimPrefix(strings.TrimSuffix(ipStr, "]"), "[")
|
||||||
return net.ParseIP(ipStr)
|
return net.ParseIP(ipStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -559,10 +560,11 @@ func HashPasswordBcrypt(password string, cost int) (string, error) {
|
|||||||
// - string: Argon2id 哈希字符串
|
// - string: Argon2id 哈希字符串
|
||||||
// - error: 生成失败时返回错误
|
// - error: 生成失败时返回错误
|
||||||
func HashPasswordArgon2id(password string, params argon2Params) (string, error) {
|
func HashPasswordArgon2id(password string, params argon2Params) (string, error) {
|
||||||
// 生成随机盐值
|
// 使用加密安全的随机数生成盐值
|
||||||
salt := make([]byte, params.saltLen)
|
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,
|
hash := argon2.IDKey([]byte(password), salt,
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
@ -602,14 +603,13 @@ func (m *connLimiterMiddleware) Process(next fasthttp.RequestHandler) fasthttp.R
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接数原子操作辅助函数
|
|
||||||
// 连接数原子操作辅助函数
|
// 连接数原子操作辅助函数
|
||||||
func loadInt64(ptr *int64) int64 {
|
func loadInt64(ptr *int64) int64 {
|
||||||
return *ptr // 生产环境应使用 sync/atomic
|
return atomic.LoadInt64(ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInt64(ptr *int64, delta int64) {
|
func addInt64(ptr *int64, delta int64) {
|
||||||
*ptr += delta // 简化实现;生产环境应使用 atomic.AddInt64
|
atomic.AddInt64(ptr, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证接口实现
|
// 验证接口实现
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user