mirror of
https://github.com/TeaOSLab/EdgeCommon.git
synced 2026-04-10 01:35:19 +08:00
实现WAF部分功能
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package firewallconfigs
|
||||
|
||||
type KeyValue struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func NewKeyValue(name string, value string) *KeyValue {
|
||||
return &KeyValue{
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// check point definition
|
||||
type HTTPFirewallCheckpointDefinition struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Prefix string `json:"prefix"`
|
||||
IsRequest bool `json:"isRequest"`
|
||||
Params []*KeyValue `json:"params"`
|
||||
Options []OptionInterface `json:"options"`
|
||||
}
|
||||
279
pkg/serverconfigs/firewallconfigs/http_firewall_checkpoints.go
Normal file
279
pkg/serverconfigs/firewallconfigs/http_firewall_checkpoints.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package firewallconfigs
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// all check points list
|
||||
var AllCheckpoints = []*HTTPFirewallCheckpointDefinition{
|
||||
{
|
||||
Name: "客户端地址(IP)",
|
||||
Prefix: "remoteAddr",
|
||||
Description: "试图通过分析X-Forwarded-For等Header获取的客户端地址,比如192.168.1.100",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "客户端源地址(IP)",
|
||||
Prefix: "rawRemoteAddr",
|
||||
Description: "直接连接的客户端地址,比如192.168.1.100",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "客户端端口",
|
||||
Prefix: "remotePort",
|
||||
Description: "直接连接的客户端地址端口",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "客户端用户名",
|
||||
Prefix: "remoteUser",
|
||||
Description: "通过BasicAuth登录的客户端用户名",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求URI",
|
||||
Prefix: "requestURI",
|
||||
Description: "包含URL参数的请求URI,比如/hello/world?lang=go",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求路径",
|
||||
Prefix: "requestPath",
|
||||
Description: "不包含URL参数的请求路径,比如/hello/world",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求内容长度",
|
||||
Prefix: "requestLength",
|
||||
Description: "请求Header中的Content-Length",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求体内容",
|
||||
Prefix: "requestBody",
|
||||
Description: "通常在POST或者PUT等操作时会附带请求体,最大限制32M",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求URI和请求体组合",
|
||||
Prefix: "requestAll",
|
||||
Description: "${requestURI}和${requestBody}组合",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求表单参数",
|
||||
Prefix: "requestForm",
|
||||
Description: "获取POST或者其他方法发送的表单参数,最大请求体限制32M",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "上传文件",
|
||||
Prefix: "requestUpload",
|
||||
Description: "获取POST上传的文件信息,最大请求体限制32M",
|
||||
Params: []*KeyValue{
|
||||
NewKeyValue("最小文件尺寸", "minSize"),
|
||||
NewKeyValue("最大文件尺寸", "maxSize"),
|
||||
NewKeyValue("扩展名(如.txt)", "ext"),
|
||||
NewKeyValue("原始文件名", "name"),
|
||||
NewKeyValue("表单字段名", "field"),
|
||||
},
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求JSON参数",
|
||||
Prefix: "requestJSON",
|
||||
Description: "获取POST或者其他方法发送的JSON,最大请求体限制32M,使用点(.)符号表示多级数据",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求方法",
|
||||
Prefix: "requestMethod",
|
||||
Description: "比如GET、POST",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求协议",
|
||||
Prefix: "scheme",
|
||||
Description: "比如http或https",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "HTTP协议版本",
|
||||
Prefix: "proto",
|
||||
Description: "比如HTTP/1.1",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "主机名",
|
||||
Prefix: "host",
|
||||
Description: "比如teaos.cn",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "请求来源URL",
|
||||
Prefix: "referer",
|
||||
Description: "请求Header中的Referer值",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "客户端信息",
|
||||
Prefix: "userAgent",
|
||||
Description: "比如Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "内容类型",
|
||||
Prefix: "contentType",
|
||||
Description: "请求Header的Content-Type",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "所有cookie组合字符串",
|
||||
Prefix: "cookies",
|
||||
Description: "比如sid=IxZVPFhE&city=beijing&uid=18237",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "单个cookie值",
|
||||
Prefix: "cookie",
|
||||
Description: "单个cookie值",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "所有URL参数组合",
|
||||
Prefix: "args",
|
||||
Description: "比如name=lu&age=20",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "单个URL参数值",
|
||||
Prefix: "arg",
|
||||
Description: "单个URL参数值",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "所有Header信息",
|
||||
Prefix: "headers",
|
||||
Description: "使用\n隔开的Header信息字符串",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "单个Header值",
|
||||
Prefix: "header",
|
||||
Description: "单个Header值",
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "CC统计",
|
||||
Prefix: "cc",
|
||||
Description: "统计某段时间段内的请求信息",
|
||||
Params: []*KeyValue{
|
||||
NewKeyValue("请求数", "requests"),
|
||||
},
|
||||
Options: []OptionInterface{
|
||||
&FieldOption{
|
||||
Type: "field",
|
||||
Name: "统计周期",
|
||||
Code: "period",
|
||||
Value: "60",
|
||||
IsRequired: false,
|
||||
Size: 8,
|
||||
Comment: "",
|
||||
Placeholder: "",
|
||||
RightLabel: "秒",
|
||||
MaxLength: 8,
|
||||
Validate: func(value string) (ok bool, message string) {
|
||||
if regexp.MustCompile("^\\d+$").MatchString(value) {
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
message = "周期需要是一个整数数字"
|
||||
return
|
||||
},
|
||||
},
|
||||
&OptionsOption{
|
||||
Type: "options",
|
||||
Name: "用户识别读取来源",
|
||||
Code: "userType",
|
||||
Value: "",
|
||||
IsRequired: false,
|
||||
Size: 10,
|
||||
Comment: "",
|
||||
RightLabel: "",
|
||||
Validate: nil,
|
||||
Options: []maps.Map{
|
||||
{
|
||||
"name": "IP",
|
||||
"value": "ip",
|
||||
},
|
||||
{
|
||||
"name": "Cookie",
|
||||
"value": "cookie",
|
||||
},
|
||||
{
|
||||
"name": "URL参数",
|
||||
"value": "get",
|
||||
},
|
||||
{
|
||||
"name": "POST参数",
|
||||
"value": "post",
|
||||
},
|
||||
{
|
||||
"name": "HTTP Header",
|
||||
"value": "header",
|
||||
},
|
||||
},
|
||||
},
|
||||
&FieldOption{
|
||||
Type: "field",
|
||||
Name: "用户识别字段",
|
||||
Code: "userField",
|
||||
Comment: "识别用户的唯一性字段,在用户读取来源不是IP时使用",
|
||||
},
|
||||
&FieldOption{
|
||||
Type: "field",
|
||||
Name: "字段读取位置",
|
||||
Code: "userIndex",
|
||||
Size: 5,
|
||||
MaxLength: 5,
|
||||
Comment: "读取用户识别字段的位置,从0开始,比如user12345的数字ID 12345的位置就是5,在用户读取来源不是IP时使用",
|
||||
},
|
||||
},
|
||||
IsRequest: true,
|
||||
},
|
||||
{
|
||||
Name: "响应状态码",
|
||||
Prefix: "status",
|
||||
Description: "响应状态码,比如200、404、500",
|
||||
IsRequest: false,
|
||||
},
|
||||
{
|
||||
Name: "响应Header",
|
||||
Prefix: "responseHeader",
|
||||
Description: "响应Header值",
|
||||
IsRequest: false,
|
||||
},
|
||||
{
|
||||
Name: "响应内容",
|
||||
Prefix: "responseBody",
|
||||
Description: "响应内容字符串",
|
||||
IsRequest: false,
|
||||
},
|
||||
{
|
||||
Name: "响应内容长度",
|
||||
Prefix: "bytesSent",
|
||||
Description: "响应内容长度,通过响应的Header Content-Length获取",
|
||||
IsRequest: false,
|
||||
},
|
||||
}
|
||||
|
||||
// find a check point definition
|
||||
func FindCheckpointDefinition(prefix string) *HTTPFirewallCheckpointDefinition {
|
||||
for _, def := range AllCheckpoints {
|
||||
if def.Prefix == prefix {
|
||||
return def
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (this *HTTPFirewallPolicy) AllRuleGroups() []*HTTPFirewallRuleGroup {
|
||||
return result
|
||||
}
|
||||
|
||||
// 根据代号获取分组
|
||||
// 根据代号查找分组
|
||||
func (this *HTTPFirewallPolicy) FindRuleGroupWithCode(code string) *HTTPFirewallRuleGroup {
|
||||
for _, g := range this.AllRuleGroups() {
|
||||
if g.Code == code {
|
||||
@@ -52,6 +52,16 @@ func (this *HTTPFirewallPolicy) FindRuleGroupWithCode(code string) *HTTPFirewall
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据ID查找分组
|
||||
func (this *HTTPFirewallPolicy) FindRuleGroup(groupId int64) *HTTPFirewallRuleGroup {
|
||||
for _, g := range this.AllRuleGroups() {
|
||||
if g.Id == groupId {
|
||||
return g
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除某个分组
|
||||
func (this *HTTPFirewallPolicy) RemoveRuleGroup(groupId int64) {
|
||||
if this.Inbound != nil {
|
||||
|
||||
@@ -12,5 +12,6 @@ type HTTPFirewallRule struct {
|
||||
}
|
||||
|
||||
func (this *HTTPFirewallRule) Init() error {
|
||||
// TODO 执行更严谨的校验
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,3 +26,13 @@ func (this *HTTPFirewallRuleGroup) Init() error {
|
||||
func (this *HTTPFirewallRuleGroup) AddRuleSet(ruleSet *HTTPFirewallRuleSet) {
|
||||
this.Sets = append(this.Sets, ruleSet)
|
||||
}
|
||||
|
||||
// 根据ID查找规则集
|
||||
func (this *HTTPFirewallRuleGroup) FindRuleSet(ruleSetId int64) *HTTPFirewallRuleSet {
|
||||
for _, set := range this.Sets {
|
||||
if set.Id == ruleSetId {
|
||||
return set
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
4
pkg/serverconfigs/firewallconfigs/option.go
Normal file
4
pkg/serverconfigs/firewallconfigs/option.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package firewallconfigs
|
||||
|
||||
type OptionInterface interface {
|
||||
}
|
||||
24
pkg/serverconfigs/firewallconfigs/option_field.go
Normal file
24
pkg/serverconfigs/firewallconfigs/option_field.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package firewallconfigs
|
||||
|
||||
// attach option
|
||||
type FieldOption struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Value string `json:"value"` // default value
|
||||
IsRequired bool `json:"isRequired"`
|
||||
Size int `json:"size"`
|
||||
Comment string `json:"comment"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
RightLabel string `json:"rightLabel"`
|
||||
MaxLength int `json:"maxLength"`
|
||||
Validate func(value string) (ok bool, message string) `json:"-"`
|
||||
}
|
||||
|
||||
func NewFieldOption(name string, code string) *FieldOption {
|
||||
return &FieldOption{
|
||||
Type: "field",
|
||||
Name: name,
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
28
pkg/serverconfigs/firewallconfigs/option_options.go
Normal file
28
pkg/serverconfigs/firewallconfigs/option_options.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package firewallconfigs
|
||||
|
||||
import "github.com/iwind/TeaGo/maps"
|
||||
|
||||
type OptionsOption struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Value string `json:"value"` // default value
|
||||
IsRequired bool `json:"isRequired"`
|
||||
Size int `json:"size"`
|
||||
Comment string `json:"comment"`
|
||||
RightLabel string `json:"rightLabel"`
|
||||
Validate func(value string) (ok bool, message string) `json:"-"`
|
||||
Options []maps.Map `json:"options"`
|
||||
}
|
||||
|
||||
func NewOptionsOption(name string, code string) *OptionsOption {
|
||||
return &OptionsOption{
|
||||
Type: "options",
|
||||
Name: name,
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *OptionsOption) SetOptions(options []maps.Map) {
|
||||
this.Options = options
|
||||
}
|
||||
Reference in New Issue
Block a user