WAF策略中增加验证码相关定制设置

This commit is contained in:
GoEdgeLab
2022-05-21 11:17:53 +08:00
parent caf8aef10b
commit 4dccc2929b
18 changed files with 274 additions and 91 deletions

View File

@@ -194,7 +194,7 @@ func (this *HTTPRequest) checkWAFRequest(firewallPolicy *firewallconfigs.HTTPFir
} }
// 规则测试 // 规则测试
w := sharedWAFManager.FindWAF(firewallPolicy.Id) w := waf.SharedWAFManager.FindWAF(firewallPolicy.Id)
if w == nil { if w == nil {
return return
} }
@@ -261,7 +261,7 @@ func (this *HTTPRequest) checkWAFResponse(firewallPolicy *firewallconfigs.HTTPFi
return return
} }
w := sharedWAFManager.FindWAF(firewallPolicy.Id) w := waf.SharedWAFManager.FindWAF(firewallPolicy.Id)
if w == nil { if w == nil {
return return
} }

View File

@@ -21,6 +21,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/stats" "github.com/TeaOSLab/EdgeNode/internal/stats"
"github.com/TeaOSLab/EdgeNode/internal/trackers" "github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/andybalholm/brotli" "github.com/andybalholm/brotli"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
@@ -865,7 +866,7 @@ func (this *Node) onReload(config *nodeconfigs.NodeConfig) {
} }
// WAF策略 // WAF策略
sharedWAFManager.UpdatePolicies(config.FindAllFirewallPolicies()) waf.SharedWAFManager.UpdatePolicies(config.FindAllFirewallPolicies())
iplibrary.SharedActionManager.UpdateActions(config.FirewallActions) iplibrary.SharedActionManager.UpdateActions(config.FirewallActions)
// 统计指标 // 统计指标

View File

@@ -6,6 +6,7 @@ import (
) )
type AllowAction struct { type AllowAction struct {
BaseAction
} }
func (this *AllowAction) Init(waf *WAF) error { func (this *AllowAction) Init(waf *WAF) error {

View File

@@ -7,6 +7,17 @@ import (
) )
type BaseAction struct { type BaseAction struct {
currentActionId int64
}
// ActionId 读取ActionId
func (this *BaseAction) ActionId() int64 {
return this.currentActionId
}
// SetActionId 设置Id
func (this *BaseAction) SetActionId(actionId int64) {
this.currentActionId = actionId
} }
// CloseConn 关闭连接 // CloseConn 关闭连接

View File

@@ -20,6 +20,8 @@ var urlPrefixReg = regexp.MustCompile("^(?i)(http|https)://")
var httpClient = utils.SharedHttpClient(5 * time.Second) var httpClient = utils.SharedHttpClient(5 * time.Second)
type BlockAction struct { type BlockAction struct {
BaseAction
StatusCode int `yaml:"statusCode" json:"statusCode"` StatusCode int `yaml:"statusCode" json:"statusCode"`
Body string `yaml:"body" json:"body"` // supports HTML Body string `yaml:"body" json:"body"` // supports HTML
URL string `yaml:"url" json:"url"` URL string `yaml:"url" json:"url"`

View File

@@ -18,16 +18,71 @@ const (
) )
type CaptchaAction struct { type CaptchaAction struct {
BaseAction
Life int32 `yaml:"life" json:"life"` Life int32 `yaml:"life" json:"life"`
MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数 MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数
FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间 FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"` // 是否全局有效
Language string `yaml:"language" json:"language"` // 语言zh-CN, en-US ... CountLetters int8 `yaml:"countLetters" json:"countLetters"`
UIIsOn bool `yaml:"uiIsOn" json:"uiIsOn"` // 是否使用自定义UI
UITitle string `yaml:"uiTitle" json:"uiTitle"` // 消息标题
UIPrompt string `yaml:"uiPrompt" json:"uiPrompt"` // 消息提示
UIButtonTitle string `yaml:"uiButtonTitle" json:"uiButtonTitle"` // 按钮标题
UIShowRequestId bool `yaml:"uiShowRequestId" json:"uiShowRequestId"` // 是否显示请求ID
UICss string `yaml:"uiCss" json:"uiCss"` // CSS样式
UIFooter string `yaml:"uiFooter" json:"uiFooter"` // 页脚
UIBody string `yaml:"uiBody" json:"uiBody"` // 内容轮廓
Lang string `yaml:"lang" json:"lang"` // 语言zh-CN, en-US ...
AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单 AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
Scope string `yaml:"scope" json:"scope"` Scope string `yaml:"scope" json:"scope"`
} }
func (this *CaptchaAction) Init(waf *WAF) error { func (this *CaptchaAction) Init(waf *WAF) error {
if waf.DefaultCaptchaAction != nil {
if this.Life <= 0 {
this.Life = waf.DefaultCaptchaAction.Life
}
if this.MaxFails <= 0 {
this.MaxFails = waf.DefaultCaptchaAction.MaxFails
}
if this.FailBlockTimeout <= 0 {
this.FailBlockTimeout = waf.DefaultCaptchaAction.FailBlockTimeout
}
this.FailBlockScopeAll = waf.DefaultCaptchaAction.FailBlockScopeAll
if this.CountLetters <= 0 {
this.CountLetters = waf.DefaultCaptchaAction.CountLetters
}
this.UIIsOn = waf.DefaultCaptchaAction.UIIsOn
if len(this.UITitle) == 0 {
this.UITitle = waf.DefaultCaptchaAction.UITitle
}
if len(this.UIPrompt) == 0 {
this.UIPrompt = waf.DefaultCaptchaAction.UIPrompt
}
if len(this.UIButtonTitle) == 0 {
this.UIButtonTitle = waf.DefaultCaptchaAction.UIButtonTitle
}
this.UIShowRequestId = waf.DefaultCaptchaAction.UIShowRequestId
if len(this.UICss) == 0 {
this.UICss = waf.DefaultCaptchaAction.UICss
}
if len(this.UIFooter) == 0 {
this.UIFooter = waf.DefaultCaptchaAction.UIFooter
}
if len(this.UIBody) == 0 {
this.UIBody = waf.DefaultCaptchaAction.UIBody
}
if len(this.Lang) == 0 {
this.Lang = waf.DefaultCaptchaAction.Lang
}
}
return nil return nil
} }
@@ -49,7 +104,7 @@ func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req
return true return true
} }
refURL := request.WAFRaw().URL.String() var refURL = request.WAFRaw().URL.String()
// 覆盖配置 // 覆盖配置
if strings.HasPrefix(refURL, CaptchaPath) { if strings.HasPrefix(refURL, CaptchaPath) {
@@ -63,10 +118,8 @@ func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req
} }
var captchaConfig = maps.Map{ var captchaConfig = maps.Map{
"action": this, "actionId": this.ActionId(),
"timestamp": time.Now().Unix(), "timestamp": time.Now().Unix(),
"maxFails": this.MaxFails,
"failBlockTimeout": this.FailBlockTimeout,
"url": refURL, "url": refURL,
"policyId": waf.Id, "policyId": waf.Id,
"groupId": group.Id, "groupId": group.Id,

View File

@@ -8,6 +8,8 @@ import (
) )
type GoGroupAction struct { type GoGroupAction struct {
BaseAction
GroupId string `yaml:"groupId" json:"groupId"` GroupId string `yaml:"groupId" json:"groupId"`
} }

View File

@@ -8,6 +8,8 @@ import (
) )
type GoSetAction struct { type GoSetAction struct {
BaseAction
GroupId string `yaml:"groupId" json:"groupId"` GroupId string `yaml:"groupId" json:"groupId"`
SetId string `yaml:"setId" json:"setId"` SetId string `yaml:"setId" json:"setId"`
} }

View File

@@ -11,6 +11,12 @@ type ActionInterface interface {
// Init 初始化 // Init 初始化
Init(waf *WAF) error Init(waf *WAF) error
// ActionId 读取ActionId
ActionId() int64
// SetActionId 设置ID
SetActionId(id int64)
// Code 代号 // Code 代号
Code() string Code() string
@@ -20,6 +26,6 @@ type ActionInterface interface {
// WillChange determine if the action will change the request // WillChange determine if the action will change the request
WillChange() bool WillChange() bool
// Perform perform the action // Perform the action
Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool)
} }

View File

@@ -6,6 +6,7 @@ import (
) )
type LogAction struct { type LogAction struct {
BaseAction
} }
func (this *LogAction) Init(waf *WAF) error { func (this *LogAction) Init(waf *WAF) error {

View File

@@ -50,6 +50,7 @@ func init() {
} }
type NotifyAction struct { type NotifyAction struct {
BaseAction
} }
func (this *NotifyAction) Init(waf *WAF) error { func (this *NotifyAction) Init(waf *WAF) error {
@@ -69,7 +70,7 @@ func (this *NotifyAction) WillChange() bool {
return false return false
} }
// Perform perform the action // Perform the action
func (this *NotifyAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) { func (this *NotifyAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) (allow bool) {
select { select {
case notifyChan <- &notifyTask{ case notifyChan <- &notifyTask{

View File

@@ -6,6 +6,8 @@ import (
) )
type TagAction struct { type TagAction struct {
BaseAction
Tags []string `yaml:"tags" json:"tags"` Tags []string `yaml:"tags" json:"tags"`
} }

View File

@@ -5,15 +5,19 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"reflect" "reflect"
"sync/atomic"
) )
var seedActionId int64 = 1
func FindActionInstance(action ActionString, options maps.Map) ActionInterface { func FindActionInstance(action ActionString, options maps.Map) ActionInterface {
for _, def := range AllActions { for _, def := range AllActions {
if def.Code == action { if def.Code == action {
if def.Type != nil { if def.Type != nil {
// create new instance // create new instance
ptrValue := reflect.New(def.Type) var ptrValue = reflect.New(def.Type)
instance := ptrValue.Interface().(ActionInterface) var instance = ptrValue.Interface().(ActionInterface)
instance.SetActionId(atomic.AddInt64(&seedActionId, 1))
if len(options) > 0 { if len(options) > 0 {
optionsJSON, err := json.Marshal(options) optionsJSON, err := json.Marshal(options)

View File

@@ -6,7 +6,6 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/ttlcache" "github.com/TeaOSLab/EdgeNode/internal/ttlcache"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/jsonutils"
"github.com/TeaOSLab/EdgeNode/internal/waf/requests" "github.com/TeaOSLab/EdgeNode/internal/waf/requests"
"github.com/dchest/captcha" "github.com/dchest/captcha"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
@@ -26,8 +25,8 @@ func NewCaptchaValidator() *CaptchaValidator {
return &CaptchaValidator{} return &CaptchaValidator{}
} }
func (this *CaptchaValidator) Run(request requests.Request, writer http.ResponseWriter) { func (this *CaptchaValidator) Run(req requests.Request, writer http.ResponseWriter) {
var info = request.WAFRaw().URL.Query().Get("info") var info = req.WAFRaw().URL.Query().Get("info")
if len(info) == 0 { if len(info) == 0 {
writer.WriteHeader(http.StatusBadRequest) writer.WriteHeader(http.StatusBadRequest)
_, _ = writer.Write([]byte("invalid request")) _, _ = writer.Write([]byte("invalid request"))
@@ -41,35 +40,48 @@ func (this *CaptchaValidator) Run(request requests.Request, writer http.Response
var timestamp = m.GetInt64("timestamp") var timestamp = m.GetInt64("timestamp")
if timestamp < time.Now().Unix()-600 { // 10分钟之后信息过期 if timestamp < time.Now().Unix()-600 { // 10分钟之后信息过期
http.Redirect(writer, request.WAFRaw(), m.GetString("url"), http.StatusTemporaryRedirect) http.Redirect(writer, req.WAFRaw(), m.GetString("url"), http.StatusTemporaryRedirect)
return
}
var actionConfig = &CaptchaAction{}
err = jsonutils.MapToObject(m.GetMap("action"), actionConfig)
if err != nil {
http.Redirect(writer, request.WAFRaw(), m.GetString("url"), http.StatusTemporaryRedirect)
return return
} }
var actionId = m.GetInt64("actionId")
var setId = m.GetInt64("setId") var setId = m.GetInt64("setId")
var originURL = m.GetString("url") var originURL = m.GetString("url")
var maxFails = m.GetInt("maxFails")
var failBlockTimeout = m.GetInt("failBlockTimeout")
var policyId = m.GetInt64("policyId") var policyId = m.GetInt64("policyId")
var groupId = m.GetInt64("groupId") var groupId = m.GetInt64("groupId")
if request.WAFRaw().Method == http.MethodPost && len(request.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")) > 0 {
this.validate(actionConfig, maxFails, failBlockTimeout, policyId, groupId, setId, originURL, request, writer) var waf = SharedWAFManager.FindWAF(policyId)
if waf == nil {
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusTemporaryRedirect)
return
}
var actionConfig = waf.FindAction(actionId)
if actionConfig == nil {
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusTemporaryRedirect)
return
}
captchaActionConfig, ok := actionConfig.(*CaptchaAction)
if !ok {
http.Redirect(writer, req.WAFRaw(), originURL, http.StatusTemporaryRedirect)
return
}
if req.WAFRaw().Method == http.MethodPost && len(req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")) > 0 {
this.validate(captchaActionConfig, policyId, groupId, setId, originURL, req, writer)
} else { } else {
// 增加计数 // 增加计数
this.IncreaseFails(request, maxFails, failBlockTimeout, policyId, groupId, setId) this.IncreaseFails(req, captchaActionConfig, policyId, groupId, setId)
this.show(actionConfig, request, writer) this.show(captchaActionConfig, req, writer)
} }
} }
func (this *CaptchaValidator) show(actionConfig *CaptchaAction, request requests.Request, writer http.ResponseWriter) { func (this *CaptchaValidator) show(actionConfig *CaptchaAction, req requests.Request, writer http.ResponseWriter) {
// show captcha // show captcha
var captchaId = captcha.NewLen(6) var countLetters = 6
if actionConfig.CountLetters > 0 && actionConfig.CountLetters <= 10 {
countLetters = int(actionConfig.CountLetters)
}
var captchaId = captcha.NewLen(countLetters)
var buf = bytes.NewBuffer([]byte{}) var buf = bytes.NewBuffer([]byte{})
err := captcha.WriteImage(buf, captchaId, 200, 100) err := captcha.WriteImage(buf, captchaId, 200, 100)
if err != nil { if err != nil {
@@ -77,9 +89,9 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, request requests
return return
} }
var lang = actionConfig.Language var lang = actionConfig.Lang
if len(lang) == 0 { if len(lang) == 0 {
acceptLanguage := request.WAFRaw().Header.Get("Accept-Language") var acceptLanguage = req.WAFRaw().Header.Get("Accept-Language")
if len(acceptLanguage) > 0 { if len(acceptLanguage) > 0 {
langIndex := strings.Index(acceptLanguage, ",") langIndex := strings.Index(acceptLanguage, ",")
if langIndex > 0 { if langIndex > 0 {
@@ -114,12 +126,62 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, request requests
msgRequestId = "Request ID" msgRequestId = "Request ID"
} }
var msgCss = ""
var requestIdBox = `<address>` + msgRequestId + `: ` + req.Format("${requestId}") + `</address>`
var msgFooter = ""
var body = `<form method="POST">
<input type="hidden" name="GOEDGE_WAF_CAPTCHA_ID" value="` + captchaId + `"/>
<div class="ui-image">
<img src="data:image/png;base64, ` + base64.StdEncoding.EncodeToString(buf.Bytes()) + `"/>` + `
</div>
<div class="ui-input">
<p>` + msgPrompt + `</p>
<input type="text" name="GOEDGE_WAF_CAPTCHA_CODE" id="GOEDGE_WAF_CAPTCHA_CODE" maxlength="6" autocomplete="off" z-index="1" class="input"/>
</div>
<div class="ui-button">
<button type="submit" style="line-height:24px;margin-top:10px">` + msgButtonTitle + `</button>
</div>
</form>
` + requestIdBox + `
` + msgFooter + ``
// 默认设置
if actionConfig.UIIsOn {
if len(actionConfig.UITitle) > 0 {
msgTitle = actionConfig.UITitle
}
if len(actionConfig.UIPrompt) > 0 {
msgPrompt = actionConfig.UIPrompt
}
if len(actionConfig.UIButtonTitle) > 0 {
msgButtonTitle = actionConfig.UIButtonTitle
}
if len(actionConfig.UICss) > 0 {
msgCss = actionConfig.UICss
}
if !actionConfig.UIShowRequestId {
requestIdBox = ""
}
if len(actionConfig.UIFooter) > 0 {
msgFooter = actionConfig.UIFooter
}
if len(actionConfig.UIBody) > 0 {
var index = strings.Index(actionConfig.UIBody, "${body}")
if index < 0 {
body = actionConfig.UIBody + body
} else {
body = actionConfig.UIBody[:index] + body + actionConfig.UIBody[index+7:] // 7是"${body}"的长度
}
}
}
writer.Header().Set("Content-Type", "text/html; charset=utf-8") writer.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = writer.Write([]byte(`<!DOCTYPE html> _, _ = writer.Write([]byte(`<!DOCTYPE html>
<html> <html>
<head> <head>
<title>` + msgTitle + `</title> <title>` + msgTitle + `</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
<meta charset="UTF-8"/>
<script type="text/javascript"> <script type="text/javascript">
if (window.addEventListener != null) { if (window.addEventListener != null) {
window.addEventListener("load", function () { window.addEventListener("load", function () {
@@ -131,32 +193,22 @@ func (this *CaptchaValidator) show(actionConfig *CaptchaAction, request requests
form { width: 20em; margin: 0 auto; text-align: center; } form { width: 20em; margin: 0 auto; text-align: center; }
.input { font-size:16px;line-height:24px; letter-spacing: 15px; padding-left: 10px; width: 140px; } .input { font-size:16px;line-height:24px; letter-spacing: 15px; padding-left: 10px; width: 140px; }
address { margin-top: 1em; padding-top: 0.5em; border-top: 1px #ccc solid; text-align: center; } address { margin-top: 1em; padding-top: 0.5em; border-top: 1px #ccc solid; text-align: center; }
` + msgCss + `
</style> </style>
</head> </head>
<body> <body>` + body + `
<form method="POST">
<input type="hidden" name="GOEDGE_WAF_CAPTCHA_ID" value="` + captchaId + `"/>
<img src="data:image/png;base64, ` + base64.StdEncoding.EncodeToString(buf.Bytes()) + `"/>` + `
<div>
<p>` + msgPrompt + `</p>
<input type="text" name="GOEDGE_WAF_CAPTCHA_CODE" id="GOEDGE_WAF_CAPTCHA_CODE" maxlength="6" autocomplete="off" z-index="1" class="input"/>
</div>
<div>
<button type="submit" style="line-height:24px;margin-top:10px">` + msgButtonTitle + `</button>
</div>
</form>
<address>` + msgRequestId + `: ` + request.Format("${requestId}") + `</address>
</body> </body>
</html>`)) </html>`))
} }
func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, maxFails int, failBlockTimeout int, policyId int64, groupId int64, setId int64, originURL string, request requests.Request, writer http.ResponseWriter) (allow bool) { func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, originURL string, req requests.Request, writer http.ResponseWriter) (allow bool) {
var captchaId = request.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")
var captchaId = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_ID")
if len(captchaId) > 0 { if len(captchaId) > 0 {
var captchaCode = request.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_CODE") var captchaCode = req.WAFRaw().FormValue("GOEDGE_WAF_CAPTCHA_CODE")
if captcha.VerifyString(captchaId, captchaCode) { if captcha.VerifyString(captchaId, captchaCode) {
// 清除计数 // 清除计数
ttlcache.SharedCache.Delete("CAPTCHA:FAILS:" + request.WAFRemoteIP()) ttlcache.SharedCache.Delete(this.cacheKey(req))
var life = CaptchaSeconds var life = CaptchaSeconds
if actionConfig.Life > 0 { if actionConfig.Life > 0 {
@@ -164,18 +216,18 @@ func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, maxFails int
} }
// 加入到白名单 // 加入到白名单
SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId, "") SharedIPWhiteList.RecordIP("set:"+strconv.FormatInt(setId, 10), actionConfig.Scope, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(life), policyId, false, groupId, setId, "")
http.Redirect(writer, request.WAFRaw(), originURL, http.StatusSeeOther) http.Redirect(writer, req.WAFRaw(), originURL, http.StatusSeeOther)
return false return false
} else { } else {
// 增加计数 // 增加计数
if !this.IncreaseFails(request, maxFails, failBlockTimeout, policyId, groupId, setId) { if !this.IncreaseFails(req, actionConfig, policyId, groupId, setId) {
return false return false
} }
http.Redirect(writer, request.WAFRaw(), request.WAFRaw().URL.String(), http.StatusSeeOther) http.Redirect(writer, req.WAFRaw(), req.WAFRaw().URL.String(), http.StatusSeeOther)
} }
} }
@@ -183,17 +235,28 @@ func (this *CaptchaValidator) validate(actionConfig *CaptchaAction, maxFails int
} }
// IncreaseFails 增加失败次数,以便后续操作 // IncreaseFails 增加失败次数,以便后续操作
func (this *CaptchaValidator) IncreaseFails(request requests.Request, maxFails int, failBlockTimeout int, policyId int64, groupId int64, setId int64) (goNext bool) { func (this *CaptchaValidator) IncreaseFails(req requests.Request, actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64) (goNext bool) {
var maxFails = actionConfig.MaxFails
var failBlockTimeout = actionConfig.FailBlockTimeout
if maxFails > 0 && failBlockTimeout > 0 { if maxFails > 0 && failBlockTimeout > 0 {
// 加上展示的计数 // 加上展示的计数
maxFails *= 2 maxFails *= 2
var countFails = ttlcache.SharedCache.IncreaseInt64("CAPTCHA:FAILS:"+request.WAFRemoteIP(), 1, time.Now().Unix()+300, true) var countFails = ttlcache.SharedCache.IncreaseInt64(this.cacheKey(req), 1, time.Now().Unix()+300, true)
if int(countFails) >= maxFails { if int(countFails) >= maxFails {
var useLocalFirewall = false var useLocalFirewall = false
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败")
if actionConfig.FailBlockScopeAll {
useLocalFirewall = true
}
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败")
return false return false
} }
} }
return true return true
} }
func (this *CaptchaValidator) cacheKey(req requests.Request) string {
return "CAPTCHA:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId())
}

View File

@@ -66,7 +66,7 @@ func (this *RuleSet) Init(waf *WAF) error {
// action instances // action instances
this.actionInstances = []ActionInterface{} this.actionInstances = []ActionInterface{}
for _, action := range this.Actions { for _, action := range this.Actions {
instance := FindActionInstance(action.Code, action.Options) var instance = FindActionInstance(action.Code, action.Options)
if instance == nil { if instance == nil {
remotelogs.Error("WAF_RULE_SET", "can not find instance for action '"+action.Code+"'") remotelogs.Error("WAF_RULE_SET", "can not find instance for action '"+action.Code+"'")
continue continue
@@ -79,6 +79,7 @@ func (this *RuleSet) Init(waf *WAF) error {
} }
this.actionInstances = append(this.actionInstances, instance) this.actionInstances = append(this.actionInstances, instance)
waf.AddAction(instance)
} }
// sort actions // sort actions

View File

@@ -27,11 +27,13 @@ type WAF struct {
SYNFlood *firewallconfigs.SYNFloodConfig `yaml:"synFlood" json:"synFlood"` SYNFlood *firewallconfigs.SYNFloodConfig `yaml:"synFlood" json:"synFlood"`
DefaultBlockAction *BlockAction DefaultBlockAction *BlockAction
DefaultCaptchaAction *CaptchaAction
hasInboundRules bool hasInboundRules bool
hasOutboundRules bool hasOutboundRules bool
checkpointsMap map[string]checkpoints.CheckpointInterface // prefix => checkpoint checkpointsMap map[string]checkpoints.CheckpointInterface // prefix => checkpoint
actionMap map[int64]ActionInterface // actionId => ActionInterface
} }
func NewWAF() *WAF { func NewWAF() *WAF {
@@ -74,6 +76,9 @@ func (this *WAF) Init() (resultErrors []error) {
this.checkpointsMap[def.Prefix] = instance this.checkpointsMap[def.Prefix] = instance
} }
// action map
this.actionMap = map[int64]ActionInterface{}
// rules // rules
this.hasInboundRules = len(this.Inbound) > 0 this.hasInboundRules = len(this.Inbound) > 0
this.hasOutboundRules = len(this.Outbound) > 0 this.hasOutboundRules = len(this.Outbound) > 0
@@ -324,8 +329,16 @@ func (this *WAF) ContainsGroupCode(code string) bool {
return false return false
} }
func (this *WAF) AddAction(action ActionInterface) {
this.actionMap[action.ActionId()] = action
}
func (this *WAF) FindAction(actionId int64) ActionInterface {
return this.actionMap[actionId]
}
func (this *WAF) Copy() *WAF { func (this *WAF) Copy() *WAF {
waf := &WAF{ var waf = &WAF{
Id: this.Id, Id: this.Id,
IsOn: this.IsOn, IsOn: this.IsOn,
Name: this.Name, Name: this.Name,

View File

@@ -1,26 +1,25 @@
package nodes package waf
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/errors" "github.com/TeaOSLab/EdgeNode/internal/errors"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"strconv" "strconv"
"sync" "sync"
) )
var sharedWAFManager = NewWAFManager() var SharedWAFManager = NewWAFManager()
// WAFManager WAF管理器 // WAFManager WAF管理器
type WAFManager struct { type WAFManager struct {
mapping map[int64]*waf.WAF // policyId => WAF mapping map[int64]*WAF // policyId => WAF
locker sync.RWMutex locker sync.RWMutex
} }
// NewWAFManager 获取新对象 // NewWAFManager 获取新对象
func NewWAFManager() *WAFManager { func NewWAFManager() *WAFManager {
return &WAFManager{ return &WAFManager{
mapping: map[int64]*waf.WAF{}, mapping: map[int64]*WAF{},
} }
} }
@@ -29,9 +28,9 @@ func (this *WAFManager) UpdatePolicies(policies []*firewallconfigs.HTTPFirewallP
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
m := map[int64]*waf.WAF{} m := map[int64]*WAF{}
for _, p := range policies { for _, p := range policies {
w, err := this.convertWAF(p) w, err := this.ConvertWAF(p)
if w != nil { if w != nil {
m[p.Id] = w m[p.Id] = w
} }
@@ -44,22 +43,22 @@ func (this *WAFManager) UpdatePolicies(policies []*firewallconfigs.HTTPFirewallP
} }
// FindWAF 查找WAF // FindWAF 查找WAF
func (this *WAFManager) FindWAF(policyId int64) *waf.WAF { func (this *WAFManager) FindWAF(policyId int64) *WAF {
this.locker.RLock() this.locker.RLock()
w, _ := this.mapping[policyId] w, _ := this.mapping[policyId]
this.locker.RUnlock() this.locker.RUnlock()
return w return w
} }
// 将Policy转换为WAF // ConvertWAF 将Policy转换为WAF
func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (*waf.WAF, error) { func (this *WAFManager) ConvertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (*WAF, error) {
if policy == nil { if policy == nil {
return nil, errors.New("policy should not be nil") return nil, errors.New("policy should not be nil")
} }
if len(policy.Mode) == 0 { if len(policy.Mode) == 0 {
policy.Mode = firewallconfigs.FirewallModeDefend policy.Mode = firewallconfigs.FirewallModeDefend
} }
w := &waf.WAF{ var w = &WAF{
Id: policy.Id, Id: policy.Id,
IsOn: policy.IsOn, IsOn: policy.IsOn,
Name: policy.Name, Name: policy.Name,
@@ -71,7 +70,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
// inbound // inbound
if policy.Inbound != nil && policy.Inbound.IsOn { if policy.Inbound != nil && policy.Inbound.IsOn {
for _, group := range policy.Inbound.Groups { for _, group := range policy.Inbound.Groups {
g := &waf.RuleGroup{ g := &RuleGroup{
Id: group.Id, Id: group.Id,
IsOn: group.IsOn, IsOn: group.IsOn,
Name: group.Name, Name: group.Name,
@@ -82,7 +81,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
// rule sets // rule sets
for _, set := range group.Sets { for _, set := range group.Sets {
s := &waf.RuleSet{ s := &RuleSet{
Id: set.Id, Id: set.Id,
Code: set.Code, Code: set.Code,
IsOn: set.IsOn, IsOn: set.IsOn,
@@ -97,10 +96,10 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
// rules // rules
for _, rule := range set.Rules { for _, rule := range set.Rules {
r := &waf.Rule{ r := &Rule{
Description: rule.Description, Description: rule.Description,
Param: rule.Param, Param: rule.Param,
ParamFilters: []*waf.ParamFilter{}, ParamFilters: []*ParamFilter{},
Operator: rule.Operator, Operator: rule.Operator,
Value: rule.Value, Value: rule.Value,
IsCaseInsensitive: rule.IsCaseInsensitive, IsCaseInsensitive: rule.IsCaseInsensitive,
@@ -108,7 +107,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
} }
for _, paramFilter := range rule.ParamFilters { for _, paramFilter := range rule.ParamFilters {
r.ParamFilters = append(r.ParamFilters, &waf.ParamFilter{ r.ParamFilters = append(r.ParamFilters, &ParamFilter{
Code: paramFilter.Code, Code: paramFilter.Code,
Options: paramFilter.Options, Options: paramFilter.Options,
}) })
@@ -127,7 +126,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
// outbound // outbound
if policy.Outbound != nil && policy.Outbound.IsOn { if policy.Outbound != nil && policy.Outbound.IsOn {
for _, group := range policy.Outbound.Groups { for _, group := range policy.Outbound.Groups {
g := &waf.RuleGroup{ g := &RuleGroup{
Id: group.Id, Id: group.Id,
IsOn: group.IsOn, IsOn: group.IsOn,
Name: group.Name, Name: group.Name,
@@ -138,7 +137,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
// rule sets // rule sets
for _, set := range group.Sets { for _, set := range group.Sets {
s := &waf.RuleSet{ s := &RuleSet{
Id: set.Id, Id: set.Id,
Code: set.Code, Code: set.Code,
IsOn: set.IsOn, IsOn: set.IsOn,
@@ -154,7 +153,7 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
// rules // rules
for _, rule := range set.Rules { for _, rule := range set.Rules {
r := &waf.Rule{ r := &Rule{
Description: rule.Description, Description: rule.Description,
Param: rule.Param, Param: rule.Param,
Operator: rule.Operator, Operator: rule.Operator,
@@ -172,9 +171,9 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
} }
} }
// action // block action
if policy.BlockOptions != nil { if policy.BlockOptions != nil {
w.DefaultBlockAction = &waf.BlockAction{ w.DefaultBlockAction = &BlockAction{
StatusCode: policy.BlockOptions.StatusCode, StatusCode: policy.BlockOptions.StatusCode,
Body: policy.BlockOptions.Body, Body: policy.BlockOptions.Body,
URL: policy.BlockOptions.URL, URL: policy.BlockOptions.URL,
@@ -182,6 +181,26 @@ func (this *WAFManager) convertWAF(policy *firewallconfigs.HTTPFirewallPolicy) (
} }
} }
// captcha action
if policy.CaptchaOptions != nil {
w.DefaultCaptchaAction = &CaptchaAction{
Life: policy.CaptchaOptions.Life,
MaxFails: policy.CaptchaOptions.MaxFails,
FailBlockTimeout: policy.CaptchaOptions.FailBlockTimeout,
FailBlockScopeAll: policy.CaptchaOptions.FailBlockScopeAll,
CountLetters: policy.CaptchaOptions.CountLetters,
UIIsOn: policy.CaptchaOptions.UIIsOn,
UITitle: policy.CaptchaOptions.UITitle,
UIPrompt: policy.CaptchaOptions.UIPrompt,
UIButtonTitle: policy.CaptchaOptions.UIButtonTitle,
UIShowRequestId: policy.CaptchaOptions.UIShowRequestId,
UICss: policy.CaptchaOptions.UICss,
UIFooter: policy.CaptchaOptions.UIFooter,
UIBody: policy.CaptchaOptions.UIBody,
Lang: policy.CaptchaOptions.Lang,
}
}
errorList := w.Init() errorList := w.Init()
if len(errorList) > 0 { if len(errorList) > 0 {
return w, errorList[0] return w, errorList[0]

View File

@@ -1,7 +1,8 @@
package nodes package waf_test
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
"github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"testing" "testing"
) )
@@ -35,7 +36,7 @@ func TestWAFManager_convert(t *testing.T) {
}, },
}, },
} }
w, err := sharedWAFManager.convertWAF(p) w, err := waf.SharedWAFManager.ConvertWAF(p)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }