1.4.5.2
This commit is contained in:
48
EdgeNode/internal/waf/README.md
Normal file
48
EdgeNode/internal/waf/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# WAF
|
||||
A basic WAF for TeaWeb.
|
||||
|
||||
## Config Constructions
|
||||
~~~
|
||||
WAF
|
||||
Inbound
|
||||
Rule Groups
|
||||
Rule Sets
|
||||
Rules
|
||||
Checkpoint Param <Operator> Value
|
||||
Outbound
|
||||
Rule Groups
|
||||
...
|
||||
~~~
|
||||
|
||||
## Apply WAF
|
||||
~~~
|
||||
Request --> WAF --> Backends
|
||||
/
|
||||
Response <-- WAF <----
|
||||
~~~
|
||||
|
||||
## Coding
|
||||
~~~go
|
||||
waf := teawaf.NewWAF()
|
||||
|
||||
// add rule groups here
|
||||
|
||||
err := waf.Init()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
waf.Start()
|
||||
|
||||
// match http request
|
||||
// (req *http.Request, responseWriter http.ResponseWriter)
|
||||
goNext, ruleSet, _ := waf.MatchRequest(req, responseWriter)
|
||||
if ruleSet != nil {
|
||||
log.Println("meet rule set:", ruleSet.Name, "action:", ruleSet.Action)
|
||||
}
|
||||
if !goNext {
|
||||
return
|
||||
}
|
||||
|
||||
// stop the waf
|
||||
// waf.Stop()
|
||||
~~~
|
||||
46
EdgeNode/internal/waf/action_allow.go
Normal file
46
EdgeNode/internal/waf/action_allow.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AllowScope = string
|
||||
|
||||
const (
|
||||
AllowScopeGroup AllowScope = "group"
|
||||
AllowScopeServer AllowScope = "server"
|
||||
AllowScopeGlobal AllowScope = "global"
|
||||
)
|
||||
|
||||
type AllowAction struct {
|
||||
BaseAction
|
||||
|
||||
Scope AllowScope `yaml:"scope" json:"scope"`
|
||||
}
|
||||
|
||||
func (this *AllowAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AllowAction) Code() string {
|
||||
return ActionAllow
|
||||
}
|
||||
|
||||
func (this *AllowAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *AllowAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *AllowAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
// do nothing
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextGroup: this.Scope == AllowScopeGroup,
|
||||
IsAllowed: true,
|
||||
AllowScope: this.Scope,
|
||||
}
|
||||
}
|
||||
17
EdgeNode/internal/waf/action_base.go
Normal file
17
EdgeNode/internal/waf/action_base.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
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
|
||||
}
|
||||
148
EdgeNode/internal/waf/action_block.go
Normal file
148
EdgeNode/internal/waf/action_block.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/bytepool"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// url client configure
|
||||
var urlPrefixReg = regexp.MustCompile("^(?i)(http|https)://")
|
||||
var httpClient = utils.SharedHttpClient(5 * time.Second)
|
||||
|
||||
type BlockAction struct {
|
||||
BaseAction
|
||||
|
||||
StatusCode int `yaml:"statusCode" json:"statusCode"`
|
||||
Body string `yaml:"body" json:"body"` // supports HTML
|
||||
URL string `yaml:"url" json:"url"`
|
||||
Timeout int32 `yaml:"timeout" json:"timeout"`
|
||||
TimeoutMax int32 `yaml:"timeoutMax" json:"timeoutMax"`
|
||||
Scope string `yaml:"scope" json:"scope"`
|
||||
|
||||
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"`
|
||||
}
|
||||
|
||||
func (this *BlockAction) Init(waf *WAF) error {
|
||||
if waf.DefaultBlockAction != nil {
|
||||
if this.StatusCode <= 0 {
|
||||
this.StatusCode = waf.DefaultBlockAction.StatusCode
|
||||
}
|
||||
if len(this.Body) == 0 {
|
||||
this.Body = waf.DefaultBlockAction.Body
|
||||
}
|
||||
if len(this.URL) == 0 {
|
||||
this.URL = waf.DefaultBlockAction.URL
|
||||
}
|
||||
if this.Timeout <= 0 {
|
||||
this.Timeout = waf.DefaultBlockAction.Timeout
|
||||
this.TimeoutMax = waf.DefaultBlockAction.TimeoutMax // 只有没有填写封锁时长的时候才会使用默认的封锁时长最大值
|
||||
}
|
||||
|
||||
this.FailBlockScopeAll = waf.DefaultBlockAction.FailBlockScopeAll
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BlockAction) Code() string {
|
||||
return ActionBlock
|
||||
}
|
||||
|
||||
func (this *BlockAction) IsAttack() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *BlockAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *BlockAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
// 加入到黑名单
|
||||
var timeout = this.Timeout
|
||||
if timeout <= 0 {
|
||||
timeout = 300 // 默认封锁300秒
|
||||
}
|
||||
|
||||
// 随机时长
|
||||
var timeoutMax = this.TimeoutMax
|
||||
if timeoutMax > timeout {
|
||||
timeout = timeout + int32(rands.Int64()%int64(timeoutMax-timeout+1))
|
||||
}
|
||||
|
||||
SharedIPBlackList.RecordIP(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+int64(timeout), waf.Id, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal), group.Id, set.Id, "")
|
||||
|
||||
if writer != nil {
|
||||
// close the connection
|
||||
defer request.WAFClose()
|
||||
|
||||
// output response
|
||||
if this.StatusCode > 0 {
|
||||
request.ProcessResponseHeaders(writer.Header(), this.StatusCode)
|
||||
writer.WriteHeader(this.StatusCode)
|
||||
} else {
|
||||
request.ProcessResponseHeaders(writer.Header(), http.StatusForbidden)
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
if len(this.URL) > 0 {
|
||||
if urlPrefixReg.MatchString(this.URL) {
|
||||
req, err := http.NewRequest(http.MethodGet, this.URL, nil)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return PerformResult{}
|
||||
}
|
||||
req.Header.Set("User-Agent", teaconst.GlobalProductName+"/"+teaconst.Version)
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return PerformResult{}
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
for k, v := range resp.Header {
|
||||
for _, v1 := range v {
|
||||
writer.Header().Add(k, v1)
|
||||
}
|
||||
}
|
||||
|
||||
var buf = bytepool.Pool1k.Get()
|
||||
_, _ = io.CopyBuffer(writer, resp.Body, buf.Bytes)
|
||||
bytepool.Pool1k.Put(buf)
|
||||
} else {
|
||||
var path = this.URL
|
||||
if !filepath.IsAbs(this.URL) {
|
||||
path = Tea.Root + string(os.PathSeparator) + path
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return PerformResult{}
|
||||
}
|
||||
_, _ = writer.Write(data)
|
||||
}
|
||||
return PerformResult{}
|
||||
}
|
||||
if len(this.Body) > 0 {
|
||||
_, _ = writer.Write([]byte(this.Body))
|
||||
} else {
|
||||
_, _ = writer.Write([]byte("The request is blocked by " + teaconst.ProductName))
|
||||
}
|
||||
}
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
196
EdgeNode/internal/waf/action_captcha.go
Normal file
196
EdgeNode/internal/waf/action_captcha.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
CaptchaSeconds = 600 // 10 minutes
|
||||
CaptchaPath = "/WAF/VERIFY/CAPTCHA"
|
||||
)
|
||||
|
||||
type CaptchaAction struct {
|
||||
BaseAction
|
||||
|
||||
Life int32 `yaml:"life" json:"life"`
|
||||
MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数
|
||||
FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
|
||||
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"` // 是否全局有效
|
||||
|
||||
CountLetters int8 `yaml:"countLetters" json:"countLetters"`
|
||||
|
||||
CaptchaType firewallconfigs.CaptchaType `yaml:"captchaType" json:"captchaType"`
|
||||
|
||||
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"` // 内容轮廓
|
||||
|
||||
OneClickUIIsOn bool `yaml:"oneClickUIIsOn" json:"oneClickUIIsOn"` // 是否使用自定义UI
|
||||
OneClickUITitle string `yaml:"oneClickUITitle" json:"oneClickUITitle"` // 消息标题
|
||||
OneClickUIPrompt string `yaml:"oneClickUIPrompt" json:"oneClickUIPrompt"` // 消息提示
|
||||
OneClickUIShowRequestId bool `yaml:"oneClickUIShowRequestId" json:"oneClickUIShowRequestId"` // 是否显示请求ID
|
||||
OneClickUICss string `yaml:"oneClickUICss" json:"oneClickUICss"` // CSS样式
|
||||
OneClickUIFooter string `yaml:"oneClickUIFooter" json:"oneClickUIFooter"` // 页脚
|
||||
OneClickUIBody string `yaml:"oneClickUIBody" json:"oneClickUIBody"` // 内容轮廓
|
||||
|
||||
SlideUIIsOn bool `yaml:"sliceUIIsOn" json:"sliceUIIsOn"` // 是否使用自定义UI
|
||||
SlideUITitle string `yaml:"slideUITitle" json:"slideUITitle"` // 消息标题
|
||||
SlideUIPrompt string `yaml:"slideUIPrompt" json:"slideUIPrompt"` // 消息提示
|
||||
SlideUIShowRequestId bool `yaml:"SlideUIShowRequestId" json:"SlideUIShowRequestId"` // 是否显示请求ID
|
||||
SlideUICss string `yaml:"slideUICss" json:"slideUICss"` // CSS样式
|
||||
SlideUIFooter string `yaml:"slideUIFooter" json:"slideUIFooter"` // 页脚
|
||||
SlideUIBody string `yaml:"slideUIBody" json:"slideUIBody"` // 内容轮廓
|
||||
|
||||
GeeTestConfig *firewallconfigs.GeeTestConfig `yaml:"geeTestConfig" json:"geeTestConfig"` // 极验设置 MUST be struct
|
||||
|
||||
Lang string `yaml:"lang" json:"lang"` // 语言,zh-CN, en-US ...
|
||||
AddToWhiteList bool `yaml:"addToWhiteList" json:"addToWhiteList"` // 是否加入到白名单
|
||||
Scope string `yaml:"scope" json:"scope"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if len(this.CaptchaType) == 0 {
|
||||
this.CaptchaType = waf.DefaultCaptchaAction.CaptchaType
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) Code() string {
|
||||
return ActionCaptcha
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *CaptchaAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
// 是否在白名单中
|
||||
if SharedIPWhiteList.Contains(wafutils.ComposeIPType(set.Id, req), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 检查Cookie值
|
||||
var fullCookieName = captchaCookiePrefix + "_" + types.String(set.Id)
|
||||
cookie, err := req.WAFRaw().Cookie(fullCookieName)
|
||||
if err == nil && cookie != nil && len(cookie.Value) > 0 {
|
||||
var info = &AllowCookieInfo{}
|
||||
err = info.Decode(cookie.Value)
|
||||
if err == nil && set.Id == info.SetId && info.ExpiresAt > fasttime.Now().Unix() {
|
||||
// 重新记录到白名单
|
||||
SharedIPWhiteList.RecordIP(wafutils.ComposeIPType(set.Id, req), this.Scope, req.WAFServerId(), req.WAFRemoteIP(), info.ExpiresAt, waf.Id, false, group.Id, set.Id, "")
|
||||
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var refURL = req.WAFRaw().URL.String()
|
||||
|
||||
// 覆盖配置
|
||||
if strings.HasPrefix(refURL, CaptchaPath) {
|
||||
var info = req.WAFRaw().URL.Query().Get("info")
|
||||
if len(info) > 0 {
|
||||
var oldArg = &InfoArg{}
|
||||
decodeErr := oldArg.Decode(info)
|
||||
if decodeErr == nil && oldArg.IsValid() {
|
||||
refURL = oldArg.URL
|
||||
} else {
|
||||
// 兼容老版本
|
||||
m, err := utils.SimpleDecryptMap(info)
|
||||
if err == nil && m != nil {
|
||||
refURL = m.GetString("url")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var captchaConfig = &InfoArg{
|
||||
ActionId: this.ActionId(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
URL: refURL,
|
||||
PolicyId: waf.Id,
|
||||
GroupId: group.Id,
|
||||
SetId: set.Id,
|
||||
UseLocalFirewall: waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.AllowScopeGlobal),
|
||||
}
|
||||
info, err := utils.SimpleEncryptObject(captchaConfig)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_CAPTCHA_ACTION", "encode captcha config failed: "+err.Error())
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 占用一次失败次数
|
||||
CaptchaIncreaseFails(req, this, waf.Id, group.Id, set.Id, CaptchaPageCodeInit, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal))
|
||||
|
||||
req.DisableStat()
|
||||
req.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
|
||||
http.Redirect(writer, req.WAFRaw(), CaptchaPath+"?info="+url.QueryEscape(info)+"&from="+url.QueryEscape(refURL), http.StatusTemporaryRedirect)
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
13
EdgeNode/internal/waf/action_category.go
Normal file
13
EdgeNode/internal/waf/action_category.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
|
||||
type ActionCategory = string
|
||||
|
||||
const (
|
||||
ActionCategoryAllow ActionCategory = firewallconfigs.HTTPFirewallActionCategoryAllow
|
||||
ActionCategoryBlock ActionCategory = firewallconfigs.HTTPFirewallActionCategoryBlock
|
||||
ActionCategoryVerify ActionCategory = firewallconfigs.HTTPFirewallActionCategoryVerify
|
||||
)
|
||||
10
EdgeNode/internal/waf/action_config.go
Normal file
10
EdgeNode/internal/waf/action_config.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import "github.com/iwind/TeaGo/maps"
|
||||
|
||||
type ActionConfig struct {
|
||||
Code string `yaml:"code" json:"code"`
|
||||
Options maps.Map `yaml:"options" json:"options"`
|
||||
}
|
||||
13
EdgeNode/internal/waf/action_definition.go
Normal file
13
EdgeNode/internal/waf/action_definition.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package waf
|
||||
|
||||
import "reflect"
|
||||
|
||||
// ActionDefinition action definition
|
||||
type ActionDefinition struct {
|
||||
Name string
|
||||
Code ActionString
|
||||
Description string
|
||||
Category string // category: block, verify, allow
|
||||
Instance ActionInterface
|
||||
Type reflect.Type
|
||||
}
|
||||
86
EdgeNode/internal/waf/action_get_302.go
Normal file
86
EdgeNode/internal/waf/action_get_302.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Get302Path = "/WAF/VERIFY/GET"
|
||||
)
|
||||
|
||||
// Get302Action
|
||||
// 原理: origin url --> 302 verify url --> origin url
|
||||
// TODO 将来支持meta refresh验证
|
||||
type Get302Action struct {
|
||||
BaseAction
|
||||
|
||||
Life int32 `yaml:"life" json:"life"`
|
||||
Scope string `yaml:"scope" json:"scope"`
|
||||
}
|
||||
|
||||
func (this *Get302Action) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Get302Action) Code() string {
|
||||
return ActionGet302
|
||||
}
|
||||
|
||||
func (this *Get302Action) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Get302Action) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *Get302Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
// 仅限于Get
|
||||
if request.WAFRaw().Method != http.MethodGet {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 是否已经在白名单中
|
||||
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP()) {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
var m = InfoArg{
|
||||
URL: request.WAFRaw().URL.String(),
|
||||
Timestamp: time.Now().Unix(),
|
||||
Life: this.Life,
|
||||
Scope: this.Scope,
|
||||
PolicyId: waf.Id,
|
||||
GroupId: group.Id,
|
||||
SetId: set.Id,
|
||||
UseLocalFirewall: false,
|
||||
}
|
||||
info, err := utils.SimpleEncryptObject(m)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_GET_302_ACTION", "encode info failed: "+err.Error())
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
request.DisableStat()
|
||||
request.ProcessResponseHeaders(writer.Header(), http.StatusFound)
|
||||
http.Redirect(writer, request.WAFRaw(), Get302Path+"?info="+url.QueryEscape(info), http.StatusFound)
|
||||
|
||||
flusher, ok := writer.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
58
EdgeNode/internal/waf/action_go_group.go
Normal file
58
EdgeNode/internal/waf/action_go_group.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GoGroupAction struct {
|
||||
BaseAction
|
||||
|
||||
GroupId string `yaml:"groupId" json:"groupId"`
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) Code() string {
|
||||
return ActionGoGroup
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *GoGroupAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
var nextGroup = waf.FindRuleGroup(types.Int64(this.GroupId))
|
||||
if nextGroup == nil || !nextGroup.IsOn {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
|
||||
b, _, nextSet, err := nextGroup.MatchRequest(request)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF", "GO_GROUP_ACTION: "+err.Error())
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
|
||||
if !b {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
|
||||
return nextSet.PerformActions(waf, nextGroup, request, writer)
|
||||
}
|
||||
64
EdgeNode/internal/waf/action_go_set.go
Normal file
64
EdgeNode/internal/waf/action_go_set.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GoSetAction struct {
|
||||
BaseAction
|
||||
|
||||
GroupId string `yaml:"groupId" json:"groupId"`
|
||||
SetId string `yaml:"setId" json:"setId"`
|
||||
}
|
||||
|
||||
func (this *GoSetAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *GoSetAction) Code() string {
|
||||
return ActionGoSet
|
||||
}
|
||||
|
||||
func (this *GoSetAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *GoSetAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *GoSetAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
var nextGroup = waf.FindRuleGroup(types.Int64(this.GroupId))
|
||||
if nextGroup == nil || !nextGroup.IsOn {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
var nextSet = nextGroup.FindRuleSet(types.Int64(this.SetId))
|
||||
if nextSet == nil || !nextSet.IsOn {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
|
||||
b, _, err := nextSet.MatchRequest(request)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF", "GO_GROUP_ACTION: "+err.Error())
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
if !b {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
GoNextSet: true,
|
||||
}
|
||||
}
|
||||
return nextSet.PerformActions(waf, nextGroup, request, writer)
|
||||
}
|
||||
4
EdgeNode/internal/waf/action_instance.go
Normal file
4
EdgeNode/internal/waf/action_instance.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package waf
|
||||
|
||||
type Action struct {
|
||||
}
|
||||
31
EdgeNode/internal/waf/action_interface.go
Normal file
31
EdgeNode/internal/waf/action_interface.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ActionInterface interface {
|
||||
// Init 初始化
|
||||
Init(waf *WAF) error
|
||||
|
||||
// ActionId 读取ActionId
|
||||
ActionId() int64
|
||||
|
||||
// SetActionId 设置ID
|
||||
SetActionId(id int64)
|
||||
|
||||
// Code 代号
|
||||
Code() string
|
||||
|
||||
// IsAttack 是否为拦截攻击动作
|
||||
IsAttack() bool
|
||||
|
||||
// WillChange determine if the action will change the request
|
||||
WillChange() bool
|
||||
|
||||
// Perform the action
|
||||
Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult
|
||||
}
|
||||
159
EdgeNode/internal/waf/action_js_cookie.go
Normal file
159
EdgeNode/internal/waf/action_js_cookie.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JSCookieAction struct {
|
||||
BaseAction
|
||||
|
||||
Life int32 `yaml:"life" json:"life"`
|
||||
MaxFails int `yaml:"maxFails" json:"maxFails"` // 最大失败次数
|
||||
FailBlockTimeout int `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
|
||||
Scope string `yaml:"scope" json:"scope"`
|
||||
|
||||
FailBlockScopeAll bool `yaml:"failBlockScopeAll" json:"failBlockScopeAll"`
|
||||
}
|
||||
|
||||
func (this *JSCookieAction) Init(waf *WAF) error {
|
||||
|
||||
if waf.DefaultJSCookieAction != nil {
|
||||
if this.Life <= 0 {
|
||||
this.Life = waf.DefaultJSCookieAction.Life
|
||||
}
|
||||
if this.MaxFails <= 0 {
|
||||
this.MaxFails = waf.DefaultJSCookieAction.MaxFails
|
||||
}
|
||||
if this.FailBlockTimeout <= 0 {
|
||||
this.FailBlockTimeout = waf.DefaultJSCookieAction.FailBlockTimeout
|
||||
}
|
||||
if len(this.Scope) == 0 {
|
||||
this.Scope = waf.DefaultJSCookieAction.Scope
|
||||
}
|
||||
|
||||
this.FailBlockScopeAll = waf.DefaultJSCookieAction.FailBlockScopeAll
|
||||
}
|
||||
|
||||
if len(this.Scope) == 0 {
|
||||
this.Scope = firewallconfigs.FirewallScopeGlobal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *JSCookieAction) Code() string {
|
||||
return ActionJavascriptCookie
|
||||
}
|
||||
|
||||
func (this *JSCookieAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *JSCookieAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *JSCookieAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
// 是否在白名单中
|
||||
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
nodeConfig, err := nodeconfigs.SharedNodeConfig()
|
||||
if err != nil {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
var life = this.Life
|
||||
if life <= 0 {
|
||||
life = 3600
|
||||
}
|
||||
|
||||
// 检查Cookie
|
||||
var cookieName = "ge_js_validator_" + types.String(set.Id)
|
||||
cookie, err := req.WAFRaw().Cookie(cookieName)
|
||||
if err == nil && cookie != nil {
|
||||
var cookieValue = cookie.Value
|
||||
if len(cookieValue) > 10 {
|
||||
var pieces = strings.Split(cookieValue, "@")
|
||||
if len(pieces) == 3 {
|
||||
var timestamp = pieces[0]
|
||||
var sum = pieces[2]
|
||||
if types.Int64(timestamp) >= time.Now().Unix()-int64(life) && fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId))) == sum {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req.ProcessResponseHeaders(writer.Header(), http.StatusOK)
|
||||
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
writer.Header().Set("Cache-Control", "no-cache")
|
||||
|
||||
var timestamp = types.String(time.Now().Unix())
|
||||
|
||||
var cookieValue = timestamp + "@" + types.String(set.Id) + "@" + fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+types.String(set.Id)+"@"+nodeConfig.NodeId)))
|
||||
var respHTML = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title></title>
|
||||
<meta charset="UTF-8"/>
|
||||
<script type="text/javascript">
|
||||
document.cookie = "` + cookieName + `=` + cookieValue + `; path=/; max-age=` + types.String(life) + `;";
|
||||
window.location.reload();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>`
|
||||
writer.Header().Set("Content-Length", types.String(len(respHTML)))
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
_, _ = writer.Write([]byte(respHTML))
|
||||
|
||||
// 记录失败次数
|
||||
this.increaseFails(req, waf.Id, group.Id, set.Id, waf.UseLocalFirewall && (this.FailBlockScopeAll || this.Scope == firewallconfigs.FirewallScopeGlobal))
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
|
||||
func (this *JSCookieAction) increaseFails(req requests.Request, policyId int64, groupId int64, setId int64, useLocalFirewall bool) (goNext bool) {
|
||||
var maxFails = this.MaxFails
|
||||
var failBlockTimeout = this.FailBlockTimeout
|
||||
|
||||
if maxFails <= 0 {
|
||||
maxFails = 10 // 默认10次
|
||||
} else if maxFails <= 5 {
|
||||
maxFails = 5 // 不能小于3,防止意外刷新出现
|
||||
}
|
||||
if failBlockTimeout <= 0 {
|
||||
failBlockTimeout = 1800 // 默认1800s
|
||||
}
|
||||
|
||||
var key = "WAF:JS_COOKIE:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId()) + ":" + req.WAFRaw().URL.String()
|
||||
|
||||
var countFails = counters.SharedCounter.IncreaseKey(key, 300)
|
||||
if int(countFails) >= maxFails {
|
||||
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeServer, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "JS_COOKIE验证连续失败超过"+types.String(maxFails)+"次")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
32
EdgeNode/internal/waf/action_log.go
Normal file
32
EdgeNode/internal/waf/action_log.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type LogAction struct {
|
||||
BaseAction
|
||||
}
|
||||
|
||||
func (this *LogAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *LogAction) Code() string {
|
||||
return ActionLog
|
||||
}
|
||||
|
||||
func (this *LogAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *LogAction) WillChange() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *LogAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
95
EdgeNode/internal/waf/action_notify.go
Normal file
95
EdgeNode/internal/waf/action_notify.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type notifyTask struct {
|
||||
ServerId int64
|
||||
HttpFirewallPolicyId int64
|
||||
HttpFirewallRuleGroupId int64
|
||||
HttpFirewallRuleSetId int64
|
||||
CreatedAt int64
|
||||
}
|
||||
|
||||
var notifyChan = make(chan *notifyTask, 128)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_NOTIFY_ACTION", "create rpc client failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for task := range notifyChan {
|
||||
_, err = rpcClient.FirewallRPC.NotifyHTTPFirewallEvent(rpcClient.Context(), &pb.NotifyHTTPFirewallEventRequest{
|
||||
ServerId: task.ServerId,
|
||||
HttpFirewallPolicyId: task.HttpFirewallPolicyId,
|
||||
HttpFirewallRuleGroupId: task.HttpFirewallRuleGroupId,
|
||||
HttpFirewallRuleSetId: task.HttpFirewallRuleSetId,
|
||||
CreatedAt: task.CreatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_NOTIFY_ACTION", "notify failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type NotifyAction struct {
|
||||
BaseAction
|
||||
}
|
||||
|
||||
func (this *NotifyAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *NotifyAction) Code() string {
|
||||
return ActionNotify
|
||||
}
|
||||
|
||||
func (this *NotifyAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WillChange determine if the action will change the request
|
||||
func (this *NotifyAction) WillChange() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Perform the action
|
||||
func (this *NotifyAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
select {
|
||||
case notifyChan <- ¬ifyTask{
|
||||
ServerId: request.WAFServerId(),
|
||||
HttpFirewallPolicyId: types.Int64(waf.Id),
|
||||
HttpFirewallRuleGroupId: types.Int64(group.Id),
|
||||
HttpFirewallRuleSetId: types.Int64(set.Id),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
77
EdgeNode/internal/waf/action_page.go
Normal file
77
EdgeNode/internal/waf/action_page.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PageAction struct {
|
||||
BaseAction
|
||||
|
||||
UseDefault bool `yaml:"useDefault" json:"useDefault"`
|
||||
Status int `yaml:"status" json:"status"`
|
||||
Body string `yaml:"body" json:"body"`
|
||||
}
|
||||
|
||||
func (this *PageAction) Init(waf *WAF) error {
|
||||
if waf.DefaultPageAction != nil {
|
||||
if this.Status <= 0 || this.UseDefault {
|
||||
this.Status = waf.DefaultPageAction.Status
|
||||
}
|
||||
if len(this.Body) == 0 || this.UseDefault {
|
||||
this.Body = waf.DefaultPageAction.Body
|
||||
}
|
||||
}
|
||||
|
||||
if this.Status <= 0 {
|
||||
this.Status = http.StatusForbidden
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *PageAction) Code() string {
|
||||
return ActionPage
|
||||
}
|
||||
|
||||
func (this *PageAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WillChange determine if the action will change the request
|
||||
func (this *PageAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Perform the action
|
||||
func (this *PageAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
if writer == nil {
|
||||
return PerformResult{}
|
||||
}
|
||||
|
||||
request.ProcessResponseHeaders(writer.Header(), this.Status)
|
||||
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
writer.WriteHeader(this.Status)
|
||||
|
||||
var body = this.Body
|
||||
if len(body) == 0 {
|
||||
body = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>403 Forbidden</title>
|
||||
<style>
|
||||
address { line-height: 1.8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>403 Forbidden By WAF</h1>
|
||||
<address>Connection: ${remoteAddr} (Client) -> ${serverAddr} (Server)</address>
|
||||
<address>Request ID: ${requestId}</address>
|
||||
</body>
|
||||
</html>`
|
||||
}
|
||||
_, _ = writer.Write([]byte(request.Format(body)))
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
147
EdgeNode/internal/waf/action_post_307.go
Normal file
147
EdgeNode/internal/waf/action_post_307.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/bytepool"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Post307Action struct {
|
||||
Life int32 `yaml:"life" json:"life"`
|
||||
Scope string `yaml:"scope" json:"scope"`
|
||||
|
||||
BaseAction
|
||||
}
|
||||
|
||||
func (this *Post307Action) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Post307Action) Code() string {
|
||||
return ActionPost307
|
||||
}
|
||||
|
||||
func (this *Post307Action) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Post307Action) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *Post307Action) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
const cookieName = "WAF_VALIDATOR_ID"
|
||||
|
||||
// 仅限于POST
|
||||
if request.WAFRaw().Method != http.MethodPost {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 是否已经在白名单中
|
||||
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP()) {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否有Cookie
|
||||
cookie, cookieErr := request.WAFRaw().Cookie(cookieName)
|
||||
if cookieErr == nil && cookie != nil {
|
||||
var remoteIP string
|
||||
var life int64
|
||||
var setId int64
|
||||
var policyId int64
|
||||
var groupId int64
|
||||
var timestamp int64
|
||||
|
||||
var infoArg = &InfoArg{}
|
||||
var success bool
|
||||
decodeErr := infoArg.Decode(cookie.Value)
|
||||
if decodeErr == nil && infoArg.IsValid() {
|
||||
success = true
|
||||
|
||||
remoteIP = infoArg.RemoteIP
|
||||
life = int64(infoArg.Life)
|
||||
setId = infoArg.SetId
|
||||
policyId = infoArg.PolicyId
|
||||
groupId = infoArg.GroupId
|
||||
timestamp = infoArg.Timestamp
|
||||
} else {
|
||||
// 兼容老版本
|
||||
m, decodeMapErr := utils.SimpleDecryptMap(cookie.Value)
|
||||
if decodeMapErr == nil {
|
||||
success = true
|
||||
|
||||
remoteIP = m.GetString("remoteIP")
|
||||
timestamp = m.GetInt64("timestamp")
|
||||
life = m.GetInt64("life")
|
||||
setId = m.GetInt64("setId")
|
||||
groupId = m.GetInt64("groupId")
|
||||
policyId = m.GetInt64("policyId")
|
||||
}
|
||||
}
|
||||
|
||||
if success && remoteIP == request.WAFRemoteIP() && time.Now().Unix() < timestamp+10 {
|
||||
if life <= 0 {
|
||||
life = 600 // 默认10分钟
|
||||
}
|
||||
SharedIPWhiteList.RecordIP("set:"+types.String(setId), this.Scope, request.WAFServerId(), request.WAFRemoteIP(), time.Now().Unix()+life, policyId, false, groupId, setId, "")
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var m = &InfoArg{
|
||||
Timestamp: time.Now().Unix(),
|
||||
Life: this.Life,
|
||||
Scope: this.Scope,
|
||||
PolicyId: waf.Id,
|
||||
GroupId: group.Id,
|
||||
SetId: set.Id,
|
||||
RemoteIP: request.WAFRemoteIP(),
|
||||
UseLocalFirewall: false,
|
||||
}
|
||||
info, err := utils.SimpleEncryptObject(m)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_POST_307_ACTION", "encode info failed: "+err.Error())
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
|
||||
// 清空请求内容
|
||||
var req = request.WAFRaw()
|
||||
if req.ContentLength > 0 && req.Body != nil {
|
||||
var buf = bytepool.Pool16k.Get()
|
||||
_, _ = io.CopyBuffer(io.Discard, req.Body, buf.Bytes)
|
||||
bytepool.Pool16k.Put(buf)
|
||||
_ = req.Body.Close()
|
||||
}
|
||||
|
||||
// 设置Cookie
|
||||
http.SetCookie(writer, &http.Cookie{
|
||||
Name: cookieName,
|
||||
Path: "/",
|
||||
MaxAge: 10,
|
||||
Value: info,
|
||||
})
|
||||
|
||||
request.DisableStat()
|
||||
request.ProcessResponseHeaders(writer.Header(), http.StatusTemporaryRedirect)
|
||||
http.Redirect(writer, request.WAFRaw(), request.WAFRaw().URL.String(), http.StatusTemporaryRedirect)
|
||||
|
||||
flusher, ok := writer.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
254
EdgeNode/internal/waf/action_record_ip.go
Normal file
254
EdgeNode/internal/waf/action_record_ip.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ipconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type recordIPTask struct {
|
||||
ip string
|
||||
listId int64
|
||||
expiresAt int64
|
||||
level string
|
||||
serverId int64
|
||||
|
||||
reason string
|
||||
|
||||
sourceServerId int64
|
||||
sourceHTTPFirewallPolicyId int64
|
||||
sourceHTTPFirewallRuleGroupId int64
|
||||
sourceHTTPFirewallRuleSetId int64
|
||||
}
|
||||
|
||||
var recordIPTaskChan = make(chan *recordIPTask, 512)
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
var memGB = memutils.SystemMemoryGB()
|
||||
if memGB > 16 {
|
||||
recordIPTaskChan = make(chan *recordIPTask, 4<<10)
|
||||
} else if memGB > 8 {
|
||||
recordIPTaskChan = make(chan *recordIPTask, 2<<10)
|
||||
} else if memGB > 4 {
|
||||
recordIPTaskChan = make(chan *recordIPTask, 1<<10)
|
||||
}
|
||||
|
||||
events.On(events.EventLoaded, func() {
|
||||
goman.New(func() {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_RECORD_IP_ACTION", "create rpc client failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
const maxItems = 512 // 每次上传的最大IP数
|
||||
|
||||
for {
|
||||
var pbItemMap = map[string]*pb.CreateIPItemsRequest_IPItem{} // ip => IPItem
|
||||
|
||||
func() {
|
||||
for {
|
||||
select {
|
||||
case task := <-recordIPTaskChan:
|
||||
var ipType = "ipv4"
|
||||
if strings.Contains(task.ip, ":") {
|
||||
ipType = "ipv6"
|
||||
}
|
||||
var reason = task.reason
|
||||
if len(reason) == 0 {
|
||||
reason = "触发WAF规则自动加入"
|
||||
}
|
||||
|
||||
pbItemMap[task.ip] = &pb.CreateIPItemsRequest_IPItem{
|
||||
IpListId: task.listId,
|
||||
Value: task.ip,
|
||||
IpFrom: task.ip,
|
||||
IpTo: "",
|
||||
ExpiredAt: task.expiresAt,
|
||||
Reason: reason,
|
||||
Type: ipType,
|
||||
EventLevel: task.level,
|
||||
ServerId: task.serverId,
|
||||
SourceNodeId: teaconst.NodeId,
|
||||
SourceServerId: task.sourceServerId,
|
||||
SourceHTTPFirewallPolicyId: task.sourceHTTPFirewallPolicyId,
|
||||
SourceHTTPFirewallRuleGroupId: task.sourceHTTPFirewallRuleGroupId,
|
||||
SourceHTTPFirewallRuleSetId: task.sourceHTTPFirewallRuleSetId,
|
||||
}
|
||||
|
||||
if len(pbItemMap) >= maxItems {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if len(pbItemMap) > 0 {
|
||||
var pbItems = []*pb.CreateIPItemsRequest_IPItem{}
|
||||
for _, pbItem := range pbItemMap {
|
||||
pbItems = append(pbItems, pbItem)
|
||||
}
|
||||
|
||||
for i := 0; i < 5; /* max tries */ i++ {
|
||||
_, err = rpcClient.IPItemRPC.CreateIPItems(rpcClient.Context(), &pb.CreateIPItemsRequest{IpItems: pbItems})
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_RECORD_IP_ACTION", "create ip item failed: "+err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type RecordIPAction struct {
|
||||
BaseAction
|
||||
|
||||
Type string `yaml:"type" json:"type"`
|
||||
IPListId int64 `yaml:"ipListId" json:"ipListId"`
|
||||
IPListIsDeleted bool `yaml:"ipListIsDeleted" json:"ipListIsDeleted"`
|
||||
Level string `yaml:"level" json:"level"`
|
||||
Timeout int32 `yaml:"timeout" json:"timeout"`
|
||||
Scope string `yaml:"scope" json:"scope"`
|
||||
}
|
||||
|
||||
func (this *RecordIPAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RecordIPAction) Code() string {
|
||||
return ActionRecordIP
|
||||
}
|
||||
|
||||
func (this *RecordIPAction) IsAttack() bool {
|
||||
return this.Type == ipconfigs.IPListTypeBlack
|
||||
}
|
||||
|
||||
func (this *RecordIPAction) WillChange() bool {
|
||||
return this.Type == ipconfigs.IPListTypeBlack
|
||||
}
|
||||
|
||||
func (this *RecordIPAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
var ipListId = this.IPListId
|
||||
|
||||
if ipListId <= 0 || firewallconfigs.IsGlobalListId(ipListId) {
|
||||
// server or policy list ids
|
||||
switch this.Type {
|
||||
case ipconfigs.IPListTypeWhite:
|
||||
ipListId = waf.AllowListId
|
||||
case ipconfigs.IPListTypeBlack:
|
||||
ipListId = waf.DenyListId
|
||||
case ipconfigs.IPListTypeGrey:
|
||||
ipListId = waf.GreyListId
|
||||
}
|
||||
|
||||
// global list ids
|
||||
if ipListId <= 0 {
|
||||
ipListId = firewallconfigs.FindGlobalListIdWithType(this.Type)
|
||||
if ipListId <= 0 {
|
||||
ipListId = firewallconfigs.GlobalBlackListId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 是否已删除
|
||||
var ipListIsAvailable = (firewallconfigs.IsGlobalListId(ipListId)) || (ipListId > 0 && !this.IPListIsDeleted && !ExistDeletedIPList(ipListId))
|
||||
|
||||
// 是否在本地白名单中
|
||||
if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP()) {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
IsAllowed: true,
|
||||
AllowScope: AllowScopeGlobal,
|
||||
}
|
||||
}
|
||||
|
||||
var timeout = this.Timeout
|
||||
var isForever = false
|
||||
if timeout <= 0 {
|
||||
isForever = true
|
||||
timeout = 86400 // 1天
|
||||
}
|
||||
var expiresAt = time.Now().Unix() + int64(timeout)
|
||||
|
||||
var isGrey bool
|
||||
var isWhite bool
|
||||
|
||||
if this.Type == ipconfigs.IPListTypeBlack {
|
||||
request.ProcessResponseHeaders(writer.Header(), http.StatusForbidden)
|
||||
writer.WriteHeader(http.StatusForbidden)
|
||||
|
||||
request.WAFClose()
|
||||
|
||||
// 先加入本地的黑名单
|
||||
if ipListIsAvailable {
|
||||
SharedIPBlackList.Add(IPTypeAll, this.Scope, request.WAFServerId(), request.WAFRemoteIP(), expiresAt)
|
||||
}
|
||||
} else if this.Type == ipconfigs.IPListTypeGrey {
|
||||
isGrey = true
|
||||
} else {
|
||||
isWhite = true
|
||||
|
||||
// 加入本地白名单
|
||||
if ipListIsAvailable {
|
||||
SharedIPWhiteList.Add("set:"+types.String(set.Id), this.Scope, request.WAFServerId(), request.WAFRemoteIP(), expiresAt)
|
||||
}
|
||||
}
|
||||
|
||||
// 上报
|
||||
if ipListId > 0 && ipListIsAvailable {
|
||||
var serverId int64
|
||||
if this.Scope == firewallconfigs.FirewallScopeServer {
|
||||
serverId = request.WAFServerId()
|
||||
}
|
||||
|
||||
var realExpiresAt = expiresAt
|
||||
if isForever {
|
||||
realExpiresAt = 0
|
||||
}
|
||||
|
||||
select {
|
||||
case recordIPTaskChan <- &recordIPTask{
|
||||
ip: request.WAFRemoteIP(),
|
||||
listId: ipListId,
|
||||
expiresAt: realExpiresAt,
|
||||
level: this.Level,
|
||||
serverId: serverId,
|
||||
sourceServerId: request.WAFServerId(),
|
||||
sourceHTTPFirewallPolicyId: waf.Id,
|
||||
sourceHTTPFirewallRuleGroupId: group.Id,
|
||||
sourceHTTPFirewallRuleSetId: set.Id,
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return PerformResult{
|
||||
ContinueRequest: isWhite || isGrey,
|
||||
IsAllowed: isWhite,
|
||||
AllowScope: AllowScopeGlobal,
|
||||
}
|
||||
}
|
||||
44
EdgeNode/internal/waf/action_redirect.go
Normal file
44
EdgeNode/internal/waf/action_redirect.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type RedirectAction struct {
|
||||
BaseAction
|
||||
|
||||
Status int `yaml:"status" json:"status"`
|
||||
URL string `yaml:"url" json:"url"`
|
||||
}
|
||||
|
||||
func (this *RedirectAction) Init(waf *WAF) error {
|
||||
if this.Status <= 0 {
|
||||
this.Status = http.StatusTemporaryRedirect
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *RedirectAction) Code() string {
|
||||
return ActionRedirect
|
||||
}
|
||||
|
||||
func (this *RedirectAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// WillChange determine if the action will change the request
|
||||
func (this *RedirectAction) WillChange() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Perform the action
|
||||
func (this *RedirectAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
request.ProcessResponseHeaders(writer.Header(), this.Status)
|
||||
writer.Header().Set("Location", this.URL)
|
||||
writer.WriteHeader(this.Status)
|
||||
|
||||
return PerformResult{}
|
||||
}
|
||||
34
EdgeNode/internal/waf/action_tag.go
Normal file
34
EdgeNode/internal/waf/action_tag.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type TagAction struct {
|
||||
BaseAction
|
||||
|
||||
Tags []string `yaml:"tags" json:"tags"`
|
||||
}
|
||||
|
||||
func (this *TagAction) Init(waf *WAF) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *TagAction) Code() string {
|
||||
return ActionTag
|
||||
}
|
||||
|
||||
func (this *TagAction) IsAttack() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *TagAction) WillChange() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *TagAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, request requests.Request, writer http.ResponseWriter) PerformResult {
|
||||
return PerformResult{
|
||||
ContinueRequest: true,
|
||||
}
|
||||
}
|
||||
109
EdgeNode/internal/waf/action_types.go
Normal file
109
EdgeNode/internal/waf/action_types.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package waf
|
||||
|
||||
import "reflect"
|
||||
|
||||
type ActionString = string
|
||||
|
||||
const (
|
||||
ActionLog ActionString = "log" // allow and log
|
||||
ActionBlock ActionString = "block" // block
|
||||
ActionCaptcha ActionString = "captcha" // block and show captcha
|
||||
ActionJavascriptCookie ActionString = "js_cookie" // js cookie
|
||||
ActionNotify ActionString = "notify" // 告警
|
||||
ActionGet302 ActionString = "get_302" // 针对GET的302重定向认证
|
||||
ActionPost307 ActionString = "post_307" // 针对POST的307重定向认证
|
||||
ActionRecordIP ActionString = "record_ip" // 记录IP
|
||||
ActionTag ActionString = "tag" // 标签
|
||||
ActionPage ActionString = "page" // 显示网页
|
||||
ActionRedirect ActionString = "redirect" // 跳转
|
||||
ActionAllow ActionString = "allow" // allow
|
||||
ActionGoGroup ActionString = "go_group" // go to next rule group
|
||||
ActionGoSet ActionString = "go_set" // go to next rule set
|
||||
)
|
||||
|
||||
var AllActions = []*ActionDefinition{
|
||||
{
|
||||
Name: "阻止",
|
||||
Code: ActionBlock,
|
||||
Instance: new(BlockAction),
|
||||
Type: reflect.TypeOf(new(BlockAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "允许通过",
|
||||
Code: ActionAllow,
|
||||
Instance: new(AllowAction),
|
||||
Type: reflect.TypeOf(new(AllowAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "允许并记录日志",
|
||||
Code: ActionLog,
|
||||
Instance: new(LogAction),
|
||||
Type: reflect.TypeOf(new(LogAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "Captcha验证码",
|
||||
Code: ActionCaptcha,
|
||||
Instance: new(CaptchaAction),
|
||||
Type: reflect.TypeOf(new(CaptchaAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "JS Cookie验证",
|
||||
Code: ActionJavascriptCookie,
|
||||
Instance: new(JSCookieAction),
|
||||
Type: reflect.TypeOf(new(JSCookieAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "告警",
|
||||
Code: ActionNotify,
|
||||
Instance: new(NotifyAction),
|
||||
Type: reflect.TypeOf(new(NotifyAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "GET 302",
|
||||
Code: ActionGet302,
|
||||
Instance: new(Get302Action),
|
||||
Type: reflect.TypeOf(new(Get302Action)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "POST 307",
|
||||
Code: ActionPost307,
|
||||
Instance: new(Post307Action),
|
||||
Type: reflect.TypeOf(new(Post307Action)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "记录IP",
|
||||
Code: ActionRecordIP,
|
||||
Instance: new(RecordIPAction),
|
||||
Type: reflect.TypeOf(new(RecordIPAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "标签",
|
||||
Code: ActionTag,
|
||||
Instance: new(TagAction),
|
||||
Type: reflect.TypeOf(new(TagAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "显示页面",
|
||||
Code: ActionPage,
|
||||
Instance: new(PageAction),
|
||||
Type: reflect.TypeOf(new(PageAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "跳转",
|
||||
Code: ActionRedirect,
|
||||
Instance: new(RedirectAction),
|
||||
Type: reflect.TypeOf(new(RedirectAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "跳到下一个规则分组",
|
||||
Code: ActionGoGroup,
|
||||
Instance: new(GoGroupAction),
|
||||
Type: reflect.TypeOf(new(GoGroupAction)).Elem(),
|
||||
},
|
||||
{
|
||||
Name: "跳到下一个规则集",
|
||||
Code: ActionGoSet,
|
||||
Instance: new(GoSetAction),
|
||||
Type: reflect.TypeOf(new(GoSetAction)).Elem(),
|
||||
},
|
||||
}
|
||||
51
EdgeNode/internal/waf/action_utils.go
Normal file
51
EdgeNode/internal/waf/action_utils.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var seedActionId int64 = 1
|
||||
|
||||
func FindActionInstance(action ActionString, options maps.Map) ActionInterface {
|
||||
for _, def := range AllActions {
|
||||
if def.Code == action {
|
||||
if def.Type != nil {
|
||||
// create new instance
|
||||
var ptrValue = reflect.New(def.Type)
|
||||
var instance = ptrValue.Interface().(ActionInterface)
|
||||
instance.SetActionId(atomic.AddInt64(&seedActionId, 1))
|
||||
|
||||
if len(options) > 0 {
|
||||
optionsJSON, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_FindActionInstance", "encode options to json failed: "+err.Error())
|
||||
} else {
|
||||
err = json.Unmarshal(optionsJSON, instance)
|
||||
if err != nil {
|
||||
remotelogs.Error("WAF_FindActionInstance", "decode options from json failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
// return shared instance
|
||||
return def.Instance
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FindActionName(action ActionString) string {
|
||||
for _, def := range AllActions {
|
||||
if def.Code == action {
|
||||
return def.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
40
EdgeNode/internal/waf/action_utils_test.go
Normal file
40
EdgeNode/internal/waf/action_utils_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package waf_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFindActionInstance(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
t.Logf("ActionBlock: %p", waf.FindActionInstance(waf.ActionBlock, nil))
|
||||
t.Logf("ActionBlock: %p", waf.FindActionInstance(waf.ActionBlock, nil))
|
||||
t.Logf("ActionGoGroup: %p", waf.FindActionInstance(waf.ActionGoGroup, nil))
|
||||
t.Logf("ActionGoGroup: %p", waf.FindActionInstance(waf.ActionGoGroup, nil))
|
||||
t.Logf("ActionGoSet: %p", waf.FindActionInstance(waf.ActionGoSet, nil))
|
||||
t.Logf("ActionGoSet: %p", waf.FindActionInstance(waf.ActionGoSet, nil))
|
||||
t.Logf("ActionGoSet: %#v", waf.FindActionInstance(waf.ActionGoSet, maps.Map{"groupId": "a", "setId": "b"}))
|
||||
|
||||
a.IsTrue(waf.FindActionInstance(waf.ActionGoSet, nil) != waf.FindActionInstance(waf.ActionGoSet, nil))
|
||||
}
|
||||
|
||||
func TestFindActionInstance_Options(t *testing.T) {
|
||||
//t.Logf("%p", FindActionInstance(ActionBlock, maps.Map{}))
|
||||
//t.Logf("%p", FindActionInstance(ActionBlock, maps.Map{}))
|
||||
//logs.PrintAsJSON(FindActionInstance(ActionBlock, maps.Map{}), t)
|
||||
logs.PrintAsJSON(waf.FindActionInstance(waf.ActionBlock, maps.Map{
|
||||
"timeout": 3600,
|
||||
}), t)
|
||||
}
|
||||
|
||||
func BenchmarkFindActionInstance(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
for i := 0; i < b.N; i++ {
|
||||
waf.FindActionInstance(waf.ActionGoSet, nil)
|
||||
}
|
||||
}
|
||||
46
EdgeNode/internal/waf/allow_cookie_info.go
Normal file
46
EdgeNode/internal/waf/allow_cookie_info.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
)
|
||||
|
||||
type AllowCookieInfo struct {
|
||||
SetId int64
|
||||
ExpiresAt int64
|
||||
}
|
||||
|
||||
func (this *AllowCookieInfo) Encode() (string, error) {
|
||||
if this.SetId < 0 {
|
||||
this.SetId = 0
|
||||
}
|
||||
if this.ExpiresAt < 0 {
|
||||
this.ExpiresAt = 0
|
||||
}
|
||||
|
||||
var result = make([]byte, 16)
|
||||
binary.BigEndian.PutUint64(result, uint64(this.SetId))
|
||||
binary.BigEndian.PutUint64(result[8:], uint64(this.ExpiresAt))
|
||||
return base64.StdEncoding.EncodeToString(utils.SimpleEncrypt(result)), nil
|
||||
}
|
||||
|
||||
func (this *AllowCookieInfo) Decode(encodedString string) error {
|
||||
data, err := base64.StdEncoding.DecodeString(encodedString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var result = utils.SimpleDecrypt(data)
|
||||
if len(result) != 16 {
|
||||
return errors.New("unexpected data length")
|
||||
}
|
||||
|
||||
this.SetId = int64(binary.BigEndian.Uint64(result[:8]))
|
||||
this.ExpiresAt = int64(binary.BigEndian.Uint64(result[8:16]))
|
||||
|
||||
return nil
|
||||
}
|
||||
35
EdgeNode/internal/waf/allow_cookie_info_test.go
Normal file
35
EdgeNode/internal/waf/allow_cookie_info_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllowCookieInfo_Encode(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var info = &waf.AllowCookieInfo{
|
||||
SetId: 123,
|
||||
ExpiresAt: fasttime.Now().Unix(),
|
||||
}
|
||||
data, err := info.Encode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("encrypted: ["+types.String(len(data))+"]", data)
|
||||
|
||||
var info2 = &waf.AllowCookieInfo{}
|
||||
err = info2.Decode(data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("%+v", info2)
|
||||
a.IsTrue(info.SetId == info2.SetId)
|
||||
a.IsTrue(info.ExpiresAt == info2.ExpiresAt)
|
||||
}
|
||||
50
EdgeNode/internal/waf/captcha_counter.go
Normal file
50
EdgeNode/internal/waf/captcha_counter.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CaptchaPageCode = string
|
||||
|
||||
const (
|
||||
CaptchaPageCodeInit CaptchaPageCode = "init"
|
||||
CaptchaPageCodeShow CaptchaPageCode = "show"
|
||||
CaptchaPageCodeImage CaptchaPageCode = "image"
|
||||
CaptchaPageCodeSubmit CaptchaPageCode = "submit"
|
||||
)
|
||||
|
||||
// CaptchaIncreaseFails 增加Captcha失败次数,以便后续操作
|
||||
func CaptchaIncreaseFails(req requests.Request, actionConfig *CaptchaAction, policyId int64, groupId int64, setId int64, pageCode CaptchaPageCode, useLocalFirewall bool) (goNext bool) {
|
||||
var maxFails = actionConfig.MaxFails
|
||||
var failBlockTimeout = actionConfig.FailBlockTimeout
|
||||
if maxFails > 0 && failBlockTimeout > 0 {
|
||||
if maxFails <= 3 {
|
||||
maxFails = 3 // 不能小于3,防止意外刷新出现
|
||||
}
|
||||
var countFails = counters.SharedCounter.IncreaseKey(CaptchaCacheKey(req, pageCode), 300)
|
||||
if int(countFails) >= maxFails {
|
||||
SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeServer, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败超过"+types.String(maxFails)+"次")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// CaptchaDeleteCacheKey 清除计数
|
||||
func CaptchaDeleteCacheKey(req requests.Request) {
|
||||
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeInit))
|
||||
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeShow))
|
||||
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeImage))
|
||||
counters.SharedCounter.ResetKey(CaptchaCacheKey(req, CaptchaPageCodeSubmit))
|
||||
}
|
||||
|
||||
// CaptchaCacheKey 获取Captcha缓存Key
|
||||
func CaptchaCacheKey(req requests.Request, pageCode CaptchaPageCode) string {
|
||||
return "WAF:CAPTCHA:FAILS:" + pageCode + ":" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId())
|
||||
}
|
||||
71
EdgeNode/internal/waf/captcha_generator.go
Normal file
71
EdgeNode/internal/waf/captcha_generator.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/dchest/captcha"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CaptchaGenerator captcha generator
|
||||
type CaptchaGenerator struct {
|
||||
store captcha.Store
|
||||
}
|
||||
|
||||
func NewCaptchaGenerator() *CaptchaGenerator {
|
||||
return &CaptchaGenerator{
|
||||
store: captcha.NewMemoryStore(100_000, 5*time.Minute),
|
||||
}
|
||||
}
|
||||
|
||||
// NewCaptcha create new captcha
|
||||
func (this *CaptchaGenerator) NewCaptcha(length int) (captchaId string) {
|
||||
captchaId = rands.HexString(16)
|
||||
|
||||
if length <= 0 || length > 20 {
|
||||
length = 4
|
||||
}
|
||||
|
||||
this.store.Set(captchaId, captcha.RandomDigits(length))
|
||||
return
|
||||
}
|
||||
|
||||
// WriteImage write image to front writer
|
||||
func (this *CaptchaGenerator) WriteImage(w io.Writer, id string, width, height int) error {
|
||||
var d = this.store.Get(id, false)
|
||||
if d == nil {
|
||||
return captcha.ErrNotFound
|
||||
}
|
||||
_, err := captcha.NewImage(id, d, width, height).WriteTo(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify user input
|
||||
func (this *CaptchaGenerator) Verify(id string, digits string) bool {
|
||||
var countDigits = len(digits)
|
||||
if countDigits == 0 {
|
||||
return false
|
||||
}
|
||||
var value = this.store.Get(id, true)
|
||||
if len(value) != countDigits {
|
||||
return false
|
||||
}
|
||||
|
||||
var nb = make([]byte, countDigits)
|
||||
for i := 0; i < countDigits; i++ {
|
||||
var d = digits[i]
|
||||
if d >= '0' && d <= '9' {
|
||||
nb[i] = d - '0'
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.Equal(nb, value)
|
||||
}
|
||||
|
||||
// Get captcha data
|
||||
func (this *CaptchaGenerator) Get(id string) []byte {
|
||||
return this.store.Get(id, false)
|
||||
}
|
||||
87
EdgeNode/internal/waf/captcha_generator_test.go
Normal file
87
EdgeNode/internal/waf/captcha_generator_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCaptchaGenerator_NewCaptcha(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var generator = waf.NewCaptchaGenerator()
|
||||
var captchaId = generator.NewCaptcha(6)
|
||||
t.Log("captchaId:", captchaId)
|
||||
|
||||
var digits = generator.Get(captchaId)
|
||||
var s []string
|
||||
for _, digit := range digits {
|
||||
s = append(s, types.String(digit))
|
||||
}
|
||||
t.Log(strings.Join(s, " "))
|
||||
|
||||
a.IsTrue(generator.Verify(captchaId, strings.Join(s, "")))
|
||||
a.IsFalse(generator.Verify(captchaId, strings.Join(s, "")))
|
||||
}
|
||||
|
||||
func TestCaptchaGenerator_NewCaptcha_UTF8(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var generator = waf.NewCaptchaGenerator()
|
||||
var captchaId = generator.NewCaptcha(6)
|
||||
t.Log("captchaId:", captchaId)
|
||||
|
||||
var digits = generator.Get(captchaId)
|
||||
var s []string
|
||||
for _, digit := range digits {
|
||||
s = append(s, types.String(digit))
|
||||
}
|
||||
t.Log(strings.Join(s, " "))
|
||||
|
||||
a.IsFalse(generator.Verify(captchaId, "中文真的很长"))
|
||||
}
|
||||
|
||||
func TestCaptchaGenerator_NewCaptcha_Memory(t *testing.T) {
|
||||
runtime.GC()
|
||||
|
||||
var stat1 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat1)
|
||||
|
||||
var generator = waf.NewCaptchaGenerator()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
generator.NewCaptcha(6)
|
||||
}
|
||||
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
|
||||
var stat2 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat2)
|
||||
|
||||
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>10, "KiB")
|
||||
|
||||
_ = generator
|
||||
}
|
||||
|
||||
func BenchmarkNewCaptchaGenerator(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var generator = waf.NewCaptchaGenerator()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
generator.NewCaptcha(6)
|
||||
}
|
||||
})
|
||||
}
|
||||
70
EdgeNode/internal/waf/captcha_test.go
Normal file
70
EdgeNode/internal/waf/captcha_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package waf_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/dchest/captcha"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCaptchaMemory(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var stat1 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat1)
|
||||
|
||||
var count = 5_000
|
||||
var before = time.Now()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
var id = captcha.NewLen(6)
|
||||
var writer = &bytes.Buffer{}
|
||||
err := captcha.WriteImage(writer, id, 200, 100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
captcha.VerifyString(id, "abc")
|
||||
}
|
||||
|
||||
var stat2 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat2)
|
||||
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>20, "MB", fmt.Sprintf("%.0f QPS", float64(count)/time.Since(before).Seconds()))
|
||||
}
|
||||
|
||||
func BenchmarkCaptcha_VerifyCode_100_50(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var id = captcha.NewLen(6)
|
||||
var writer = &bytes.Buffer{}
|
||||
err := captcha.WriteImage(writer, id, 100, 50)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkCaptcha_VerifyCode_200_100(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var id = captcha.NewLen(6)
|
||||
var writer = &bytes.Buffer{}
|
||||
err := captcha.WriteImage(writer, id, 200, 100)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = id
|
||||
}
|
||||
})
|
||||
}
|
||||
988
EdgeNode/internal/waf/captcha_validator.go
Normal file
988
EdgeNode/internal/waf/captcha_validator.go
Normal file
File diff suppressed because one or more lines are too long
194
EdgeNode/internal/waf/checkpoints/cc.go
Normal file
194
EdgeNode/internal/waf/checkpoints/cc.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// CCCheckpoint ${cc.arg}
|
||||
// TODO implement more traffic rules
|
||||
type CCCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) Init() {
|
||||
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) Start() {
|
||||
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = 0
|
||||
|
||||
periodString, ok := options["period"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var period = types.Int(periodString)
|
||||
if period < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
if period > 7*86400 {
|
||||
period = 7 * 86400
|
||||
}
|
||||
|
||||
var userType = types.String(options["userType"])
|
||||
var userField = types.String(options["userField"])
|
||||
var userIndex = types.Int(options["userIndex"])
|
||||
|
||||
if param == "requests" { // requests
|
||||
var key = ""
|
||||
switch userType {
|
||||
case "ip":
|
||||
key = req.WAFRemoteIP()
|
||||
case "cookie":
|
||||
if len(userField) == 0 {
|
||||
key = req.WAFRemoteIP()
|
||||
} else {
|
||||
cookie, _ := req.WAFRaw().Cookie(userField)
|
||||
if cookie != nil {
|
||||
v := cookie.Value
|
||||
if userIndex > 0 && len(v) > userIndex {
|
||||
v = v[userIndex:]
|
||||
}
|
||||
key = "USER@" + userType + "@" + userField + "@" + v
|
||||
}
|
||||
}
|
||||
case "get":
|
||||
if len(userField) == 0 {
|
||||
key = req.WAFRemoteIP()
|
||||
} else {
|
||||
v := req.WAFRaw().URL.Query().Get(userField)
|
||||
if userIndex > 0 && len(v) > userIndex {
|
||||
v = v[userIndex:]
|
||||
}
|
||||
key = "USER@" + userType + "@" + userField + "@" + v
|
||||
}
|
||||
case "post":
|
||||
if len(userField) == 0 {
|
||||
key = req.WAFRemoteIP()
|
||||
} else {
|
||||
v := req.WAFRaw().PostFormValue(userField)
|
||||
if userIndex > 0 && len(v) > userIndex {
|
||||
v = v[userIndex:]
|
||||
}
|
||||
key = "USER@" + userType + "@" + userField + "@" + v
|
||||
}
|
||||
case "header":
|
||||
if len(userField) == 0 {
|
||||
key = req.WAFRemoteIP()
|
||||
} else {
|
||||
v := req.WAFRaw().Header.Get(userField)
|
||||
if userIndex > 0 && len(v) > userIndex {
|
||||
v = v[userIndex:]
|
||||
}
|
||||
key = "USER@" + userType + "@" + userField + "@" + v
|
||||
}
|
||||
default:
|
||||
key = req.WAFRemoteIP()
|
||||
}
|
||||
if len(key) == 0 {
|
||||
key = req.WAFRemoteIP()
|
||||
}
|
||||
value = counters.SharedCounter.IncreaseKey(types.String(ruleId)+"@WAF_CC@"+key, types.Int(period))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) ParamOptions() *ParamOptions {
|
||||
option := NewParamOptions()
|
||||
option.AddParam("请求数", "requests")
|
||||
return option
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) Options() []OptionInterface {
|
||||
options := []OptionInterface{}
|
||||
|
||||
// period
|
||||
{
|
||||
option := NewFieldOption("统计周期", "period")
|
||||
option.Value = "60"
|
||||
option.RightLabel = "秒"
|
||||
option.Size = 8
|
||||
option.MaxLength = 8
|
||||
option.Validate = func(value string) (ok bool, message string) {
|
||||
if regexp.MustCompile(`^\d+$`).MatchString(value) {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
message = "周期需要是一个整数数字"
|
||||
return
|
||||
}
|
||||
options = append(options, option)
|
||||
}
|
||||
|
||||
// type
|
||||
{
|
||||
option := NewOptionsOption("用户识别读取来源", "userType")
|
||||
option.Size = 10
|
||||
option.SetOptions([]maps.Map{
|
||||
{
|
||||
"name": "IP",
|
||||
"value": "ip",
|
||||
},
|
||||
{
|
||||
"name": "Cookie",
|
||||
"value": "cookie",
|
||||
},
|
||||
{
|
||||
"name": "URL参数",
|
||||
"value": "get",
|
||||
},
|
||||
{
|
||||
"name": "POST参数",
|
||||
"value": "post",
|
||||
},
|
||||
{
|
||||
"name": "HTTP Header",
|
||||
"value": "header",
|
||||
},
|
||||
})
|
||||
options = append(options, option)
|
||||
}
|
||||
|
||||
// user field
|
||||
{
|
||||
option := NewFieldOption("用户识别字段", "userField")
|
||||
option.Comment = "识别用户的唯一性字段,在用户读取来源不是IP时使用"
|
||||
options = append(options, option)
|
||||
}
|
||||
|
||||
// user value index
|
||||
{
|
||||
option := NewFieldOption("字段读取位置", "userIndex")
|
||||
option.Size = 5
|
||||
option.MaxLength = 5
|
||||
option.Comment = "读取用户识别字段的位置,从0开始,比如user12345的数字ID 12345的位置就是5,在用户读取来源不是IP时使用"
|
||||
options = append(options, option)
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) Stop() {
|
||||
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheDisabled
|
||||
}
|
||||
99
EdgeNode/internal/waf/checkpoints/cc2.go
Normal file
99
EdgeNode/internal/waf/checkpoints/cc2.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/counters"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CC2Checkpoint 新的CC
|
||||
type CC2Checkpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *CC2Checkpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var keys = options.GetSlice("keys")
|
||||
var keyValues = []string{}
|
||||
var hasRemoteAddr = false
|
||||
for _, key := range keys {
|
||||
if key == "${remoteAddr}" || key == "${rawRemoteAddr}" {
|
||||
hasRemoteAddr = true
|
||||
}
|
||||
keyValues = append(keyValues, req.Format(types.String(key)))
|
||||
}
|
||||
if len(keyValues) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var period = options.GetInt("period")
|
||||
if period <= 0 {
|
||||
period = 60
|
||||
} else if period > 7*86400 {
|
||||
period = 7 * 86400
|
||||
}
|
||||
|
||||
/**var threshold = options.GetInt64("threshold")
|
||||
if threshold <= 0 {
|
||||
threshold = 1000
|
||||
}**/
|
||||
|
||||
if options.GetBool("ignoreCommonFiles") {
|
||||
var rawReq = req.WAFRaw()
|
||||
if len(rawReq.Referer()) > 0 {
|
||||
var ext = filepath.Ext(rawReq.URL.Path)
|
||||
if len(ext) > 0 && utils.IsCommonFileExtension(ext) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ccKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(keyValues, "@")
|
||||
var ccValue = counters.SharedCounter.IncreaseKey(ccKey, period)
|
||||
value = ccValue
|
||||
|
||||
// 基于指纹统计
|
||||
var enableFingerprint = true
|
||||
if options.Has("enableFingerprint") && !options.GetBool("enableFingerprint") {
|
||||
enableFingerprint = false
|
||||
}
|
||||
if hasRemoteAddr && enableFingerprint {
|
||||
var fingerprint = req.WAFFingerprint()
|
||||
if len(fingerprint) > 0 {
|
||||
var fpKeyValues = []string{}
|
||||
for _, key := range keys {
|
||||
if key == "${remoteAddr}" || key == "${rawRemoteAddr}" {
|
||||
fpKeyValues = append(fpKeyValues, fmt.Sprintf("%x", fingerprint))
|
||||
continue
|
||||
}
|
||||
fpKeyValues = append(fpKeyValues, req.Format(types.String(key)))
|
||||
}
|
||||
var fpCCKey = "WAF-CC-" + types.String(ruleId) + "-" + strings.Join(fpKeyValues, "@")
|
||||
var fpValue = counters.SharedCounter.IncreaseKey(fpCCKey, period)
|
||||
if fpValue > ccValue {
|
||||
value = fpValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *CC2Checkpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *CC2Checkpoint) CacheLife() wafutils.CacheLife {
|
||||
return wafutils.CacheDisabled
|
||||
}
|
||||
43
EdgeNode/internal/waf/checkpoints/cc_test.go
Normal file
43
EdgeNode/internal/waf/checkpoints/cc_test.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCCCheckpoint_RequestValue(t *testing.T) {
|
||||
raw, err := http.NewRequest(http.MethodGet, "http://teaos.cn/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(raw)
|
||||
req.WAFRaw().RemoteAddr = "127.0.0.1"
|
||||
|
||||
checkpoint := new(CCCheckpoint)
|
||||
checkpoint.Init()
|
||||
checkpoint.Start()
|
||||
|
||||
options := maps.Map{
|
||||
"period": "5",
|
||||
}
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
|
||||
req.WAFRaw().RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
|
||||
req.WAFRaw().RemoteAddr = "127.0.0.1"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
|
||||
req.WAFRaw().RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
|
||||
req.WAFRaw().RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
|
||||
req.WAFRaw().RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options, 1))
|
||||
}
|
||||
59
EdgeNode/internal/waf/checkpoints/checkpoint.go
Normal file
59
EdgeNode/internal/waf/checkpoints/checkpoint.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Checkpoint struct {
|
||||
priority int
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Init() {
|
||||
|
||||
}
|
||||
|
||||
func (this *Checkpoint) IsRequest() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *Checkpoint) IsComposed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *Checkpoint) ParamOptions() *ParamOptions {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Options() []OptionInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Start() {
|
||||
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Stop() {
|
||||
|
||||
}
|
||||
|
||||
func (this *Checkpoint) SetPriority(priority int) {
|
||||
this.priority = priority
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Priority() int {
|
||||
return this.priority
|
||||
}
|
||||
|
||||
func (this *Checkpoint) RequestBodyIsEmpty(req requests.Request) bool {
|
||||
if req.WAFRaw().ContentLength == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
var method = req.WAFRaw().Method
|
||||
if method == http.MethodHead || method == http.MethodGet {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
11
EdgeNode/internal/waf/checkpoints/checkpoint_definition.go
Normal file
11
EdgeNode/internal/waf/checkpoints/checkpoint_definition.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package checkpoints
|
||||
|
||||
// CheckpointDefinition check point definition
|
||||
type CheckpointDefinition struct {
|
||||
Name string
|
||||
Description string
|
||||
Prefix string
|
||||
HasParams bool // has sub params
|
||||
Instance CheckpointInterface
|
||||
Priority int
|
||||
}
|
||||
46
EdgeNode/internal/waf/checkpoints/checkpoint_interface.go
Normal file
46
EdgeNode/internal/waf/checkpoints/checkpoint_interface.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// CheckpointInterface Check Point
|
||||
type CheckpointInterface interface {
|
||||
// Init initialize
|
||||
Init()
|
||||
|
||||
// IsRequest is request?
|
||||
IsRequest() bool
|
||||
|
||||
// IsComposed is composed?
|
||||
IsComposed() bool
|
||||
|
||||
// RequestValue get request value
|
||||
RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error)
|
||||
|
||||
// ResponseValue get response value
|
||||
ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error)
|
||||
|
||||
// ParamOptions param option list
|
||||
ParamOptions() *ParamOptions
|
||||
|
||||
// Options options
|
||||
Options() []OptionInterface
|
||||
|
||||
// Start start
|
||||
Start()
|
||||
|
||||
// Stop stop
|
||||
Stop()
|
||||
|
||||
// SetPriority set priority
|
||||
SetPriority(priority int)
|
||||
|
||||
// Priority get priority
|
||||
Priority() int
|
||||
|
||||
// CacheLife regexp cache life
|
||||
CacheLife() utils.CacheLife
|
||||
}
|
||||
5
EdgeNode/internal/waf/checkpoints/option.go
Normal file
5
EdgeNode/internal/waf/checkpoints/option.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package checkpoints
|
||||
|
||||
type OptionInterface interface {
|
||||
Type() string
|
||||
}
|
||||
26
EdgeNode/internal/waf/checkpoints/option_field.go
Normal file
26
EdgeNode/internal/waf/checkpoints/option_field.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package checkpoints
|
||||
|
||||
// attach option
|
||||
type FieldOption struct {
|
||||
Name string
|
||||
Code string
|
||||
Value string // default value
|
||||
IsRequired bool
|
||||
Size int
|
||||
Comment string
|
||||
Placeholder string
|
||||
RightLabel string
|
||||
MaxLength int
|
||||
Validate func(value string) (ok bool, message string)
|
||||
}
|
||||
|
||||
func NewFieldOption(name string, code string) *FieldOption {
|
||||
return &FieldOption{
|
||||
Name: name,
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FieldOption) Type() string {
|
||||
return "field"
|
||||
}
|
||||
30
EdgeNode/internal/waf/checkpoints/option_options.go
Normal file
30
EdgeNode/internal/waf/checkpoints/option_options.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package checkpoints
|
||||
|
||||
import "github.com/iwind/TeaGo/maps"
|
||||
|
||||
type OptionsOption struct {
|
||||
Name string
|
||||
Code string
|
||||
Value string // default value
|
||||
IsRequired bool
|
||||
Size int
|
||||
Comment string
|
||||
RightLabel string
|
||||
Validate func(value string) (ok bool, message string)
|
||||
Options []maps.Map
|
||||
}
|
||||
|
||||
func NewOptionsOption(name string, code string) *OptionsOption {
|
||||
return &OptionsOption{
|
||||
Name: name,
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *OptionsOption) Type() string {
|
||||
return "options"
|
||||
}
|
||||
|
||||
func (this *OptionsOption) SetOptions(options []maps.Map) {
|
||||
this.Options = options
|
||||
}
|
||||
21
EdgeNode/internal/waf/checkpoints/param_option.go
Normal file
21
EdgeNode/internal/waf/checkpoints/param_option.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
type KeyValue struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ParamOptions struct {
|
||||
Options []*KeyValue `json:"options"`
|
||||
}
|
||||
|
||||
func NewParamOptions() *ParamOptions {
|
||||
return &ParamOptions{}
|
||||
}
|
||||
|
||||
func (this *ParamOptions) AddParam(name string, value string) {
|
||||
this.Options = append(this.Options, &KeyValue{
|
||||
Name: name,
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
60
EdgeNode/internal/waf/checkpoints/request_all.go
Normal file
60
EdgeNode/internal/waf/checkpoints/request_all.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// RequestAllCheckpoint ${requestAll}
|
||||
type RequestAllCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestAllCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var valueBytes = [][]byte{}
|
||||
if len(req.WAFRaw().RequestURI) > 0 {
|
||||
valueBytes = append(valueBytes, []byte(req.WAFRaw().RequestURI))
|
||||
} else if req.WAFRaw().URL != nil {
|
||||
valueBytes = append(valueBytes, []byte(req.WAFRaw().URL.RequestURI()))
|
||||
}
|
||||
|
||||
if this.RequestBodyIsEmpty(req) {
|
||||
value = valueBytes
|
||||
return
|
||||
}
|
||||
|
||||
if req.WAFRaw().Body != nil {
|
||||
var bodyData = req.WAFGetCacheBody()
|
||||
hasRequestBody = true
|
||||
if len(bodyData) == 0 {
|
||||
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
|
||||
if err != nil {
|
||||
return "", hasRequestBody, err, nil
|
||||
}
|
||||
|
||||
bodyData = data
|
||||
req.WAFSetCacheBody(data)
|
||||
req.WAFRestoreBody(data)
|
||||
}
|
||||
if len(bodyData) > 0 {
|
||||
valueBytes = append(valueBytes, bodyData)
|
||||
}
|
||||
}
|
||||
|
||||
value = valueBytes
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestAllCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = ""
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestAllCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
76
EdgeNode/internal/waf/checkpoints/request_all_test.go
Normal file
76
EdgeNode/internal/waf/checkpoints/request_all_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestAllCheckpoint_RequestValue(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn/hello/world", bytes.NewBuffer([]byte("123456")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checkpoint := new(RequestAllCheckpoint)
|
||||
v, _, sysErr, userErr := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
|
||||
if sysErr != nil {
|
||||
t.Fatal(sysErr)
|
||||
}
|
||||
if userErr != nil {
|
||||
t.Fatal(userErr)
|
||||
}
|
||||
if v != nil {
|
||||
vv, ok := v.([][]byte)
|
||||
if ok {
|
||||
for _, v2 := range vv {
|
||||
t.Log(string(v2), ":", v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
|
||||
func TestRequestAllCheckpoint_RequestValue_Max(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(strings.Repeat("123456", 10240000))))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checkpoint := new(RequestBodyCheckpoint)
|
||||
value, _, err, _ := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("value bytes:", len(types.String(value)))
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("raw bytes:", len(body))
|
||||
}
|
||||
|
||||
func BenchmarkRequestAllCheckpoint_RequestValue(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn/hello/world", bytes.NewBuffer(bytes.Repeat([]byte("HELLO"), 1024)))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
checkpoint := new(RequestAllCheckpoint)
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, _, _ = checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
|
||||
}
|
||||
}
|
||||
26
EdgeNode/internal/waf/checkpoints/request_arg.go
Normal file
26
EdgeNode/internal/waf/checkpoints/request_arg.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestArgCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return req.WAFRaw().URL.Query().Get(param), hasRequestBody, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestArgCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
21
EdgeNode/internal/waf/checkpoints/request_arg_test.go
Normal file
21
EdgeNode/internal/waf/checkpoints/request_arg_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestArgParam_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/?name=lu", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
|
||||
checkpoint := new(RequestArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
|
||||
t.Log(checkpoint.ResponseValue(req, nil, "name", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "name2", nil, 1))
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_args.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_args.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestArgsCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestArgsCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().URL.RawQuery
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestArgsCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestArgsCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
50
EdgeNode/internal/waf/checkpoints/request_body.go
Normal file
50
EdgeNode/internal/waf/checkpoints/request_body.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// RequestBodyCheckpoint ${requestBody}
|
||||
type RequestBodyCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestBodyCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.RequestBodyIsEmpty(req) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
if req.WAFRaw().Body == nil {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
var bodyData = req.WAFGetCacheBody()
|
||||
hasRequestBody = true
|
||||
if len(bodyData) == 0 {
|
||||
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
|
||||
if err != nil {
|
||||
return "", hasRequestBody, err, nil
|
||||
}
|
||||
|
||||
bodyData = data
|
||||
req.WAFSetCacheBody(data)
|
||||
req.WAFRestoreBody(data)
|
||||
}
|
||||
|
||||
return bodyData, hasRequestBody, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestBodyCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestBodyCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheDisabled
|
||||
}
|
||||
48
EdgeNode/internal/waf/checkpoints/request_body_test.go
Normal file
48
EdgeNode/internal/waf/checkpoints/request_body_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestBodyCheckpoint_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("123456")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var req = requests.NewTestRequest(rawReq)
|
||||
checkpoint := new(RequestBodyCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
|
||||
body, err := io.ReadAll(rawReq.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
t.Log(string(req.WAFGetCacheBody()))
|
||||
}
|
||||
|
||||
func TestRequestBodyCheckpoint_RequestValue_Max(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(strings.Repeat("123456", 10240000))))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checkpoint := new(RequestBodyCheckpoint)
|
||||
value, _, err, _ := checkpoint.RequestValue(requests.NewTestRequest(req), "", nil, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("value bytes:", len(types.String(value)))
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("raw bytes:", len(body))
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_cname.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_cname.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestCNAMECheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.Format("${cname}")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCNAMECheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_content_type.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_content_type.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestContentTypeCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestContentTypeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().Header.Get("Content-Type")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestContentTypeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestContentTypeCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
33
EdgeNode/internal/waf/checkpoints/request_cookie.go
Normal file
33
EdgeNode/internal/waf/checkpoints/request_cookie.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestCookieCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestCookieCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
cookie, err := req.WAFRaw().Cookie(param)
|
||||
if err != nil {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
value = cookie.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCookieCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCookieCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
33
EdgeNode/internal/waf/checkpoints/request_cookies.go
Normal file
33
EdgeNode/internal/waf/checkpoints/request_cookies.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestCookiesCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestCookiesCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var cookies = []string{}
|
||||
for _, cookie := range req.WAFRaw().Cookies() {
|
||||
cookies = append(cookies, url.QueryEscape(cookie.Name)+"="+url.QueryEscape(cookie.Value))
|
||||
}
|
||||
value = strings.Join(cookies, "&")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCookiesCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCookiesCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
54
EdgeNode/internal/waf/checkpoints/request_form_arg.go
Normal file
54
EdgeNode/internal/waf/checkpoints/request_form_arg.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// RequestFormArgCheckpoint ${requestForm.arg}
|
||||
type RequestFormArgCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestFormArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
hasRequestBody = true
|
||||
|
||||
if this.RequestBodyIsEmpty(req) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
if req.WAFRaw().Body == nil {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
var bodyData = req.WAFGetCacheBody()
|
||||
if len(bodyData) == 0 {
|
||||
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
|
||||
if err != nil {
|
||||
return "", hasRequestBody, err, nil
|
||||
}
|
||||
|
||||
bodyData = data
|
||||
req.WAFSetCacheBody(data)
|
||||
req.WAFRestoreBody(data)
|
||||
}
|
||||
|
||||
// TODO improve performance
|
||||
values, _ := url.ParseQuery(string(bodyData))
|
||||
return values.Get(param), hasRequestBody, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestFormArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestFormArgCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
32
EdgeNode/internal/waf/checkpoints/request_form_arg_test.go
Normal file
32
EdgeNode/internal/waf/checkpoints/request_form_arg_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestFormArgCheckpoint_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("name=lu&age=20&encoded="+url.QueryEscape("<strong>ENCODED STRING</strong>"))))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
req.WAFRaw().Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestFormArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "age", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "Hello", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "encoded", nil, 1))
|
||||
|
||||
body, err := io.ReadAll(req.WAFRaw().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type RequestGeneralHeaderLengthCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestGeneralHeaderLengthCheckpoint) IsComposed() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *RequestGeneralHeaderLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = false
|
||||
|
||||
var headers = options.GetSlice("headers")
|
||||
if len(headers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var length = options.GetInt("length")
|
||||
|
||||
for _, header := range headers {
|
||||
v := req.WAFRaw().Header.Get(types.String(header))
|
||||
if len(v) > length {
|
||||
value = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestGeneralHeaderLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestGeneralHeaderLengthCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheDisabled
|
||||
}
|
||||
30
EdgeNode/internal/waf/checkpoints/request_geo_city_name.go
Normal file
30
EdgeNode/internal/waf/checkpoints/request_geo_city_name.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestGeoCityNameCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestGeoCityNameCheckpoint) IsComposed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *RequestGeoCityNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.Format("${geo.city.name}")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestGeoCityNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
|
||||
func (this *RequestGeoCityNameCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestGeoCountryNameCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestGeoCountryNameCheckpoint) IsComposed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *RequestGeoCountryNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.Format("${geo.country.name}")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestGeoCountryNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
|
||||
func (this *RequestGeoCountryNameCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestGeoProvinceNameCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestGeoProvinceNameCheckpoint) IsComposed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *RequestGeoProvinceNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.Format("${geo.province.name}")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestGeoProvinceNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
|
||||
func (this *RequestGeoProvinceNameCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
33
EdgeNode/internal/waf/checkpoints/request_header.go
Normal file
33
EdgeNode/internal/waf/checkpoints/request_header.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestHeaderCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
v, found := req.WAFRaw().Header[param]
|
||||
if !found {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
value = strings.Join(v, ";")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestHeaderMaxLengthCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHeaderMaxLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var maxLen int
|
||||
for _, v := range req.WAFRaw().Header {
|
||||
for _, subV := range v {
|
||||
var l = len(subV)
|
||||
if l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
}
|
||||
}
|
||||
value = maxLen
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderMaxLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderMaxLengthCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
32
EdgeNode/internal/waf/checkpoints/request_header_names.go
Normal file
32
EdgeNode/internal/waf/checkpoints/request_header_names.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestHeaderNamesCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHeaderNamesCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var headerNames = []string{}
|
||||
for k := range req.WAFRaw().Header {
|
||||
headerNames = append(headerNames, k)
|
||||
}
|
||||
value = strings.Join(headerNames, "\n")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderNamesCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderNamesCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestHeaderNamesCheckpoint_RequestValue(t *testing.T) {
|
||||
var checkpoint = &checkpoints.RequestHeaderNamesCheckpoint{}
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rawReq.Header.Set("Accept", "text/html")
|
||||
rawReq.Header.Set("User-Agent", "Chrome")
|
||||
rawReq.Header.Set("Accept-Encoding", "br, gzip")
|
||||
var req = requests.NewTestRequest(rawReq)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 0))
|
||||
}
|
||||
36
EdgeNode/internal/waf/checkpoints/request_headers.go
Normal file
36
EdgeNode/internal/waf/checkpoints/request_headers.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestHeadersCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHeadersCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var headers = []string{}
|
||||
for k, v := range req.WAFRaw().Header {
|
||||
for _, subV := range v {
|
||||
headers = append(headers, k+": "+subV)
|
||||
}
|
||||
}
|
||||
sort.Strings(headers)
|
||||
value = strings.Join(headers, "\n")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeadersCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeadersCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
33
EdgeNode/internal/waf/checkpoints/request_headers_test.go
Normal file
33
EdgeNode/internal/waf/checkpoints/request_headers_test.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkRequestHeadersCheckpoint_RequestValue(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var header = http.Header{
|
||||
"Content-Type": []string{"keep-alive"},
|
||||
"User-Agent": []string{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"},
|
||||
"Accept-Encoding": []string{"gzip, deflate, br"},
|
||||
"Referer": []string{"https://goedge.cn/"},
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var headers = []string{}
|
||||
for k, v := range header {
|
||||
for _, subV := range v {
|
||||
headers = append(headers, k+": "+subV)
|
||||
}
|
||||
}
|
||||
sort.Strings(headers)
|
||||
_ = strings.Join(headers, "\n")
|
||||
}
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_host.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_host.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestHostCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHostCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().Host
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHostCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHostCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
20
EdgeNode/internal/waf/checkpoints/request_host_test.go
Normal file
20
EdgeNode/internal/waf/checkpoints/request_host_test.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestHostCheckpoint_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
req.WAFRaw().Header.Set("Host", "cloud.teaos.cn")
|
||||
|
||||
checkpoint := new(RequestHostCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
}
|
||||
31
EdgeNode/internal/waf/checkpoints/request_is_cname.go
Normal file
31
EdgeNode/internal/waf/checkpoints/request_is_cname.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestIsCNAMECheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestIsCNAMECheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if req.Format("${cname}") == req.Format("${host}") {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestIsCNAMECheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestIsCNAMECheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
30
EdgeNode/internal/waf/checkpoints/request_isp_name.go
Normal file
30
EdgeNode/internal/waf/checkpoints/request_isp_name.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestISPNameCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestISPNameCheckpoint) IsComposed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *RequestISPNameCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.Format("${isp.name}")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestISPNameCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
|
||||
func (this *RequestISPNameCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
54
EdgeNode/internal/waf/checkpoints/request_json_arg.go
Normal file
54
EdgeNode/internal/waf/checkpoints/request_json_arg.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
wafutils "github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RequestJSONArgCheckpoint ${requestJSON.arg}
|
||||
type RequestJSONArgCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestJSONArgCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var bodyData = req.WAFGetCacheBody()
|
||||
hasRequestBody = true
|
||||
if len(bodyData) == 0 {
|
||||
data, err := req.WAFReadBody(req.WAFMaxRequestSize()) // read body
|
||||
if err != nil {
|
||||
return "", hasRequestBody, err, nil
|
||||
}
|
||||
|
||||
bodyData = data
|
||||
req.WAFSetCacheBody(data)
|
||||
defer req.WAFRestoreBody(data)
|
||||
}
|
||||
|
||||
// TODO improve performance
|
||||
var m any = nil
|
||||
err := json.Unmarshal(bodyData, &m)
|
||||
if err != nil || m == nil {
|
||||
return "", hasRequestBody, nil, err
|
||||
}
|
||||
|
||||
value = utils.Get(m, strings.Split(param, "."))
|
||||
if value != nil {
|
||||
return value, hasRequestBody, nil, err
|
||||
}
|
||||
return "", hasRequestBody, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestJSONArgCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestJSONArgCheckpoint) CacheLife() wafutils.CacheLife {
|
||||
return wafutils.CacheMiddleLife
|
||||
}
|
||||
99
EdgeNode/internal/waf/checkpoints/request_json_arg_test.go
Normal file
99
EdgeNode/internal/waf/checkpoints/request_json_arg_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestJSONArgCheckpoint_RequestValue_Map(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
|
||||
{
|
||||
"name": "lu",
|
||||
"age": 20,
|
||||
"books": [ "PHP", "Golang", "Python" ]
|
||||
}
|
||||
`)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestJSONArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "age", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "Hello", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "books", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "books.1", nil, 1))
|
||||
|
||||
body, err := io.ReadAll(req.WAFRaw().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
|
||||
func TestRequestJSONArgCheckpoint_RequestValue_Array(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
|
||||
[{
|
||||
"name": "lu",
|
||||
"age": 20,
|
||||
"books": [ "PHP", "Golang", "Python" ]
|
||||
}]
|
||||
`)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestJSONArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "0.name", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.age", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.Hello", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books.1", nil, 1))
|
||||
|
||||
body, err := io.ReadAll(req.WAFRaw().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
|
||||
func TestRequestJSONArgCheckpoint_RequestValue_Error(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte(`
|
||||
[{
|
||||
"name": "lu",
|
||||
"age": 20,
|
||||
"books": [ "PHP", "Golang", "Python" ]
|
||||
}]
|
||||
`)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestJSONArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "0.name", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.age", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.Hello", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books.1", nil, 1))
|
||||
|
||||
body, err := io.ReadAll(req.WAFRaw().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_length.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_length.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestLengthCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().ContentLength
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestLengthCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_method.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_method.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestMethodCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestMethodCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().Method
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestMethodCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestMethodCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
26
EdgeNode/internal/waf/checkpoints/request_path.go
Normal file
26
EdgeNode/internal/waf/checkpoints/request_path.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestPathCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestPathCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return req.WAFRaw().URL.Path, false, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestPathCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestPathCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
18
EdgeNode/internal/waf/checkpoints/request_path_test.go
Normal file
18
EdgeNode/internal/waf/checkpoints/request_path_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestPathCheckpoint_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "http://teaos.cn/index?name=lu", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
checkpoint := new(RequestPathCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_proto.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_proto.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestProtoCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestProtoCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().Proto
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestProtoCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestProtoCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
33
EdgeNode/internal/waf/checkpoints/request_raw_remote_addr.go
Normal file
33
EdgeNode/internal/waf/checkpoints/request_raw_remote_addr.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"net"
|
||||
)
|
||||
|
||||
type RequestRawRemoteAddrCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRawRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
host, _, err := net.SplitHostPort(req.WAFRaw().RemoteAddr)
|
||||
if err == nil {
|
||||
value = host
|
||||
} else {
|
||||
value = req.WAFRaw().RemoteAddr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRawRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRawRemoteAddrCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_referer.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_referer.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestRefererCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRefererCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().Referer()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
104
EdgeNode/internal/waf/checkpoints/request_referer_block.go
Normal file
104
EdgeNode/internal/waf/checkpoints/request_referer_block.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// RequestRefererBlockCheckpoint 防盗链
|
||||
type RequestRefererBlockCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
// RequestValue 计算checkpoint值
|
||||
// 选项:allowEmpty, allowSameDomain, allowDomains
|
||||
func (this *RequestRefererBlockCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var checkOrigin = options.GetBool("checkOrigin")
|
||||
var referer = req.WAFRaw().Referer()
|
||||
if len(referer) == 0 && checkOrigin {
|
||||
var origin = req.WAFRaw().Header.Get("Origin")
|
||||
if len(origin) > 0 && origin != "null" {
|
||||
referer = "https://" + origin // 因为Origin都只有域名部分,所以为了下面的URL 分析需要加上https://
|
||||
}
|
||||
}
|
||||
|
||||
if len(referer) == 0 {
|
||||
if options.GetBool("allowEmpty") {
|
||||
value = 1
|
||||
return
|
||||
}
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
|
||||
u, err := url.Parse(referer)
|
||||
if err != nil {
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
var host = u.Host
|
||||
|
||||
if options.GetBool("allowSameDomain") && host == req.WAFRaw().Host {
|
||||
value = 1
|
||||
return
|
||||
}
|
||||
|
||||
// allow domains
|
||||
var allowDomains = options.GetSlice("allowDomains")
|
||||
var allowDomainStrings = []string{}
|
||||
for _, domain := range allowDomains {
|
||||
allowDomainStrings = append(allowDomainStrings, types.String(domain))
|
||||
}
|
||||
|
||||
// deny domains
|
||||
var denyDomains = options.GetSlice("denyDomains")
|
||||
var denyDomainStrings = []string{}
|
||||
for _, domain := range denyDomains {
|
||||
denyDomainStrings = append(denyDomainStrings, types.String(domain))
|
||||
}
|
||||
|
||||
if len(allowDomainStrings) == 0 {
|
||||
if len(denyDomainStrings) > 0 {
|
||||
if configutils.MatchDomains(denyDomainStrings, host) {
|
||||
value = 0
|
||||
} else {
|
||||
value = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
|
||||
if configutils.MatchDomains(allowDomainStrings, host) {
|
||||
if len(denyDomainStrings) > 0 {
|
||||
if configutils.MatchDomains(denyDomainStrings, host) {
|
||||
value = 0
|
||||
} else {
|
||||
value = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
value = 1
|
||||
return
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererBlockCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererBlockCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
44
EdgeNode/internal/waf/checkpoints/request_referer_origin.go
Normal file
44
EdgeNode/internal/waf/checkpoints/request_referer_origin.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestRefererOriginCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRefererOriginCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
var s []string
|
||||
|
||||
var referer = req.WAFRaw().Referer()
|
||||
if len(referer) > 0 {
|
||||
s = append(s, referer)
|
||||
}
|
||||
|
||||
var origin = req.WAFRaw().Header.Get("Origin")
|
||||
if len(origin) > 0 {
|
||||
s = append(s, origin)
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
value = s
|
||||
} else {
|
||||
value = ""
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererOriginCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererOriginCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package checkpoints_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/checkpoints"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestRefererOriginCheckpoint_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var req = requests.NewTestRequest(rawReq)
|
||||
|
||||
var checkpoint = &checkpoints.RequestRefererOriginCheckpoint{}
|
||||
|
||||
{
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 0))
|
||||
}
|
||||
|
||||
{
|
||||
rawReq.Header.Set("Referer", "https://example.com/hello.yaml")
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 0))
|
||||
}
|
||||
|
||||
{
|
||||
rawReq.Header.Set("Origin", "https://example.com/world.yaml")
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 0))
|
||||
}
|
||||
|
||||
{
|
||||
rawReq.Header.Del("Referer")
|
||||
rawReq.Header.Set("Origin", "https://example.com/world.yaml")
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 0))
|
||||
}
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_remote_addr.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_remote_addr.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestRemoteAddrCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRemoteAddrCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRemoteIP()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemoteAddrCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemoteAddrCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
34
EdgeNode/internal/waf/checkpoints/request_remote_port.go
Normal file
34
EdgeNode/internal/waf/checkpoints/request_remote_port.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
)
|
||||
|
||||
type RequestRemotePortCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRemotePortCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
_, port, err := net.SplitHostPort(req.WAFRaw().RemoteAddr)
|
||||
if err == nil {
|
||||
value = types.Int(port)
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemotePortCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemotePortCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
32
EdgeNode/internal/waf/checkpoints/request_remote_user.go
Normal file
32
EdgeNode/internal/waf/checkpoints/request_remote_user.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestRemoteUserCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRemoteUserCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
username, _, ok := req.WAFRaw().BasicAuth()
|
||||
if !ok {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
value = username
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemoteUserCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemoteUserCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_scheme.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_scheme.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestSchemeCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestSchemeCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.Format("${scheme}")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestSchemeCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestSchemeCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
18
EdgeNode/internal/waf/checkpoints/request_scheme_test.go
Normal file
18
EdgeNode/internal/waf/checkpoints/request_scheme_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestSchemeCheckpoint_RequestValue(t *testing.T) {
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "https://teaos.cn/?name=lu", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
checkpoint := new(RequestSchemeCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil, 1))
|
||||
}
|
||||
234
EdgeNode/internal/waf/checkpoints/request_upload.go
Normal file
234
EdgeNode/internal/waf/checkpoints/request_upload.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var multipartHeaderRegexp = regexp.MustCompile(`(?i)(?:^|\r\n)--+\w+\r\n((([\w-]+: .+)\r\n)+)`)
|
||||
var multipartHeaderContentRangeRegexp = regexp.MustCompile(`/(\d+)`)
|
||||
|
||||
// RequestUploadCheckpoint ${requestUpload.arg}
|
||||
type RequestUploadCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestUploadCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.RequestBodyIsEmpty(req) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
value = ""
|
||||
if param == "minSize" || param == "maxSize" {
|
||||
value = 0
|
||||
}
|
||||
|
||||
if req.WAFRaw().Method != http.MethodPost {
|
||||
return
|
||||
}
|
||||
|
||||
if req.WAFRaw().Body == nil {
|
||||
return
|
||||
}
|
||||
|
||||
hasRequestBody = true
|
||||
|
||||
var requestContentLength = req.WAFRaw().ContentLength
|
||||
|
||||
var fields []string
|
||||
var minSize int64
|
||||
var maxSize int64
|
||||
var names []string
|
||||
var extensions []string
|
||||
|
||||
if requestContentLength <= req.WAFMaxRequestSize() { // full read
|
||||
if req.WAFRaw().MultipartForm == nil {
|
||||
var bodyData = req.WAFGetCacheBody()
|
||||
if len(bodyData) == 0 {
|
||||
data, err := req.WAFReadBody(req.WAFMaxRequestSize())
|
||||
if err != nil {
|
||||
sysErr = err
|
||||
return
|
||||
}
|
||||
|
||||
bodyData = data
|
||||
req.WAFSetCacheBody(data)
|
||||
defer req.WAFRestoreBody(data)
|
||||
}
|
||||
var oldBody = req.WAFRaw().Body
|
||||
req.WAFRaw().Body = io.NopCloser(bytes.NewBuffer(bodyData))
|
||||
err := req.WAFRaw().ParseMultipartForm(req.WAFMaxRequestSize())
|
||||
if err == nil {
|
||||
for field, files := range req.WAFRaw().MultipartForm.File {
|
||||
if param == "field" {
|
||||
fields = append(fields, field)
|
||||
} else if param == "minSize" {
|
||||
for _, file := range files {
|
||||
if minSize == 0 || minSize > file.Size {
|
||||
minSize = file.Size
|
||||
}
|
||||
}
|
||||
} else if param == "maxSize" {
|
||||
for _, file := range files {
|
||||
if maxSize < file.Size {
|
||||
maxSize = file.Size
|
||||
}
|
||||
}
|
||||
} else if param == "name" {
|
||||
for _, file := range files {
|
||||
if !lists.ContainsString(names, file.Filename) {
|
||||
names = append(names, file.Filename)
|
||||
}
|
||||
}
|
||||
} else if param == "ext" {
|
||||
for _, file := range files {
|
||||
if len(file.Filename) > 0 {
|
||||
exit := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if !lists.ContainsString(extensions, exit) {
|
||||
extensions = append(extensions, exit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 还原
|
||||
req.WAFRaw().Body = oldBody
|
||||
|
||||
if err != nil {
|
||||
userErr = err
|
||||
return
|
||||
}
|
||||
|
||||
if req.WAFRaw().MultipartForm == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else { // read first part
|
||||
var bodyData = req.WAFGetCacheBody()
|
||||
if len(bodyData) == 0 {
|
||||
data, err := req.WAFReadBody(req.WAFMaxRequestSize())
|
||||
if err != nil {
|
||||
sysErr = err
|
||||
return
|
||||
}
|
||||
|
||||
bodyData = data
|
||||
req.WAFSetCacheBody(data)
|
||||
defer req.WAFRestoreBody(data)
|
||||
}
|
||||
|
||||
var subMatches = multipartHeaderRegexp.FindAllSubmatch(bodyData, -1)
|
||||
for _, subMatch := range subMatches {
|
||||
var headers = bytes.Split(subMatch[1], []byte{'\r', '\n'})
|
||||
var partContentLength int64 = -1
|
||||
for _, header := range headers {
|
||||
if len(header) > 2 {
|
||||
var kv = bytes.SplitN(header, []byte{':'}, 2)
|
||||
if len(kv) == 2 {
|
||||
var k = kv[0]
|
||||
var v = kv[1]
|
||||
switch string(bytes.ToLower(k)) {
|
||||
case "content-disposition":
|
||||
var props = bytes.Split(v, []byte{';', ' '})
|
||||
for _, prop := range props {
|
||||
var propKV = bytes.SplitN(prop, []byte{'='}, 2)
|
||||
if len(propKV) == 2 {
|
||||
var propValue = string(propKV[1])
|
||||
switch string(propKV[0]) {
|
||||
case "name":
|
||||
if param == "field" {
|
||||
propValue, _ = strconv.Unquote(propValue)
|
||||
fields = append(fields, propValue)
|
||||
}
|
||||
case "filename":
|
||||
if param == "name" {
|
||||
propValue, _ = strconv.Unquote(propValue)
|
||||
names = append(names, propValue)
|
||||
} else if param == "ext" {
|
||||
propValue, _ = strconv.Unquote(propValue)
|
||||
extensions = append(extensions, strings.ToLower(filepath.Ext(propValue)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "content-range":
|
||||
if partContentLength <= 0 {
|
||||
var contentRange = multipartHeaderContentRangeRegexp.FindSubmatch(v)
|
||||
if len(contentRange) >= 2 {
|
||||
partContentLength = types.Int64(string(contentRange[1]))
|
||||
}
|
||||
}
|
||||
case "content-length":
|
||||
if partContentLength <= 0 {
|
||||
partContentLength = types.Int64(string(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// minSize & maxSize
|
||||
if partContentLength > 0 {
|
||||
if param == "minSize" && (minSize == 0 /** not set yet **/ || partContentLength < minSize) {
|
||||
minSize = partContentLength
|
||||
} else if param == "maxSize" && partContentLength > maxSize {
|
||||
maxSize = partContentLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if param == "field" { // field
|
||||
value = strings.Join(fields, ",")
|
||||
} else if param == "minSize" { // minSize
|
||||
if minSize == 0 && requestContentLength > 0 {
|
||||
minSize = requestContentLength
|
||||
}
|
||||
value = minSize
|
||||
} else if param == "maxSize" { // maxSize
|
||||
if maxSize == 0 && requestContentLength > 0 {
|
||||
maxSize = requestContentLength
|
||||
}
|
||||
value = maxSize
|
||||
} else if param == "name" { // name
|
||||
value = strings.Join(names, ",")
|
||||
} else if param == "ext" { // ext
|
||||
value = strings.Join(extensions, ",")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestUploadCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestUploadCheckpoint) ParamOptions() *ParamOptions {
|
||||
option := NewParamOptions()
|
||||
option.AddParam("最小文件尺寸", "minSize")
|
||||
option.AddParam("最大文件尺寸", "maxSize")
|
||||
option.AddParam("扩展名(如.txt)", "ext")
|
||||
option.AddParam("原始文件名", "name")
|
||||
option.AddParam("表单字段名", "field")
|
||||
return option
|
||||
}
|
||||
|
||||
func (this *RequestUploadCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
102
EdgeNode/internal/waf/checkpoints/request_upload_test.go
Normal file
102
EdgeNode/internal/waf/checkpoints/request_upload_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestUploadCheckpoint_RequestValue(t *testing.T) {
|
||||
body := bytes.NewBuffer([]byte{})
|
||||
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormField("name")
|
||||
if err == nil {
|
||||
_, err := part.Write([]byte("lu"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormField("age")
|
||||
if err == nil {
|
||||
_, err = part.Write([]byte("20"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile", "hello.txt")
|
||||
if err == nil {
|
||||
_, err = part.Write([]byte("Hello, World!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile2", "hello.PHP")
|
||||
if err == nil {
|
||||
_, err = part.Write([]byte("Hello, World, PHP!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile3", "hello.asp")
|
||||
if err == nil {
|
||||
_, err = part.Write([]byte("Hello, World, ASP Pages!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile4", "hello.asp")
|
||||
if err == nil {
|
||||
_, err = part.Write([]byte("Hello, World, ASP Pages!"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn/", body)
|
||||
if err != nil {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
req := requests.NewTestRequest(rawReq)
|
||||
req.WAFRaw().Header.Add("Content-Type", writer.FormDataContentType())
|
||||
|
||||
checkpoint := new(RequestUploadCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "field", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "minSize", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "maxSize", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil, 1))
|
||||
t.Log(checkpoint.RequestValue(req, "ext", nil, 1))
|
||||
|
||||
data, err := io.ReadAll(req.WAFRaw().Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(data))
|
||||
}
|
||||
31
EdgeNode/internal/waf/checkpoints/request_uri.go
Normal file
31
EdgeNode/internal/waf/checkpoints/request_uri.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestURICheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestURICheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if len(req.WAFRaw().RequestURI) > 0 {
|
||||
value = req.WAFRaw().RequestURI
|
||||
} else if req.WAFRaw().URL != nil {
|
||||
value = req.WAFRaw().URL.RequestURI()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestURICheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestURICheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
26
EdgeNode/internal/waf/checkpoints/request_url.go
Normal file
26
EdgeNode/internal/waf/checkpoints/request_url.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestURLCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestURLCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return req.Format("${requestURL}"), hasRequestBody, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestURLCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestURLCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/request_user_agent.go
Normal file
27
EdgeNode/internal/waf/checkpoints/request_user_agent.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RequestUserAgentCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestUserAgentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = req.WAFRaw().UserAgent()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestUserAgentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestUserAgentCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
52
EdgeNode/internal/waf/checkpoints/response_body.go
Normal file
52
EdgeNode/internal/waf/checkpoints/response_body.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ResponseBodyCheckpoint ${responseBody}
|
||||
type ResponseBodyCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if resp.ContentLength == 0 {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
value = ""
|
||||
if resp != nil && resp.Body != nil {
|
||||
if len(resp.BodyData) > 0 {
|
||||
value = string(resp.BodyData)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
sysErr = err
|
||||
return
|
||||
}
|
||||
resp.BodyData = body
|
||||
_ = resp.Body.Close()
|
||||
value = body
|
||||
resp.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
29
EdgeNode/internal/waf/checkpoints/response_body_test.go
Normal file
29
EdgeNode/internal/waf/checkpoints/response_body_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResponseBodyCheckpoint_ResponseValue(t *testing.T) {
|
||||
resp := requests.NewResponse(new(http.Response))
|
||||
resp.StatusCode = 200
|
||||
resp.Header = http.Header{}
|
||||
resp.Header.Set("Hello", "World")
|
||||
resp.Body = io.NopCloser(bytes.NewBuffer([]byte("Hello, World")))
|
||||
|
||||
checkpoint := new(ResponseBodyCheckpoint)
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("after read:", string(data))
|
||||
}
|
||||
33
EdgeNode/internal/waf/checkpoints/response_bytes_sent.go
Normal file
33
EdgeNode/internal/waf/checkpoints/response_bytes_sent.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// ResponseBytesSentCheckpoint ${bytesSent}
|
||||
type ResponseBytesSentCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = 0
|
||||
if resp != nil {
|
||||
value = resp.ContentLength
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheShortLife
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
)
|
||||
|
||||
type ResponseGeneralHeaderLengthCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseGeneralHeaderLengthCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseGeneralHeaderLengthCheckpoint) IsComposed() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *ResponseGeneralHeaderLengthCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseGeneralHeaderLengthCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = false
|
||||
|
||||
headers := options.GetSlice("headers")
|
||||
if len(headers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
length := options.GetInt("length")
|
||||
|
||||
for _, header := range headers {
|
||||
v := req.WAFRaw().Header.Get(types.String(header))
|
||||
if len(v) > length {
|
||||
value = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseGeneralHeaderLengthCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
34
EdgeNode/internal/waf/checkpoints/response_header.go
Normal file
34
EdgeNode/internal/waf/checkpoints/response_header.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// ResponseHeaderCheckpoint ${responseHeader.arg}
|
||||
type ResponseHeaderCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if resp != nil && resp.Header != nil {
|
||||
value = resp.Header.Get(param)
|
||||
} else {
|
||||
value = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
17
EdgeNode/internal/waf/checkpoints/response_header_test.go
Normal file
17
EdgeNode/internal/waf/checkpoints/response_header_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResponseHeaderCheckpoint_ResponseValue(t *testing.T) {
|
||||
resp := requests.NewResponse(new(http.Response))
|
||||
resp.StatusCode = 200
|
||||
resp.Header = http.Header{}
|
||||
resp.Header.Set("Hello", "World")
|
||||
|
||||
checkpoint := new(ResponseHeaderCheckpoint)
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "Hello", nil, 1))
|
||||
}
|
||||
32
EdgeNode/internal/waf/checkpoints/response_status.go
Normal file
32
EdgeNode/internal/waf/checkpoints/response_status.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// ResponseStatusCheckpoint ${bytesSent}
|
||||
type ResponseStatusCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if resp != nil {
|
||||
value = resp.StatusCode
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheLongLife
|
||||
}
|
||||
15
EdgeNode/internal/waf/checkpoints/response_status_test.go
Normal file
15
EdgeNode/internal/waf/checkpoints/response_status_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResponseStatusCheckpoint_ResponseValue(t *testing.T) {
|
||||
resp := requests.NewResponse(new(http.Response))
|
||||
resp.StatusCode = 200
|
||||
|
||||
checkpoint := new(ResponseStatusCheckpoint)
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil, 1))
|
||||
}
|
||||
27
EdgeNode/internal/waf/checkpoints/sample_request.go
Normal file
27
EdgeNode/internal/waf/checkpoints/sample_request.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// SampleRequestCheckpoint just a sample checkpoint, copy and change it for your new checkpoint
|
||||
type SampleRequestCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *SampleRequestCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SampleRequestCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options, ruleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SampleRequestCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
28
EdgeNode/internal/waf/checkpoints/sample_response.go
Normal file
28
EdgeNode/internal/waf/checkpoints/sample_response.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// SampleResponseCheckpoint just a sample checkpoint, copy and change it for your new checkpoint
|
||||
type SampleResponseCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *SampleResponseCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *SampleResponseCheckpoint) RequestValue(req requests.Request, param string, options maps.Map, ruleId int64) (value any, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SampleResponseCheckpoint) ResponseValue(req requests.Request, resp *requests.Response, param string, options maps.Map, ruleId int64) (value any, hasRequestBody bool, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SampleResponseCheckpoint) CacheLife() utils.CacheLife {
|
||||
return utils.CacheMiddleLife
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user