mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2025-12-06 14:59:18 +08:00
Merge pull request #548 from samwafgo/feat_banIP_moreinfo
feat: add ip failure
This commit is contained in:
@@ -12,6 +12,7 @@ type APIGroup struct {
|
||||
WafAllowUrlApi
|
||||
WafLdpUrlApi
|
||||
WafAntiCCApi
|
||||
WafIPFailureApi
|
||||
WafBlockIpApi
|
||||
WafBlockUrlApi
|
||||
WafAccountApi
|
||||
|
||||
170
api/waf_ip_failure.go
Normal file
170
api/waf_ip_failure.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"SamWaf/enums"
|
||||
"SamWaf/global"
|
||||
"SamWaf/model/common/response"
|
||||
"SamWaf/model/request"
|
||||
response2 "SamWaf/model/response"
|
||||
"SamWaf/utils"
|
||||
"SamWaf/wafipban"
|
||||
"SamWaf/waftask"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type WafIPFailureApi struct {
|
||||
}
|
||||
|
||||
// GetConfigApi 获取IP失败封禁配置
|
||||
func (w *WafIPFailureApi) GetConfigApi(c *gin.Context) {
|
||||
var req request.WafIPFailureConfigReq
|
||||
err := c.ShouldBind(&req)
|
||||
if err == nil {
|
||||
configResp := response2.IPFailureConfigResp{
|
||||
Enabled: global.GCONFIG_IP_FAILURE_BAN_ENABLED,
|
||||
StatusCodes: global.GCONFIG_IP_FAILURE_STATUS_CODES,
|
||||
LockTime: global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME,
|
||||
}
|
||||
response.OkWithDetailed(configResp, "获取成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfigApi 设置IP失败封禁配置
|
||||
func (w *WafIPFailureApi) SetConfigApi(c *gin.Context) {
|
||||
var req request.WafIPFailureSetConfigReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
// 更新配置
|
||||
// 1. 更新启用状态
|
||||
config := wafSystemConfigService.GetDetailByItemApi(request.WafSystemConfigDetailByItemReq{Item: "ip_failure_ban_enabled"})
|
||||
wafSystemConfigService.ModifyApi(request.WafSystemConfigEditReq{
|
||||
Id: config.Id,
|
||||
Item: config.Item,
|
||||
ItemClass: config.ItemClass,
|
||||
Value: fmt.Sprintf("%d", req.Enabled),
|
||||
Remarks: config.Remarks,
|
||||
ItemType: config.ItemType,
|
||||
Options: config.Options,
|
||||
})
|
||||
|
||||
// 2. 更新状态码
|
||||
config = wafSystemConfigService.GetDetailByItemApi(request.WafSystemConfigDetailByItemReq{Item: "ip_failure_status_codes"})
|
||||
wafSystemConfigService.ModifyApi(request.WafSystemConfigEditReq{
|
||||
Id: config.Id,
|
||||
Item: config.Item,
|
||||
ItemClass: config.ItemClass,
|
||||
Value: req.StatusCodes,
|
||||
Remarks: config.Remarks,
|
||||
ItemType: config.ItemType,
|
||||
Options: config.Options,
|
||||
})
|
||||
|
||||
// 3. 更新锁定时间
|
||||
config = wafSystemConfigService.GetDetailByItemApi(request.WafSystemConfigDetailByItemReq{Item: "ip_failure_ban_lock_time"})
|
||||
wafSystemConfigService.ModifyApi(request.WafSystemConfigEditReq{
|
||||
Id: config.Id,
|
||||
Item: config.Item,
|
||||
ItemClass: config.ItemClass,
|
||||
Value: fmt.Sprintf("%d", req.LockTime),
|
||||
Remarks: config.Remarks,
|
||||
ItemType: config.ItemType,
|
||||
Options: config.Options,
|
||||
})
|
||||
|
||||
// 重新加载配置
|
||||
waftask.TaskLoadSetting(true)
|
||||
|
||||
response.OkWithMessage("设置成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBanIpListApi 获取被封禁的IP列表
|
||||
func (w *WafIPFailureApi) GetBanIpListApi(c *gin.Context) {
|
||||
// 获取所有IP失败记录
|
||||
banIpList := global.GCACHE_WAFCACHE.ListAvailableKeysWithPrefix(enums.CACHE_IP_FAILURE_PRE)
|
||||
beans := make([]response2.IPFailureIpResp, 0, len(banIpList))
|
||||
|
||||
manager := wafipban.GetIPFailureManager()
|
||||
|
||||
// 遍历 banIpList,将每个 IP 信息添加到 beans 中
|
||||
for banKey, duration := range banIpList {
|
||||
// 去掉 IP 的前缀
|
||||
ip := strings.TrimPrefix(banKey, enums.CACHE_IP_FAILURE_PRE)
|
||||
|
||||
// 获取IP失败详细信息
|
||||
record := manager.GetFailureInfo(ip)
|
||||
if record == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 只显示满足条件的IP(即有阈值记录的IP,表示是被规则封禁的)
|
||||
if record.TriggerMinutes == 0 || record.TriggerCount == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 将剩余时间格式化:小于1分钟显示秒,否则显示时和分
|
||||
var remainTime string
|
||||
totalMinutes := int(duration.Minutes())
|
||||
if totalMinutes < 1 {
|
||||
// 小于1分钟,显示秒
|
||||
seconds := int(duration.Seconds())
|
||||
remainTime = fmt.Sprintf("%d秒", seconds)
|
||||
} else {
|
||||
// 大于等于1分钟,显示时和分
|
||||
hours := int(duration.Hours())
|
||||
minutes := totalMinutes % 60
|
||||
if hours > 0 {
|
||||
remainTime = fmt.Sprintf("%d时%02d分", hours, minutes)
|
||||
} else {
|
||||
remainTime = fmt.Sprintf("%d分", minutes)
|
||||
}
|
||||
}
|
||||
|
||||
region := utils.GetCountry(ip)
|
||||
|
||||
// 将信息添加到 beans 中
|
||||
beans = append(beans, response2.IPFailureIpResp{
|
||||
IP: ip,
|
||||
FailCount: record.Count,
|
||||
FirstTime: record.FirstTime.Format("2006-01-02 15:04:05"),
|
||||
LastTime: record.LastTime.Format("2006-01-02 15:04:05"),
|
||||
RemainTime: remainTime,
|
||||
Region: fmt.Sprintf("%v", region),
|
||||
TriggerMinutes: record.TriggerMinutes,
|
||||
TriggerCount: record.TriggerCount,
|
||||
})
|
||||
}
|
||||
|
||||
// 计算总条目数
|
||||
total := len(beans)
|
||||
|
||||
// 返回带分页信息的响应
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: beans,
|
||||
Total: int64(total),
|
||||
PageIndex: 1,
|
||||
PageSize: 999999,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// RemoveIPFailureBanIPApi 移除被封禁的IP
|
||||
func (w *WafIPFailureApi) RemoveIPFailureBanIPApi(c *gin.Context) {
|
||||
var req request.WafIPFailureRemoveBanIpReq
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err == nil {
|
||||
// 直接清除失败记录窗口累积
|
||||
manager := wafipban.GetIPFailureManager()
|
||||
manager.ClearIPFailure(req.Ip)
|
||||
global.GCACHE_WAFCACHE.Remove(enums.CACHE_CCVISITBAN_PRE + req.Ip)
|
||||
response.OkWithMessage(req.Ip+" 移除成功", c)
|
||||
} else {
|
||||
response.FailWithMessage("解析失败", c)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ security:
|
||||
soft_id: SamWafCom
|
||||
user_code: 8ad2bca0-4fd0-46aa-afe6-833132a05eee
|
||||
zlog:
|
||||
db_debug_enable: false
|
||||
db_debug_enable: true
|
||||
debug_enable: false
|
||||
outputformat: console
|
||||
|
||||
@@ -55,9 +55,8 @@ var (
|
||||
GCONFIG_RECORD_GPT_MODEL string = "deepseek-chat" //GPT 模型名称
|
||||
|
||||
// IP失败封禁相关配置
|
||||
GCONFIG_IP_FAILURE_STATUS_CODES string = "401|403|404|444|429|503" //失败状态码配置,支持多个用|分隔,也支持正则表达式
|
||||
GCONFIG_IP_FAILURE_BAN_ENABLED int64 = 0 //是否启用IP失败封禁 1启用 0禁用
|
||||
GCONFIG_IP_FAILURE_BAN_TIME_WINDOW int64 = 5 //IP失败封禁时间窗口(分钟)默认5分钟
|
||||
GCONFIG_IP_FAILURE_BAN_MAX_COUNT int64 = 10 //IP失败封禁最大失败次数 默认10次
|
||||
GCONFIG_IP_FAILURE_STATUS_CODES string = "401|403|404|444|429|503" //失败状态码配置,支持多个用|分隔,也支持正则表达式
|
||||
GCONFIG_IP_FAILURE_BAN_ENABLED int64 = 0 //是否启用IP失败封禁 1启用 0禁用
|
||||
GCONFIG_IP_FAILURE_BAN_LOCK_TIME int64 = 10 //IP失败封禁锁定时间(分钟)默认10分钟
|
||||
|
||||
)
|
||||
|
||||
@@ -123,6 +123,33 @@ func (w *WebLog) GetIPFailureCount(minutes int64) int64 {
|
||||
return getIPFailureCount(w.SRC_IP, minutes)
|
||||
}
|
||||
|
||||
// RecordIPFailureThreshold 记录IP失败封禁的阈值信息(当规则匹配时调用)
|
||||
// minutes: 触发封禁的时间窗口(分钟)
|
||||
// count: 触发封禁的失败次数阈值
|
||||
func (w *WebLog) RecordIPFailureThreshold(minutes int64, count int64) {
|
||||
if w.SRC_IP == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是bot且危险程度是0,不记录阈值
|
||||
if w.IsBot == 1 && w.RISK_LEVEL == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是证书申请路径,不记录阈值
|
||||
if getSSLChallengePath != nil {
|
||||
sslPath := getSSLChallengePath()
|
||||
if sslPath != "" && strings.HasPrefix(w.URL, sslPath) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 调用IP失败管理器记录阈值(延迟导入避免编译时循环依赖)
|
||||
if recordIPFailureThreshold != nil {
|
||||
recordIPFailureThreshold(w.SRC_IP, minutes, count)
|
||||
}
|
||||
}
|
||||
|
||||
// getSSLChallengePath 获取SSL证书验证路径(延迟导入避免循环依赖)
|
||||
var getSSLChallengePath func() string
|
||||
|
||||
@@ -139,6 +166,14 @@ func SetIPFailureCountGetter(fn func(string, int64) int64) {
|
||||
getIPFailureCount = fn
|
||||
}
|
||||
|
||||
// recordIPFailureThreshold 记录IP失败封禁阈值(通过函数变量实现延迟导入)
|
||||
var recordIPFailureThreshold func(string, int64, int64)
|
||||
|
||||
// SetIPFailureThresholdRecorder 设置IP失败封禁阈值记录函数
|
||||
func SetIPFailureThresholdRecorder(fn func(string, int64, int64)) {
|
||||
recordIPFailureThreshold = fn
|
||||
}
|
||||
|
||||
type WAFLog struct {
|
||||
REQ_UUID string `json:"req_uuid"`
|
||||
ACTION string `json:"action"`
|
||||
|
||||
2
main.go
2
main.go
@@ -212,7 +212,7 @@ func (m *wafSystenService) run() {
|
||||
global.GWAF_OWASP = wafowasp.NewWafOWASP(true, utils.GetCurrentDir())
|
||||
|
||||
// 初始化ip ban
|
||||
wafipban.InitIPBanManager()
|
||||
wafipban.InitIPBanManager(global.GCACHE_WAFCACHE)
|
||||
|
||||
//提前初始化
|
||||
global.GDATA_CURRENT_LOG_DB_MAP = map[string]*gorm.DB{}
|
||||
|
||||
17
model/request/waf_ip_failure_req.go
Normal file
17
model/request/waf_ip_failure_req.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package request
|
||||
|
||||
// WafIPFailureConfigReq 获取IP失败封禁配置请求
|
||||
type WafIPFailureConfigReq struct {
|
||||
}
|
||||
|
||||
// WafIPFailureSetConfigReq 设置IP失败封禁配置请求
|
||||
type WafIPFailureSetConfigReq struct {
|
||||
Enabled int64 `json:"enabled" form:"enabled"` // 是否启用IP失败封禁 1启用 0禁用
|
||||
StatusCodes string `json:"status_codes" form:"status_codes"` // 失败状态码配置
|
||||
LockTime int64 `json:"lock_time" form:"lock_time"` // 封禁锁定时间(分钟)
|
||||
}
|
||||
|
||||
// WafIPFailureRemoveBanIpReq 移除封禁IP请求
|
||||
type WafIPFailureRemoveBanIpReq struct {
|
||||
Ip string `json:"ip" form:"ip"` //移除封禁IP
|
||||
}
|
||||
20
model/response/ip_failure_resp.go
Normal file
20
model/response/ip_failure_resp.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package response
|
||||
|
||||
// IPFailureConfigResp IP失败封禁配置响应
|
||||
type IPFailureConfigResp struct {
|
||||
Enabled int64 `json:"enabled"` // 是否启用IP失败封禁 1启用 0禁用
|
||||
StatusCodes string `json:"status_codes"` // 失败状态码配置
|
||||
LockTime int64 `json:"lock_time"` // 封禁锁定时间(分钟)
|
||||
}
|
||||
|
||||
// IPFailureIpResp IP失败封禁IP响应
|
||||
type IPFailureIpResp struct {
|
||||
IP string `json:"ip"` // IP地址
|
||||
FailCount int64 `json:"fail_count"` // 失败次数
|
||||
FirstTime string `json:"first_time"` // 首次失败时间
|
||||
LastTime string `json:"last_time"` // 最后失败时间
|
||||
RemainTime string `json:"remain_time"` // 剩余封禁时间
|
||||
Region string `json:"region"` // IP归属地
|
||||
TriggerMinutes int64 `json:"trigger_minutes"` // 触发封禁的时间窗口(分钟)
|
||||
TriggerCount int64 `json:"trigger_count"` // 触发封禁的失败次数阈值
|
||||
}
|
||||
@@ -10,6 +10,7 @@ type ApiGroup struct {
|
||||
AllowUrlRouter
|
||||
LdpUrlRouter
|
||||
AntiCCRouter
|
||||
IPFailureRouter
|
||||
BlockIpRouter
|
||||
BlockUrlRouter
|
||||
AccountRouter
|
||||
|
||||
19
router/waf_ip_failure.go
Normal file
19
router/waf_ip_failure.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SamWaf/api"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type IPFailureRouter struct {
|
||||
}
|
||||
|
||||
func (receiver *IPFailureRouter) InitIPFailureRouter(group *gin.RouterGroup) {
|
||||
api := api.APIGroupAPP.WafIPFailureApi
|
||||
router := group.Group("")
|
||||
router.GET("/samwaf/wafhost/ipfailure/config", api.GetConfigApi)
|
||||
router.POST("/samwaf/wafhost/ipfailure/config", api.SetConfigApi)
|
||||
router.GET("/samwaf/wafhost/ipfailure/baniplist", api.GetBanIpListApi)
|
||||
router.POST("/samwaf/wafhost/ipfailure/removebanip", api.RemoveIPFailureBanIPApi)
|
||||
}
|
||||
@@ -8,8 +8,27 @@ import (
|
||||
"SamWaf/model/wafenginmodel"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// parseIPFailureThreshold 从规则文本中解析IP失败阈值信息
|
||||
// 解析类似 "MF.GetIPFailureCount(5) > 10" 的模式
|
||||
// 返回: minutes, count, 是否找到
|
||||
func parseIPFailureThreshold(ruleText string) (int64, int64, bool) {
|
||||
// 匹配 GetIPFailureCount(数字) > 数字 或 GetIPFailureCount(数字) >= 数字 的模式
|
||||
re := regexp.MustCompile(`GetIPFailureCount\s*\(\s*(\d+)\s*\)\s*[>=]+\s*(\d+)`)
|
||||
matches := re.FindStringSubmatch(ruleText)
|
||||
if len(matches) >= 3 {
|
||||
minutes, err1 := strconv.ParseInt(matches[1], 10, 64)
|
||||
count, err2 := strconv.ParseInt(matches[2], 10, 64)
|
||||
if err1 == nil && err2 == nil {
|
||||
return minutes, count, true
|
||||
}
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
检测rule
|
||||
@@ -30,6 +49,13 @@ func (waf *WafEngine) CheckRule(r *http.Request, weblogbean *innerbean.WebLog, f
|
||||
rulestr := ""
|
||||
for _, v := range ruleMatchs {
|
||||
rulestr = rulestr + v.RuleDescription + ","
|
||||
|
||||
minutes, count, found := parseIPFailureThreshold(v.GrlText)
|
||||
if found {
|
||||
// 记录阈值信息
|
||||
weblogbean.RecordIPFailureThreshold(minutes, count)
|
||||
}
|
||||
|
||||
}
|
||||
weblogbean.RISK_LEVEL = 1
|
||||
|
||||
@@ -53,6 +79,18 @@ func (waf *WafEngine) CheckRule(r *http.Request, weblogbean *innerbean.WebLog, f
|
||||
for _, v := range ruleMatchs {
|
||||
rulestr = rulestr + v.RuleDescription + ","
|
||||
}
|
||||
// 尝试从规则数据中解析阈值信息
|
||||
// 遍历规则数据,查找包含 GetIPFailureCount 的规则
|
||||
for _, ruleData := range waf.HostTarget[global.GWAF_GLOBAL_HOST_NAME].RuleData {
|
||||
if ruleData.RuleContent != "" {
|
||||
minutes, count, found := parseIPFailureThreshold(ruleData.RuleContent)
|
||||
if found {
|
||||
// 记录阈值信息
|
||||
weblogbean.RecordIPFailureThreshold(minutes, count)
|
||||
break // 找到第一个匹配的规则即可
|
||||
}
|
||||
}
|
||||
}
|
||||
weblogbean.RISK_LEVEL = 1
|
||||
|
||||
result.IsBlock = true
|
||||
|
||||
@@ -13,11 +13,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func InitIPBanManager() {
|
||||
func InitIPBanManager(wafCache *cache.WafCache) {
|
||||
// 初始化IP失败管理器单例,使用传入的cache
|
||||
ipFailureManagerOnce.Do(func() {
|
||||
ipFailureManagerInstance = &IPFailureManager{
|
||||
cache: wafCache,
|
||||
statusMap: make(map[int]bool),
|
||||
}
|
||||
ipFailureManagerInstance.initStatusCodes()
|
||||
})
|
||||
|
||||
// 注册到innerbean包,供WebLog使用
|
||||
innerbean.SetIPFailureCountGetter(func(ip string, minutes int64) int64 {
|
||||
return GetIPFailureManager().GetFailureCount(ip, minutes)
|
||||
})
|
||||
// 注册IP失败封禁阈值记录函数
|
||||
innerbean.SetIPFailureThresholdRecorder(func(ip string, minutes int64, count int64) {
|
||||
GetIPFailureManager().RecordFailureThreshold(ip, minutes, count)
|
||||
})
|
||||
// 注册SSL证书验证路径获取函数
|
||||
innerbean.SetSSLChallengePathGetter(func() string {
|
||||
return global.GSSL_HTTP_CHANGLE_PATH
|
||||
@@ -26,10 +39,13 @@ func InitIPBanManager() {
|
||||
|
||||
// IPFailureRecord IP失败记录
|
||||
type IPFailureRecord struct {
|
||||
IP string
|
||||
Count int64
|
||||
FirstTime time.Time
|
||||
LastTime time.Time
|
||||
IP string
|
||||
Events []time.Time
|
||||
Count int64
|
||||
FirstTime time.Time
|
||||
LastTime time.Time
|
||||
TriggerMinutes int64 // 触发封禁的时间窗口(分钟)
|
||||
TriggerCount int64 // 触发封禁的失败次数阈值
|
||||
}
|
||||
|
||||
// IPFailureManager IP失败管理器
|
||||
@@ -46,14 +62,11 @@ var (
|
||||
)
|
||||
|
||||
// GetIPFailureManager 获取IP失败管理器单例
|
||||
// 注意:需要先调用 InitIPBanManager 进行初始化
|
||||
func GetIPFailureManager() *IPFailureManager {
|
||||
ipFailureManagerOnce.Do(func() {
|
||||
ipFailureManagerInstance = &IPFailureManager{
|
||||
cache: cache.InitWafCache(),
|
||||
statusMap: make(map[int]bool),
|
||||
}
|
||||
ipFailureManagerInstance.initStatusCodes()
|
||||
})
|
||||
if ipFailureManagerInstance == nil {
|
||||
zlog.Error("IPFailureManager 未初始化,请先调用 InitIPBanManager")
|
||||
}
|
||||
return ipFailureManagerInstance
|
||||
}
|
||||
|
||||
@@ -159,27 +172,31 @@ func (m *IPFailureManager) RecordFailure(webLog *innerbean.WebLog) {
|
||||
if record == nil {
|
||||
record = &IPFailureRecord{
|
||||
IP: ip,
|
||||
Count: 1,
|
||||
Events: []time.Time{},
|
||||
FirstTime: now,
|
||||
LastTime: now,
|
||||
}
|
||||
} else {
|
||||
// 检查时间窗口
|
||||
timeWindow := time.Duration(global.GCONFIG_IP_FAILURE_BAN_TIME_WINDOW) * time.Minute
|
||||
if now.Sub(record.FirstTime) > timeWindow {
|
||||
// 超出时间窗口,重置计数
|
||||
record.Count = 1
|
||||
record.FirstTime = now
|
||||
record.LastTime = now
|
||||
} else {
|
||||
// 在时间窗口内,增加计数
|
||||
record.Count++
|
||||
record.LastTime = now
|
||||
}
|
||||
// 记录事件
|
||||
record.Events = append(record.Events, now)
|
||||
// 清理过期事件(按封锁时间作为保留窗口)
|
||||
retention := time.Duration(global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME) * time.Minute
|
||||
windowStart := now.Add(-retention)
|
||||
var valid []time.Time
|
||||
for _, t := range record.Events {
|
||||
if t.After(windowStart) {
|
||||
valid = append(valid, t)
|
||||
}
|
||||
}
|
||||
record.Events = valid
|
||||
record.Count = int64(len(record.Events))
|
||||
if len(record.Events) > 0 {
|
||||
record.FirstTime = record.Events[0]
|
||||
}
|
||||
record.LastTime = now
|
||||
|
||||
// 保存到缓存,TTL设置为时间窗口的2倍
|
||||
ttl := time.Duration(global.GCONFIG_IP_FAILURE_BAN_TIME_WINDOW*2) * time.Minute
|
||||
// 保存到缓存,TTL设置为封锁时间
|
||||
ttl := time.Duration(global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME) * time.Minute
|
||||
m.cache.SetWithTTlRenewTime(key, record, ttl)
|
||||
}
|
||||
|
||||
@@ -201,25 +218,15 @@ func (m *IPFailureManager) GetFailureCount(ip string, minutes int64) int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 检查时间窗口
|
||||
timeWindow := time.Duration(minutes) * time.Minute
|
||||
now := time.Now()
|
||||
if now.Sub(record.FirstTime) > timeWindow {
|
||||
// 超出时间窗口,返回0
|
||||
return 0
|
||||
windowStart := now.Add(-time.Duration(minutes) * time.Minute)
|
||||
cnt := int64(0)
|
||||
for _, t := range record.Events {
|
||||
if t.After(windowStart) {
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
|
||||
return record.Count
|
||||
}
|
||||
|
||||
// IsIPBanned 检查IP是否应该被封禁
|
||||
func (m *IPFailureManager) IsIPBanned(ip string) bool {
|
||||
if ip == "" || global.GCONFIG_IP_FAILURE_BAN_ENABLED == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
count := m.GetFailureCount(ip, global.GCONFIG_IP_FAILURE_BAN_TIME_WINDOW)
|
||||
return count >= global.GCONFIG_IP_FAILURE_BAN_MAX_COUNT
|
||||
return cnt
|
||||
}
|
||||
|
||||
// ClearIPFailure 清除IP的失败记录
|
||||
@@ -250,3 +257,51 @@ func (m *IPFailureManager) GetFailureInfo(ip string) *IPFailureRecord {
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
// RecordFailureThreshold 记录IP失败封禁的阈值信息(当规则匹配时调用)
|
||||
// ip: IP地址
|
||||
// minutes: 触发封禁的时间窗口(分钟)
|
||||
// count: 触发封禁的失败次数阈值
|
||||
func (m *IPFailureManager) RecordFailureThreshold(ip string, minutes int64, count int64) {
|
||||
if ip == "" || global.GCONFIG_IP_FAILURE_BAN_ENABLED == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
key := enums.CACHE_IP_FAILURE_PRE + ip
|
||||
val := m.cache.Get(key)
|
||||
if val == nil {
|
||||
// 如果记录不存在,创建一个新记录
|
||||
record := &IPFailureRecord{
|
||||
IP: ip,
|
||||
Events: []time.Time{},
|
||||
TriggerMinutes: minutes,
|
||||
TriggerCount: count,
|
||||
FirstTime: time.Now(),
|
||||
LastTime: time.Now(),
|
||||
}
|
||||
ttl := time.Duration(global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME) * time.Minute
|
||||
m.cache.SetWithTTlRenewTime(key, record, ttl)
|
||||
return
|
||||
}
|
||||
|
||||
record, ok := val.(*IPFailureRecord)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新阈值信息(如果新的阈值更严格,则更新)
|
||||
if record.TriggerMinutes == 0 || record.TriggerCount == 0 {
|
||||
record.TriggerMinutes = minutes
|
||||
record.TriggerCount = count
|
||||
} else {
|
||||
// 如果新的阈值更严格(时间窗口更小或次数更少),则更新
|
||||
if minutes < record.TriggerMinutes || (minutes == record.TriggerMinutes && count < record.TriggerCount) {
|
||||
record.TriggerMinutes = minutes
|
||||
record.TriggerCount = count
|
||||
}
|
||||
}
|
||||
|
||||
// 保存更新后的记录
|
||||
ttl := time.Duration(global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME) * time.Minute
|
||||
m.cache.SetWithTTlRenewTime(key, record, ttl)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func (web *WafWebManager) initRouter(r *gin.Engine) {
|
||||
router.ApiGroupApp.InitAllowUrlRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitLdpUrlRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitAntiCCRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitIPFailureRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitBlockIpRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitBlockUrlRouter(RouterGroup)
|
||||
router.ApiGroupApp.InitAccountRouter(RouterGroup)
|
||||
|
||||
@@ -117,11 +117,8 @@ func setConfigIntValue(name string, value int64, change int) {
|
||||
case "ip_failure_ban_enabled":
|
||||
global.GCONFIG_IP_FAILURE_BAN_ENABLED = value
|
||||
break
|
||||
case "ip_failure_ban_time_window":
|
||||
global.GCONFIG_IP_FAILURE_BAN_TIME_WINDOW = value
|
||||
break
|
||||
case "ip_failure_ban_max_count":
|
||||
global.GCONFIG_IP_FAILURE_BAN_MAX_COUNT = value
|
||||
case "ip_failure_ban_lock_time":
|
||||
global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME = value
|
||||
break
|
||||
default:
|
||||
zlog.Warn("Unknown config item:", name)
|
||||
@@ -298,6 +295,5 @@ func TaskLoadSetting(initLoad bool) {
|
||||
// IP失败封禁相关配置
|
||||
updateConfigStringItem(initLoad, "security", "ip_failure_status_codes", global.GCONFIG_IP_FAILURE_STATUS_CODES, "失败状态码配置,支持多个用|分隔,也支持正则表达式,例如:401|403|404|444|429|503 或 ^4[0-9]{2}$", "string", "", configMap)
|
||||
updateConfigIntItem(initLoad, "security", "ip_failure_ban_enabled", global.GCONFIG_IP_FAILURE_BAN_ENABLED, "是否启用IP失败封禁(1启用 0禁用)", "options", "0|禁用,1|启用", configMap)
|
||||
updateConfigIntItem(initLoad, "security", "ip_failure_ban_time_window", global.GCONFIG_IP_FAILURE_BAN_TIME_WINDOW, "IP失败封禁时间窗口(单位:分钟,默认5分钟)", "int", "", configMap)
|
||||
updateConfigIntItem(initLoad, "security", "ip_failure_ban_max_count", global.GCONFIG_IP_FAILURE_BAN_MAX_COUNT, "IP失败封禁最大失败次数(默认10次)", "int", "", configMap)
|
||||
updateConfigIntItem(initLoad, "security", "ip_failure_ban_lock_time", global.GCONFIG_IP_FAILURE_BAN_LOCK_TIME, "IP失败封禁锁定时间(单位:分钟,默认10分钟)", "int", "", configMap)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user