mirror of
https://github.com/TeaOSLab/EdgeNode.git
synced 2025-12-31 18:46:36 +08:00
实现WAF
This commit is contained in:
246
internal/waf/checkpoints/cc.go
Normal file
246
internal/waf/checkpoints/cc.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/grids"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ${cc.arg}
|
||||
// TODO implement more traffic rules
|
||||
type CCCheckpoint struct {
|
||||
Checkpoint
|
||||
|
||||
grid *grids.Grid
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) Init() {
|
||||
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) Start() {
|
||||
if this.grid != nil {
|
||||
this.grid.Destroy()
|
||||
}
|
||||
this.grid = grids.NewGrid(32, grids.NewLimitCountOpt(1000_0000))
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = 0
|
||||
|
||||
if this.grid == nil {
|
||||
this.once.Do(func() {
|
||||
this.Start()
|
||||
})
|
||||
if this.grid == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
periodString, ok := options["period"]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
period := types.Int64(periodString)
|
||||
if period < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
v, _ := options["userType"]
|
||||
userType := types.String(v)
|
||||
|
||||
v, _ = options["userField"]
|
||||
userField := types.String(v)
|
||||
|
||||
v, _ = options["userIndex"]
|
||||
userIndex := types.Int(v)
|
||||
|
||||
if param == "requests" { // requests
|
||||
var key = ""
|
||||
switch userType {
|
||||
case "ip":
|
||||
key = this.ip(req)
|
||||
case "cookie":
|
||||
if len(userField) == 0 {
|
||||
key = this.ip(req)
|
||||
} else {
|
||||
cookie, _ := req.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 = this.ip(req)
|
||||
} else {
|
||||
v := req.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 = this.ip(req)
|
||||
} else {
|
||||
v := req.PostFormValue(userField)
|
||||
if userIndex > 0 && len(v) > userIndex {
|
||||
v = v[userIndex:]
|
||||
}
|
||||
key = "USER@" + userType + "@" + userField + "@" + v
|
||||
}
|
||||
case "header":
|
||||
if len(userField) == 0 {
|
||||
key = this.ip(req)
|
||||
} else {
|
||||
v := req.Header.Get(userField)
|
||||
if userIndex > 0 && len(v) > userIndex {
|
||||
v = v[userIndex:]
|
||||
}
|
||||
key = "USER@" + userType + "@" + userField + "@" + v
|
||||
}
|
||||
default:
|
||||
key = this.ip(req)
|
||||
}
|
||||
if len(key) == 0 {
|
||||
key = this.ip(req)
|
||||
}
|
||||
value = this.grid.IncreaseInt64([]byte(key), 1, period)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
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() {
|
||||
if this.grid != nil {
|
||||
this.grid.Destroy()
|
||||
this.grid = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *CCCheckpoint) ip(req *requests.Request) string {
|
||||
// X-Forwarded-For
|
||||
forwardedFor := req.Header.Get("X-Forwarded-For")
|
||||
if len(forwardedFor) > 0 {
|
||||
commaIndex := strings.Index(forwardedFor, ",")
|
||||
if commaIndex > 0 {
|
||||
return forwardedFor[:commaIndex]
|
||||
}
|
||||
return forwardedFor
|
||||
}
|
||||
|
||||
// Real-IP
|
||||
{
|
||||
realIP, ok := req.Header["X-Real-IP"]
|
||||
if ok && len(realIP) > 0 {
|
||||
return realIP[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Real-Ip
|
||||
{
|
||||
realIP, ok := req.Header["X-Real-Ip"]
|
||||
if ok && len(realIP) > 0 {
|
||||
return realIP[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Remote-Addr
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil {
|
||||
return host
|
||||
}
|
||||
return req.RemoteAddr
|
||||
}
|
||||
42
internal/waf/checkpoints/cc_test.go
Normal file
42
internal/waf/checkpoints/cc_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"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.NewRequest(raw)
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
|
||||
checkpoint := new(CCCheckpoint)
|
||||
checkpoint.Init()
|
||||
checkpoint.Start()
|
||||
|
||||
options := map[string]string{
|
||||
"period": "5",
|
||||
}
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
|
||||
req.RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
|
||||
req.RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
|
||||
req.RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
|
||||
req.RemoteAddr = "127.0.0.2"
|
||||
t.Log(checkpoint.RequestValue(req, "requests", options))
|
||||
}
|
||||
28
internal/waf/checkpoints/checkpoint.go
Normal file
28
internal/waf/checkpoints/checkpoint.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package checkpoints
|
||||
|
||||
type Checkpoint struct {
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Init() {
|
||||
|
||||
}
|
||||
|
||||
func (this *Checkpoint) IsRequest() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *Checkpoint) ParamOptions() *ParamOptions {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Options() []OptionInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Start() {
|
||||
|
||||
}
|
||||
|
||||
func (this *Checkpoint) Stop() {
|
||||
|
||||
}
|
||||
10
internal/waf/checkpoints/checkpoint_definition.go
Normal file
10
internal/waf/checkpoints/checkpoint_definition.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package checkpoints
|
||||
|
||||
// check point definition
|
||||
type CheckpointDefinition struct {
|
||||
Name string
|
||||
Description string
|
||||
Prefix string
|
||||
HasParams bool // has sub params
|
||||
Instance CheckpointInterface
|
||||
}
|
||||
32
internal/waf/checkpoints/checkpoint_interface.go
Normal file
32
internal/waf/checkpoints/checkpoint_interface.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// Check Point
|
||||
type CheckpointInterface interface {
|
||||
// initialize
|
||||
Init()
|
||||
|
||||
// is request?
|
||||
IsRequest() bool
|
||||
|
||||
// get request value
|
||||
RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error)
|
||||
|
||||
// get response value
|
||||
ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error)
|
||||
|
||||
// param option list
|
||||
ParamOptions() *ParamOptions
|
||||
|
||||
// options
|
||||
Options() []OptionInterface
|
||||
|
||||
// start
|
||||
Start()
|
||||
|
||||
// stop
|
||||
Stop()
|
||||
}
|
||||
5
internal/waf/checkpoints/option.go
Normal file
5
internal/waf/checkpoints/option.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package checkpoints
|
||||
|
||||
type OptionInterface interface {
|
||||
Type() string
|
||||
}
|
||||
26
internal/waf/checkpoints/option_field.go
Normal file
26
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
internal/waf/checkpoints/option_options.go
Normal file
30
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
internal/waf/checkpoints/param_option.go
Normal file
21
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,
|
||||
})
|
||||
}
|
||||
46
internal/waf/checkpoints/request_all.go
Normal file
46
internal/waf/checkpoints/request_all.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// ${requestAll}
|
||||
type RequestAllCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestAllCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
valueBytes := []byte{}
|
||||
if len(req.RequestURI) > 0 {
|
||||
valueBytes = append(valueBytes, req.RequestURI...)
|
||||
} else if req.URL != nil {
|
||||
valueBytes = append(valueBytes, req.URL.RequestURI()...)
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
valueBytes = append(valueBytes, ' ')
|
||||
|
||||
if len(req.BodyData) == 0 {
|
||||
data, err := req.ReadBody(int64(32 * 1024 * 1024)) // read 32m bytes
|
||||
if err != nil {
|
||||
return "", err, nil
|
||||
}
|
||||
|
||||
req.BodyData = data
|
||||
req.RestoreBody(data)
|
||||
}
|
||||
valueBytes = append(valueBytes, req.BodyData...)
|
||||
}
|
||||
|
||||
value = valueBytes
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestAllCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = ""
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
70
internal/waf/checkpoints/request_all_test.go
Normal file
70
internal/waf/checkpoints/request_all_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"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.NewRequest(req), "", nil)
|
||||
if sysErr != nil {
|
||||
t.Fatal(sysErr)
|
||||
}
|
||||
if userErr != nil {
|
||||
t.Fatal(userErr)
|
||||
}
|
||||
t.Log(v)
|
||||
t.Log(types.String(v))
|
||||
|
||||
body, err := ioutil.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.NewRequest(req), "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("value bytes:", len(types.String(value)))
|
||||
|
||||
body, err := ioutil.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.NewRequest(req), "", nil)
|
||||
}
|
||||
}
|
||||
20
internal/waf/checkpoints/request_arg.go
Normal file
20
internal/waf/checkpoints/request_arg.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestArgCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestArgCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
return req.URL.Query().Get(param), nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestArgCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
21
internal/waf/checkpoints/request_arg_test.go
Normal file
21
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.NewRequest(rawReq)
|
||||
|
||||
checkpoint := new(RequestArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil))
|
||||
t.Log(checkpoint.ResponseValue(req, nil, "name", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "name2", nil))
|
||||
}
|
||||
21
internal/waf/checkpoints/request_args.go
Normal file
21
internal/waf/checkpoints/request_args.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestArgsCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestArgsCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.URL.RawQuery
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestArgsCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
36
internal/waf/checkpoints/request_body.go
Normal file
36
internal/waf/checkpoints/request_body.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// ${requestBody}
|
||||
type RequestBodyCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestBodyCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if req.Body == nil {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.BodyData) == 0 {
|
||||
data, err := req.ReadBody(int64(32 * 1024 * 1024)) // read 32m bytes
|
||||
if err != nil {
|
||||
return "", err, nil
|
||||
}
|
||||
|
||||
req.BodyData = data
|
||||
req.RestoreBody(data)
|
||||
}
|
||||
|
||||
return req.BodyData, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestBodyCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
47
internal/waf/checkpoints/request_body_test.go
Normal file
47
internal/waf/checkpoints/request_body_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequestBodyCheckpoint_RequestValue(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPost, "http://teaos.cn", bytes.NewBuffer([]byte("123456")))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
checkpoint := new(RequestBodyCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(requests.NewRequest(req), "", nil))
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
|
||||
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.NewRequest(req), "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("value bytes:", len(types.String(value)))
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("raw bytes:", len(body))
|
||||
}
|
||||
21
internal/waf/checkpoints/request_content_type.go
Normal file
21
internal/waf/checkpoints/request_content_type.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestContentTypeCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestContentTypeCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.Header.Get("Content-Type")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestContentTypeCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
27
internal/waf/checkpoints/request_cookie.go
Normal file
27
internal/waf/checkpoints/request_cookie.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestCookieCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestCookieCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
cookie, err := req.Cookie(param)
|
||||
if err != nil {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
value = cookie.Value
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestCookieCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
27
internal/waf/checkpoints/request_cookies.go
Normal file
27
internal/waf/checkpoints/request_cookies.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestCookiesCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestCookiesCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
var cookies = []string{}
|
||||
for _, cookie := range req.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 map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
39
internal/waf/checkpoints/request_form_arg.go
Normal file
39
internal/waf/checkpoints/request_form_arg.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// ${requestForm.arg}
|
||||
type RequestFormArgCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestFormArgCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if req.Body == nil {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.BodyData) == 0 {
|
||||
data, err := req.ReadBody(32 * 1024 * 1024) // read 32m bytes
|
||||
if err != nil {
|
||||
return "", err, nil
|
||||
}
|
||||
|
||||
req.BodyData = data
|
||||
req.RestoreBody(data)
|
||||
}
|
||||
|
||||
// TODO improve performance
|
||||
values, _ := url.ParseQuery(string(req.BodyData))
|
||||
return values.Get(param), nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestFormArgCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
32
internal/waf/checkpoints/request_form_arg_test.go
Normal file
32
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/ioutil"
|
||||
"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.NewRequest(rawReq)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestFormArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "age", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "Hello", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "encoded", nil))
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
27
internal/waf/checkpoints/request_header.go
Normal file
27
internal/waf/checkpoints/request_header.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestHeaderCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHeaderCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
v, found := req.Header[param]
|
||||
if !found {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
value = strings.Join(v, ";")
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHeaderCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
30
internal/waf/checkpoints/request_headers.go
Normal file
30
internal/waf/checkpoints/request_headers.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestHeadersCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHeadersCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
var headers = []string{}
|
||||
for k, v := range req.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 map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
21
internal/waf/checkpoints/request_host.go
Normal file
21
internal/waf/checkpoints/request_host.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestHostCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestHostCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.Host
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestHostCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
20
internal/waf/checkpoints/request_host_test.go
Normal file
20
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.NewRequest(rawReq)
|
||||
req.Header.Set("Host", "cloud.teaos.cn")
|
||||
|
||||
checkpoint := new(RequestHostCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil))
|
||||
}
|
||||
44
internal/waf/checkpoints/request_json_arg.go
Normal file
44
internal/waf/checkpoints/request_json_arg.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ${requestJSON.arg}
|
||||
type RequestJSONArgCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestJSONArgCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if len(req.BodyData) == 0 {
|
||||
data, err := req.ReadBody(int64(32 * 1024 * 1024)) // read 32m bytes
|
||||
if err != nil {
|
||||
return "", err, nil
|
||||
}
|
||||
req.BodyData = data
|
||||
defer req.RestoreBody(data)
|
||||
}
|
||||
|
||||
// TODO improve performance
|
||||
var m interface{} = nil
|
||||
err := json.Unmarshal(req.BodyData, &m)
|
||||
if err != nil || m == nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
value = utils.Get(m, strings.Split(param, "."))
|
||||
if value != nil {
|
||||
return value, nil, err
|
||||
}
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestJSONArgCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
99
internal/waf/checkpoints/request_json_arg_test.go
Normal file
99
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/ioutil"
|
||||
"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.NewRequest(rawReq)
|
||||
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestJSONArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "age", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "Hello", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "books", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "books.1", nil))
|
||||
|
||||
body, err := ioutil.ReadAll(req.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.NewRequest(rawReq)
|
||||
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestJSONArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "0.name", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.age", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.Hello", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books.1", nil))
|
||||
|
||||
body, err := ioutil.ReadAll(req.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.NewRequest(rawReq)
|
||||
//req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
checkpoint := new(RequestJSONArgCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "0.name", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.age", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.Hello", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "0.books.1", nil))
|
||||
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(body))
|
||||
}
|
||||
21
internal/waf/checkpoints/request_length.go
Normal file
21
internal/waf/checkpoints/request_length.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestLengthCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestLengthCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.ContentLength
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestLengthCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
21
internal/waf/checkpoints/request_method.go
Normal file
21
internal/waf/checkpoints/request_method.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestMethodCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestMethodCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.Method
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestMethodCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
20
internal/waf/checkpoints/request_path.go
Normal file
20
internal/waf/checkpoints/request_path.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestPathCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestPathCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
return req.URL.Path, nil, nil
|
||||
}
|
||||
|
||||
func (this *RequestPathCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
18
internal/waf/checkpoints/request_path_test.go
Normal file
18
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.NewRequest(rawReq)
|
||||
checkpoint := new(RequestPathCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil))
|
||||
}
|
||||
21
internal/waf/checkpoints/request_proto.go
Normal file
21
internal/waf/checkpoints/request_proto.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestProtoCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestProtoCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.Proto
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestProtoCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
27
internal/waf/checkpoints/request_raw_remote_addr.go
Normal file
27
internal/waf/checkpoints/request_raw_remote_addr.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net"
|
||||
)
|
||||
|
||||
type RequestRawRemoteAddrCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRawRemoteAddrCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil {
|
||||
value = host
|
||||
} else {
|
||||
value = req.RemoteAddr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRawRemoteAddrCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
21
internal/waf/checkpoints/request_referer.go
Normal file
21
internal/waf/checkpoints/request_referer.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestRefererCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRefererCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.Referer()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRefererCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
59
internal/waf/checkpoints/request_remote_addr.go
Normal file
59
internal/waf/checkpoints/request_remote_addr.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type RequestRemoteAddrCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRemoteAddrCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
// X-Forwarded-For
|
||||
forwardedFor := req.Header.Get("X-Forwarded-For")
|
||||
if len(forwardedFor) > 0 {
|
||||
commaIndex := strings.Index(forwardedFor, ",")
|
||||
if commaIndex > 0 {
|
||||
value = forwardedFor[:commaIndex]
|
||||
return
|
||||
}
|
||||
value = forwardedFor
|
||||
return
|
||||
}
|
||||
|
||||
// Real-IP
|
||||
{
|
||||
realIP, ok := req.Header["X-Real-IP"]
|
||||
if ok && len(realIP) > 0 {
|
||||
value = realIP[0]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Real-Ip
|
||||
{
|
||||
realIP, ok := req.Header["X-Real-Ip"]
|
||||
if ok && len(realIP) > 0 {
|
||||
value = realIP[0]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Remote-Addr
|
||||
host, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil {
|
||||
value = host
|
||||
} else {
|
||||
value = req.RemoteAddr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemoteAddrCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
28
internal/waf/checkpoints/request_remote_port.go
Normal file
28
internal/waf/checkpoints/request_remote_port.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
)
|
||||
|
||||
type RequestRemotePortCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRemotePortCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
_, port, err := net.SplitHostPort(req.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 map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
26
internal/waf/checkpoints/request_remote_user.go
Normal file
26
internal/waf/checkpoints/request_remote_user.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestRemoteUserCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestRemoteUserCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
username, _, ok := req.BasicAuth()
|
||||
if !ok {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
value = username
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestRemoteUserCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
21
internal/waf/checkpoints/request_scheme.go
Normal file
21
internal/waf/checkpoints/request_scheme.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestSchemeCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestSchemeCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.URL.Scheme
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestSchemeCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
18
internal/waf/checkpoints/request_scheme_test.go
Normal file
18
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.NewRequest(rawReq)
|
||||
checkpoint := new(RequestSchemeCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "", nil))
|
||||
}
|
||||
130
internal/waf/checkpoints/request_upload.go
Normal file
130
internal/waf/checkpoints/request_upload.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ${requestUpload.arg}
|
||||
type RequestUploadCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestUploadCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = ""
|
||||
if param == "minSize" || param == "maxSize" {
|
||||
value = 0
|
||||
}
|
||||
|
||||
if req.Method != http.MethodPost {
|
||||
return
|
||||
}
|
||||
|
||||
if req.Body == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if req.MultipartForm == nil {
|
||||
if len(req.BodyData) == 0 {
|
||||
data, err := req.ReadBody(32 * 1024 * 1024)
|
||||
if err != nil {
|
||||
sysErr = err
|
||||
return
|
||||
}
|
||||
|
||||
req.BodyData = data
|
||||
defer req.RestoreBody(data)
|
||||
}
|
||||
oldBody := req.Body
|
||||
req.Body = ioutil.NopCloser(bytes.NewBuffer(req.BodyData))
|
||||
|
||||
err := req.ParseMultipartForm(32 * 1024 * 1024)
|
||||
|
||||
// 还原
|
||||
req.Body = oldBody
|
||||
|
||||
if err != nil {
|
||||
userErr = err
|
||||
return
|
||||
}
|
||||
|
||||
if req.MultipartForm == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if param == "field" { // field
|
||||
fields := []string{}
|
||||
for field := range req.MultipartForm.File {
|
||||
fields = append(fields, field)
|
||||
}
|
||||
value = strings.Join(fields, ",")
|
||||
} else if param == "minSize" { // minSize
|
||||
minSize := int64(0)
|
||||
for _, files := range req.MultipartForm.File {
|
||||
for _, file := range files {
|
||||
if minSize == 0 || minSize > file.Size {
|
||||
minSize = file.Size
|
||||
}
|
||||
}
|
||||
}
|
||||
value = minSize
|
||||
} else if param == "maxSize" { // maxSize
|
||||
maxSize := int64(0)
|
||||
for _, files := range req.MultipartForm.File {
|
||||
for _, file := range files {
|
||||
if maxSize < file.Size {
|
||||
maxSize = file.Size
|
||||
}
|
||||
}
|
||||
}
|
||||
value = maxSize
|
||||
} else if param == "name" { // name
|
||||
names := []string{}
|
||||
for _, files := range req.MultipartForm.File {
|
||||
for _, file := range files {
|
||||
if !lists.ContainsString(names, file.Filename) {
|
||||
names = append(names, file.Filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
value = strings.Join(names, ",")
|
||||
} else if param == "ext" { // ext
|
||||
extensions := []string{}
|
||||
for _, files := range req.MultipartForm.File {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
value = strings.Join(extensions, ",")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestUploadCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
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
|
||||
}
|
||||
81
internal/waf/checkpoints/request_upload_test.go
Normal file
81
internal/waf/checkpoints/request_upload_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"io/ioutil"
|
||||
"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 {
|
||||
part.Write([]byte("lu"))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormField("age")
|
||||
if err == nil {
|
||||
part.Write([]byte("20"))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile", "hello.txt")
|
||||
if err == nil {
|
||||
part.Write([]byte("Hello, World!"))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile2", "hello.PHP")
|
||||
if err == nil {
|
||||
part.Write([]byte("Hello, World, PHP!"))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile3", "hello.asp")
|
||||
if err == nil {
|
||||
part.Write([]byte("Hello, World, ASP Pages!"))
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
part, err := writer.CreateFormFile("myFile4", "hello.asp")
|
||||
if err == nil {
|
||||
part.Write([]byte("Hello, World, ASP Pages!"))
|
||||
}
|
||||
}
|
||||
|
||||
writer.Close()
|
||||
|
||||
rawReq, err := http.NewRequest(http.MethodPost, "http://teaos.cn/", body)
|
||||
if err != nil {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
req := requests.NewRequest(rawReq)
|
||||
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
|
||||
checkpoint := new(RequestUploadCheckpoint)
|
||||
t.Log(checkpoint.RequestValue(req, "field", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "minSize", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "maxSize", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "name", nil))
|
||||
t.Log(checkpoint.RequestValue(req, "ext", nil))
|
||||
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(data))
|
||||
}
|
||||
25
internal/waf/checkpoints/request_uri.go
Normal file
25
internal/waf/checkpoints/request_uri.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestURICheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestURICheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if len(req.RequestURI) > 0 {
|
||||
value = req.RequestURI
|
||||
} else if req.URL != nil {
|
||||
value = req.URL.RequestURI()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestURICheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
21
internal/waf/checkpoints/request_user_agent.go
Normal file
21
internal/waf/checkpoints/request_user_agent.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
type RequestUserAgentCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *RequestUserAgentCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = req.UserAgent()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *RequestUserAgentCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
41
internal/waf/checkpoints/response_body.go
Normal file
41
internal/waf/checkpoints/response_body.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// ${responseBody}
|
||||
type ResponseBodyCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseBodyCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = ""
|
||||
if resp != nil && resp.Body != nil {
|
||||
if len(resp.BodyData) > 0 {
|
||||
value = string(resp.BodyData)
|
||||
return
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
sysErr = err
|
||||
return
|
||||
}
|
||||
resp.BodyData = body
|
||||
_ = resp.Body.Close()
|
||||
value = body
|
||||
resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
return
|
||||
}
|
||||
29
internal/waf/checkpoints/response_body_test.go
Normal file
29
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/ioutil"
|
||||
"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 = ioutil.NopCloser(bytes.NewBuffer([]byte("Hello, World")))
|
||||
|
||||
checkpoint := new(ResponseBodyCheckpoint)
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil))
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil))
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil))
|
||||
t.Log(checkpoint.ResponseValue(nil, resp, "", nil))
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("after read:", string(data))
|
||||
}
|
||||
27
internal/waf/checkpoints/response_bytes_sent.go
Normal file
27
internal/waf/checkpoints/response_bytes_sent.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// ${bytesSent}
|
||||
type ResponseBytesSentCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseBytesSentCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = 0
|
||||
if resp != nil {
|
||||
value = resp.ContentLength
|
||||
}
|
||||
return
|
||||
}
|
||||
28
internal/waf/checkpoints/response_header.go
Normal file
28
internal/waf/checkpoints/response_header.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// ${responseHeader.arg}
|
||||
type ResponseHeaderCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = ""
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseHeaderCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if resp != nil && resp.Header != nil {
|
||||
value = resp.Header.Get(param)
|
||||
} else {
|
||||
value = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
17
internal/waf/checkpoints/response_header_test.go
Normal file
17
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))
|
||||
}
|
||||
26
internal/waf/checkpoints/response_status.go
Normal file
26
internal/waf/checkpoints/response_status.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// ${bytesSent}
|
||||
type ResponseStatusCheckpoint struct {
|
||||
Checkpoint
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) IsRequest() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) RequestValue(req *requests.Request, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
value = 0
|
||||
return
|
||||
}
|
||||
|
||||
func (this *ResponseStatusCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]interface{}) (value interface{}, sysErr error, userErr error) {
|
||||
if resp != nil {
|
||||
value = resp.StatusCode
|
||||
}
|
||||
return
|
||||
}
|
||||
15
internal/waf/checkpoints/response_status_test.go
Normal file
15
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))
|
||||
}
|
||||
21
internal/waf/checkpoints/sample_request.go
Normal file
21
internal/waf/checkpoints/sample_request.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// 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 map[string]string) (value interface{}, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SampleRequestCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]string) (value interface{}, sysErr error, userErr error) {
|
||||
if this.IsRequest() {
|
||||
return this.RequestValue(req, param, options)
|
||||
}
|
||||
return
|
||||
}
|
||||
22
internal/waf/checkpoints/sample_response.go
Normal file
22
internal/waf/checkpoints/sample_response.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
|
||||
)
|
||||
|
||||
// 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 map[string]string) (value interface{}, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (this *SampleResponseCheckpoint) ResponseValue(req *requests.Request, resp *requests.Response, param string, options map[string]string) (value interface{}, sysErr error, userErr error) {
|
||||
return
|
||||
}
|
||||
235
internal/waf/checkpoints/utils.go
Normal file
235
internal/waf/checkpoints/utils.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package checkpoints
|
||||
|
||||
// all check points list
|
||||
var AllCheckpoints = []*CheckpointDefinition{
|
||||
{
|
||||
Name: "客户端地址(IP)",
|
||||
Prefix: "remoteAddr",
|
||||
Description: "试图通过分析X-Forwarded-For等Header获取的客户端地址,比如192.168.1.100",
|
||||
HasParams: false,
|
||||
Instance: new(RequestRemoteAddrCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "客户端源地址(IP)",
|
||||
Prefix: "rawRemoteAddr",
|
||||
Description: "直接连接的客户端地址,比如192.168.1.100",
|
||||
HasParams: false,
|
||||
Instance: new(RequestRawRemoteAddrCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "客户端端口",
|
||||
Prefix: "remotePort",
|
||||
Description: "直接连接的客户端地址端口",
|
||||
HasParams: false,
|
||||
Instance: new(RequestRemotePortCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "客户端用户名",
|
||||
Prefix: "remoteUser",
|
||||
Description: "通过BasicAuth登录的客户端用户名",
|
||||
HasParams: false,
|
||||
Instance: new(RequestRemoteUserCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求URI",
|
||||
Prefix: "requestURI",
|
||||
Description: "包含URL参数的请求URI,比如/hello/world?lang=go",
|
||||
HasParams: false,
|
||||
Instance: new(RequestURICheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求路径",
|
||||
Prefix: "requestPath",
|
||||
Description: "不包含URL参数的请求路径,比如/hello/world",
|
||||
HasParams: false,
|
||||
Instance: new(RequestPathCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求内容长度",
|
||||
Prefix: "requestLength",
|
||||
Description: "请求Header中的Content-Length",
|
||||
HasParams: false,
|
||||
Instance: new(RequestLengthCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求体内容",
|
||||
Prefix: "requestBody",
|
||||
Description: "通常在POST或者PUT等操作时会附带请求体,最大限制32M",
|
||||
HasParams: false,
|
||||
Instance: new(RequestBodyCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求URI和请求体组合",
|
||||
Prefix: "requestAll",
|
||||
Description: "${requestURI}和${requestBody}组合",
|
||||
HasParams: false,
|
||||
Instance: new(RequestAllCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求表单参数",
|
||||
Prefix: "requestForm",
|
||||
Description: "获取POST或者其他方法发送的表单参数,最大请求体限制32M",
|
||||
HasParams: true,
|
||||
Instance: new(RequestFormArgCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "上传文件",
|
||||
Prefix: "requestUpload",
|
||||
Description: "获取POST上传的文件信息,最大请求体限制32M",
|
||||
HasParams: true,
|
||||
Instance: new(RequestUploadCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求JSON参数",
|
||||
Prefix: "requestJSON",
|
||||
Description: "获取POST或者其他方法发送的JSON,最大请求体限制32M,使用点(.)符号表示多级数据",
|
||||
HasParams: true,
|
||||
Instance: new(RequestJSONArgCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求方法",
|
||||
Prefix: "requestMethod",
|
||||
Description: "比如GET、POST",
|
||||
HasParams: false,
|
||||
Instance: new(RequestMethodCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求协议",
|
||||
Prefix: "scheme",
|
||||
Description: "比如http或https",
|
||||
HasParams: false,
|
||||
Instance: new(RequestSchemeCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "HTTP协议版本",
|
||||
Prefix: "proto",
|
||||
Description: "比如HTTP/1.1",
|
||||
HasParams: false,
|
||||
Instance: new(RequestProtoCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "主机名",
|
||||
Prefix: "host",
|
||||
Description: "比如teaos.cn",
|
||||
HasParams: false,
|
||||
Instance: new(RequestHostCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "请求来源URL",
|
||||
Prefix: "referer",
|
||||
Description: "请求Header中的Referer值",
|
||||
HasParams: false,
|
||||
Instance: new(RequestRefererCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "客户端信息",
|
||||
Prefix: "userAgent",
|
||||
Description: "比如Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103",
|
||||
HasParams: false,
|
||||
Instance: new(RequestUserAgentCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "内容类型",
|
||||
Prefix: "contentType",
|
||||
Description: "请求Header的Content-Type",
|
||||
HasParams: false,
|
||||
Instance: new(RequestContentTypeCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "所有cookie组合字符串",
|
||||
Prefix: "cookies",
|
||||
Description: "比如sid=IxZVPFhE&city=beijing&uid=18237",
|
||||
HasParams: false,
|
||||
Instance: new(RequestCookiesCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "单个cookie值",
|
||||
Prefix: "cookie",
|
||||
Description: "单个cookie值",
|
||||
HasParams: true,
|
||||
Instance: new(RequestCookieCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "所有URL参数组合",
|
||||
Prefix: "args",
|
||||
Description: "比如name=lu&age=20",
|
||||
HasParams: false,
|
||||
Instance: new(RequestArgsCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "单个URL参数值",
|
||||
Prefix: "arg",
|
||||
Description: "单个URL参数值",
|
||||
HasParams: true,
|
||||
Instance: new(RequestArgCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "所有Header信息",
|
||||
Prefix: "headers",
|
||||
Description: "使用\n隔开的Header信息字符串",
|
||||
HasParams: false,
|
||||
Instance: new(RequestHeadersCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "单个Header值",
|
||||
Prefix: "header",
|
||||
Description: "单个Header值",
|
||||
HasParams: true,
|
||||
Instance: new(RequestHeaderCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "CC统计",
|
||||
Prefix: "cc",
|
||||
Description: "统计某段时间段内的请求信息",
|
||||
HasParams: true,
|
||||
Instance: new(CCCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "响应状态码",
|
||||
Prefix: "status",
|
||||
Description: "响应状态码,比如200、404、500",
|
||||
HasParams: false,
|
||||
Instance: new(ResponseStatusCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "响应Header",
|
||||
Prefix: "responseHeader",
|
||||
Description: "响应Header值",
|
||||
HasParams: true,
|
||||
Instance: new(ResponseHeaderCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "响应内容",
|
||||
Prefix: "responseBody",
|
||||
Description: "响应内容字符串",
|
||||
HasParams: false,
|
||||
Instance: new(ResponseBodyCheckpoint),
|
||||
},
|
||||
{
|
||||
Name: "响应内容长度",
|
||||
Prefix: "bytesSent",
|
||||
Description: "响应内容长度,通过响应的Header Content-Length获取",
|
||||
HasParams: false,
|
||||
Instance: new(ResponseBytesSentCheckpoint),
|
||||
},
|
||||
}
|
||||
|
||||
// find a check point
|
||||
func FindCheckpoint(prefix string) CheckpointInterface {
|
||||
for _, def := range AllCheckpoints {
|
||||
if def.Prefix == prefix {
|
||||
return def.Instance
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// find a check point definition
|
||||
func FindCheckpointDefinition(prefix string) *CheckpointDefinition {
|
||||
for _, def := range AllCheckpoints {
|
||||
if def.Prefix == prefix {
|
||||
return def
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
31
internal/waf/checkpoints/utils_test.go
Normal file
31
internal/waf/checkpoints/utils_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package checkpoints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFindCheckpointDefinition_Markdown(t *testing.T) {
|
||||
result := []string{}
|
||||
for _, def := range AllCheckpoints {
|
||||
row := "## " + def.Name + "\n* 前缀:`${" + def.Prefix + "}`\n* 描述:" + def.Description
|
||||
if def.HasParams {
|
||||
row += "\n* 是否有子参数:YES"
|
||||
|
||||
paramOptions := def.Instance.ParamOptions()
|
||||
if paramOptions != nil && len(paramOptions.Options) > 0 {
|
||||
row += "\n* 可选子参数"
|
||||
for _, option := range paramOptions.Options {
|
||||
row += "\n * `" + option.Name + "`:值为 `" + option.Value + "`"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
row += "\n* 是否有子参数:NO"
|
||||
}
|
||||
row += "\n"
|
||||
result = append(result, row)
|
||||
}
|
||||
|
||||
fmt.Print(strings.Join(result, "\n") + "\n")
|
||||
}
|
||||
Reference in New Issue
Block a user