mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-09-01 15:32:55 +08:00
Merge pull request #674 from samwafgo/feat_rule_function
feat:rule function
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
package innerbean
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RuleFunc 规则函数助手,提供各种通用的规则判断函数
|
||||
// 使用方式: 在DataContext中注册为 "RF",然后在规则中调用
|
||||
// 例如: RF.IPInRange(MF.SRC_IP, "192.168.0.0", "192.168.1.254")
|
||||
type RuleFunc struct{}
|
||||
|
||||
// NewRuleFunc 创建规则函数助手实例
|
||||
func NewRuleFunc() *RuleFunc {
|
||||
return &RuleFunc{}
|
||||
}
|
||||
|
||||
// ================== IP 相关函数 ==================
|
||||
|
||||
// IPInRange 判断IP是否在指定范围内(包含起始和结束IP)
|
||||
// ip: 要检查的IP地址
|
||||
// startIP: 起始IP地址
|
||||
// endIP: 结束IP地址
|
||||
// 返回: true表示IP在范围内,false表示不在
|
||||
// 使用示例: RF.IPInRange(MF.SRC_IP, "172.16.0.0", "172.20.255.254")
|
||||
func (rf *RuleFunc) IPInRange(ip, startIP, endIP string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
parsedStart := net.ParseIP(startIP)
|
||||
parsedEnd := net.ParseIP(endIP)
|
||||
|
||||
if parsedIP == nil || parsedStart == nil || parsedEnd == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 确保都是相同格式(IPv4或IPv6)
|
||||
parsedIP = parsedIP.To16()
|
||||
parsedStart = parsedStart.To16()
|
||||
parsedEnd = parsedEnd.To16()
|
||||
|
||||
// IP >= startIP && IP <= endIP
|
||||
return bytes.Compare(parsedIP, parsedStart) >= 0 && bytes.Compare(parsedIP, parsedEnd) <= 0
|
||||
}
|
||||
|
||||
// IPInCIDR 判断IP是否在指定的CIDR网段内
|
||||
// ip: 要检查的IP地址
|
||||
// cidr: CIDR格式的网段,如 "192.168.1.0/24"
|
||||
// 返回: true表示IP在网段内,false表示不在
|
||||
// 使用示例: RF.IPInCIDR(MF.SRC_IP, "192.168.1.0/24")
|
||||
func (rf *RuleFunc) IPInCIDR(ip, cidr string) bool {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
if parsedIP == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return ipNet.Contains(parsedIP)
|
||||
}
|
||||
|
||||
// IPInRanges 判断IP是否在多个范围中的任意一个(类似SQL的IN操作)
|
||||
// ip: 要检查的IP地址
|
||||
// ranges: IP范围列表,格式为 "startIP-endIP" 或 CIDR格式 "192.168.0.0/24"
|
||||
// 返回: true表示IP在任意一个范围内,false表示都不在
|
||||
// 使用示例: RF.IPInRanges(MF.SRC_IP, "172.16.0.0-172.20.255.254", "192.168.0.0/24")
|
||||
func (rf *RuleFunc) IPInRanges(ip string, ranges ...string) bool {
|
||||
for _, r := range ranges {
|
||||
// 检查是否是CIDR格式
|
||||
if strings.Contains(r, "/") {
|
||||
if rf.IPInCIDR(ip, r) {
|
||||
return true
|
||||
}
|
||||
} else if strings.Contains(r, "-") {
|
||||
// 范围格式: startIP-endIP
|
||||
parts := strings.Split(r, "-")
|
||||
if len(parts) == 2 {
|
||||
if rf.IPInRange(ip, strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 单个IP精确匹配
|
||||
if ip == strings.TrimSpace(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IPEquals 判断两个IP是否相等(支持IPv4和IPv6的标准化比较)
|
||||
// ip1: 第一个IP地址
|
||||
// ip2: 第二个IP地址
|
||||
// 返回: true表示相等,false表示不相等
|
||||
func (rf *RuleFunc) IPEquals(ip1, ip2 string) bool {
|
||||
parsedIP1 := net.ParseIP(ip1)
|
||||
parsedIP2 := net.ParseIP(ip2)
|
||||
|
||||
if parsedIP1 == nil || parsedIP2 == nil {
|
||||
return ip1 == ip2 // 如果无法解析,直接字符串比较
|
||||
}
|
||||
|
||||
return parsedIP1.Equal(parsedIP2)
|
||||
}
|
||||
|
||||
// ================== 字符串相关函数 ==================
|
||||
|
||||
// In 判断值是否在给定的列表中(类似SQL的IN操作)
|
||||
// value: 要检查的值
|
||||
// list: 可能的值列表
|
||||
// 返回: true表示值在列表中,false表示不在
|
||||
// 使用示例: RF.In(MF.METHOD, "GET", "POST", "PUT")
|
||||
func (rf *RuleFunc) In(value string, list ...string) bool {
|
||||
for _, item := range list {
|
||||
if value == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InIgnoreCase 判断值是否在给定的列表中(忽略大小写,类似SQL的IN操作)
|
||||
// value: 要检查的值
|
||||
// list: 可能的值列表
|
||||
// 返回: true表示值在列表中,false表示不在
|
||||
// 使用示例: RF.InIgnoreCase(MF.METHOD, "get", "post", "put")
|
||||
func (rf *RuleFunc) InIgnoreCase(value string, list ...string) bool {
|
||||
valueLower := strings.ToLower(value)
|
||||
for _, item := range list {
|
||||
if valueLower == strings.ToLower(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAny 判断字符串是否包含给定列表中的任意一个
|
||||
// value: 要检查的字符串
|
||||
// list: 要搜索的子串列表
|
||||
// 返回: true表示包含至少一个,false表示一个都不包含
|
||||
// 使用示例: RF.ContainsAny(MF.USER_AGENT, "bot", "spider", "crawler")
|
||||
func (rf *RuleFunc) ContainsAny(value string, list ...string) bool {
|
||||
for _, item := range list {
|
||||
if strings.Contains(value, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAnyIgnoreCase 判断字符串是否包含给定列表中的任意一个(忽略大小写)
|
||||
// value: 要检查的字符串
|
||||
// list: 要搜索的子串列表
|
||||
// 返回: true表示包含至少一个,false表示一个都不包含
|
||||
// 使用示例: RF.ContainsAnyIgnoreCase(MF.USER_AGENT, "Bot", "Spider", "Crawler")
|
||||
func (rf *RuleFunc) ContainsAnyIgnoreCase(value string, list ...string) bool {
|
||||
valueLower := strings.ToLower(value)
|
||||
for _, item := range list {
|
||||
if strings.Contains(valueLower, strings.ToLower(item)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsAll 判断字符串是否包含给定列表中的全部
|
||||
// value: 要检查的字符串
|
||||
// list: 要搜索的子串列表
|
||||
// 返回: true表示包含全部,false表示至少缺少一个
|
||||
// 使用示例: RF.ContainsAll(MF.URL, "/admin", ".php")
|
||||
func (rf *RuleFunc) ContainsAll(value string, list ...string) bool {
|
||||
for _, item := range list {
|
||||
if !strings.Contains(value, item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return len(list) > 0
|
||||
}
|
||||
|
||||
// StartsWithAny 判断字符串是否以给定列表中的任意一个开头
|
||||
// value: 要检查的字符串
|
||||
// list: 可能的前缀列表
|
||||
// 返回: true表示匹配至少一个前缀,false表示一个都不匹配
|
||||
// 使用示例: RF.StartsWithAny(MF.URL, "/admin", "/api", "/manage")
|
||||
func (rf *RuleFunc) StartsWithAny(value string, list ...string) bool {
|
||||
for _, item := range list {
|
||||
if strings.HasPrefix(value, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// EndsWithAny 判断字符串是否以给定列表中的任意一个结尾
|
||||
// value: 要检查的字符串
|
||||
// list: 可能的后缀列表
|
||||
// 返回: true表示匹配至少一个后缀,false表示一个都不匹配
|
||||
// 使用示例: RF.EndsWithAny(MF.URL, ".php", ".asp", ".jsp")
|
||||
func (rf *RuleFunc) EndsWithAny(value string, list ...string) bool {
|
||||
for _, item := range list {
|
||||
if strings.HasSuffix(value, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ================== 数值比较函数 ==================
|
||||
|
||||
// IntInRange 判断整数是否在指定范围内(包含边界)
|
||||
// value: 要检查的整数
|
||||
// min: 最小值
|
||||
// max: 最大值
|
||||
// 返回: true表示在范围内,false表示不在
|
||||
// 使用示例: RF.IntInRange(MF.STATUS_CODE, 400, 499)
|
||||
func (rf *RuleFunc) IntInRange(value, min, max int64) bool {
|
||||
return value >= min && value <= max
|
||||
}
|
||||
|
||||
// IntIn 判断整数是否在给定的列表中
|
||||
// value: 要检查的整数
|
||||
// list: 可能的值列表
|
||||
// 返回: true表示在列表中,false表示不在
|
||||
// 使用示例: RF.IntIn(MF.STATUS_CODE, 200, 201, 204)
|
||||
func (rf *RuleFunc) IntIn(value int64, list ...int64) bool {
|
||||
for _, item := range list {
|
||||
if value == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ================== 逻辑辅助函数 ==================
|
||||
|
||||
// Not 逻辑非
|
||||
// value: 布尔值
|
||||
// 返回: 取反后的值
|
||||
// 使用示例: RF.Not(RF.IPInRange(MF.SRC_IP, "192.168.0.0", "192.168.1.254"))
|
||||
func (rf *RuleFunc) Not(value bool) bool {
|
||||
return !value
|
||||
}
|
||||
|
||||
// IsEmpty 判断字符串是否为空
|
||||
// value: 要检查的字符串
|
||||
// 返回: true表示为空,false表示不为空
|
||||
func (rf *RuleFunc) IsEmpty(value string) bool {
|
||||
return value == ""
|
||||
}
|
||||
|
||||
// IsNotEmpty 判断字符串是否不为空
|
||||
// value: 要检查的字符串
|
||||
// 返回: true表示不为空,false表示为空
|
||||
func (rf *RuleFunc) IsNotEmpty(value string) bool {
|
||||
return value != ""
|
||||
}
|
||||
|
||||
// LengthBetween 判断字符串长度是否在指定范围内
|
||||
// value: 要检查的字符串
|
||||
// min: 最小长度
|
||||
// max: 最大长度
|
||||
// 返回: true表示长度在范围内,false表示不在
|
||||
func (rf *RuleFunc) LengthBetween(value string, min, max int64) bool {
|
||||
length := int64(len(value))
|
||||
return length >= min && length <= max
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package innerbean
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRuleFunc_IPInRange(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
startIP string
|
||||
endIP string
|
||||
expected bool
|
||||
}{
|
||||
{"IP在范围内-开始", "172.16.0.0", "172.16.0.0", "172.20.255.254", true},
|
||||
{"IP在范围内-中间", "172.18.0.1", "172.16.0.0", "172.20.255.254", true},
|
||||
{"IP在范围内-结束", "172.20.255.254", "172.16.0.0", "172.20.255.254", true},
|
||||
{"IP在范围外-小于", "172.15.255.255", "172.16.0.0", "172.20.255.254", false},
|
||||
{"IP在范围外-大于", "172.21.0.0", "172.16.0.0", "172.20.255.254", false},
|
||||
{"192.168网段-在范围内", "192.168.0.100", "192.168.0.0", "192.168.1.254", true},
|
||||
{"192.168网段-不在范围内", "192.168.2.0", "192.168.0.0", "192.168.1.254", false},
|
||||
{"无效IP", "invalid", "172.16.0.0", "172.20.255.254", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.IPInRange(tt.ip, tt.startIP, tt.endIP)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IPInRange(%s, %s, %s) = %v, want %v",
|
||||
tt.ip, tt.startIP, tt.endIP, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_IPInCIDR(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
cidr string
|
||||
expected bool
|
||||
}{
|
||||
{"IP在CIDR内", "192.168.1.100", "192.168.1.0/24", true},
|
||||
{"IP不在CIDR内", "192.168.2.100", "192.168.1.0/24", false},
|
||||
{"大网段-在范围内", "10.0.50.1", "10.0.0.0/8", true},
|
||||
{"大网段-不在范围内", "11.0.0.1", "10.0.0.0/8", false},
|
||||
{"无效IP", "invalid", "192.168.1.0/24", false},
|
||||
{"无效CIDR", "192.168.1.1", "invalid", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.IPInCIDR(tt.ip, tt.cidr)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IPInCIDR(%s, %s) = %v, want %v",
|
||||
tt.ip, tt.cidr, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_IPInRanges(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
ranges []string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"IP在第一个范围内",
|
||||
"172.18.0.1",
|
||||
[]string{"172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IP在第二个范围内",
|
||||
"192.168.0.100",
|
||||
[]string{"172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"IP不在任何范围内",
|
||||
"10.0.0.1",
|
||||
[]string{"172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"混合格式-CIDR和范围",
|
||||
"192.168.1.50",
|
||||
[]string{"10.0.0.0/8", "192.168.0.0-192.168.1.254"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"混合格式-在CIDR内",
|
||||
"10.0.0.50",
|
||||
[]string{"10.0.0.0/8", "192.168.0.0-192.168.1.254"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"单个IP匹配",
|
||||
"127.0.0.1",
|
||||
[]string{"127.0.0.1", "192.168.0.1"},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.IPInRanges(tt.ip, tt.ranges...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IPInRanges(%s, %v) = %v, want %v",
|
||||
tt.ip, tt.ranges, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_In(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"值在列表中", "GET", []string{"GET", "POST", "PUT"}, true},
|
||||
{"值不在列表中", "DELETE", []string{"GET", "POST", "PUT"}, false},
|
||||
{"空列表", "GET", []string{}, false},
|
||||
{"精确匹配", "get", []string{"GET", "POST"}, false}, // 大小写敏感
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.In(tt.value, tt.list...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("In(%s, %v) = %v, want %v",
|
||||
tt.value, tt.list, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_InIgnoreCase(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"忽略大小写匹配", "get", []string{"GET", "POST", "PUT"}, true},
|
||||
{"忽略大小写匹配2", "Get", []string{"get", "post"}, true},
|
||||
{"不匹配", "delete", []string{"GET", "POST"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.InIgnoreCase(tt.value, tt.list...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("InIgnoreCase(%s, %v) = %v, want %v",
|
||||
tt.value, tt.list, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_ContainsAny(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"包含其中一个", "Mozilla/5.0 Googlebot", []string{"bot", "spider", "crawler"}, true},
|
||||
{"包含多个", "Googlebot spider", []string{"bot", "spider"}, true},
|
||||
{"不包含任何", "Mozilla/5.0 Chrome", []string{"bot", "spider"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.ContainsAny(tt.value, tt.list...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ContainsAny(%s, %v) = %v, want %v",
|
||||
tt.value, tt.list, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_IntInRange(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value int64
|
||||
min int64
|
||||
max int64
|
||||
expected bool
|
||||
}{
|
||||
{"在范围内", 404, 400, 499, true},
|
||||
{"最小边界", 400, 400, 499, true},
|
||||
{"最大边界", 499, 400, 499, true},
|
||||
{"小于范围", 399, 400, 499, false},
|
||||
{"大于范围", 500, 400, 499, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.IntInRange(tt.value, tt.min, tt.max)
|
||||
if result != tt.expected {
|
||||
t.Errorf("IntInRange(%d, %d, %d) = %v, want %v",
|
||||
tt.value, tt.min, tt.max, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_StartsWithAny(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"以其中一个开头", "/admin/users", []string{"/admin", "/api", "/manage"}, true},
|
||||
{"不以任何一个开头", "/user/profile", []string{"/admin", "/api"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.StartsWithAny(tt.value, tt.list...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("StartsWithAny(%s, %v) = %v, want %v",
|
||||
tt.value, tt.list, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuleFunc_EndsWithAny(t *testing.T) {
|
||||
rf := NewRuleFunc()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
list []string
|
||||
expected bool
|
||||
}{
|
||||
{"以其中一个结尾", "/admin/login.php", []string{".php", ".asp", ".jsp"}, true},
|
||||
{"不以任何一个结尾", "/admin/login.html", []string{".php", ".asp"}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := rf.EndsWithAny(tt.value, tt.list...)
|
||||
if result != tt.expected {
|
||||
t.Errorf("EndsWithAny(%s, %v) = %v, want %v",
|
||||
tt.value, tt.list, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -6,11 +6,12 @@ import (
|
||||
"SamWaf/model"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/hyperjumptech/grule-rule-engine/ast"
|
||||
"github.com/hyperjumptech/grule-rule-engine/builder"
|
||||
"github.com/hyperjumptech/grule-rule-engine/engine"
|
||||
"github.com/hyperjumptech/grule-rule-engine/pkg"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// 规则帮助类
|
||||
@@ -80,6 +81,7 @@ func (rulehelper *RuleHelper) LoadRules(ruleconfig []model.Rules) (string, error
|
||||
func (rulehelper *RuleHelper) Exec(key string, ruleinfo *innerbean.WAF_REQUEST_FULL) error {
|
||||
dataCtx := ast.NewDataContext()
|
||||
dataCtx.Add(key, ruleinfo)
|
||||
dataCtx.Add("RF", innerbean.NewRuleFunc()) // 注册规则函数助手
|
||||
err := rulehelper.engine.Execute(dataCtx, rulehelper.KnowledgeBase)
|
||||
if err != nil {
|
||||
zlog.Error("Exec", err)
|
||||
@@ -97,6 +99,7 @@ func (rulehelper *RuleHelper) Match(key string, ruleinfo *innerbean.WebLog) ([]*
|
||||
}()
|
||||
dataCtx := ast.NewDataContext()
|
||||
dataCtx.Add(key, ruleinfo)
|
||||
dataCtx.Add("RF", innerbean.NewRuleFunc()) // 注册规则函数助手
|
||||
if rulehelper.KnowledgeBase == nil {
|
||||
return nil, errors.New("没有规则数据")
|
||||
}
|
||||
@@ -111,6 +114,10 @@ func (rulehelper *RuleHelper) CheckRuleAvailable(ruleText string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dataCtx.Add("RF", innerbean.NewRuleFunc()) // 注册规则函数助手
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
knowledgeLibrary := ast.NewKnowledgeLibrary()
|
||||
ruleBuilder := builder.NewRuleBuilder(knowledgeLibrary)
|
||||
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"SamWaf/innerbean"
|
||||
"SamWaf/model"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestUserRule_IPInRanges 测试用户原始规则的改写版本 - IPInRanges 方式
|
||||
func TestUserRule_IPInRanges(t *testing.T) {
|
||||
ruleHelper := &RuleHelper{}
|
||||
ruleHelper.InitRuleEngine()
|
||||
|
||||
// 用户改写后的规则 - 使用 IPInRanges(推荐方式)
|
||||
drls := `
|
||||
rule R835f9bf09867473dbe873027241db107 "允许特定内网网段访问" salience 10 {
|
||||
when
|
||||
RF.IPInRanges(MF.SRC_IP, "172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254") == true
|
||||
then
|
||||
Retract("R835f9bf09867473dbe873027241db107");
|
||||
}`
|
||||
|
||||
var ruleconfigs []model.Rules
|
||||
rule := model.Rules{
|
||||
HostCode: "",
|
||||
RuleCode: "R835f9bf09867473dbe873027241db107",
|
||||
RuleName: "允许特定内网网段访问",
|
||||
RuleContent: drls,
|
||||
RuleContentJSON: "",
|
||||
RuleVersionName: "1.0",
|
||||
RuleVersion: 1,
|
||||
IsPublicRule: 0,
|
||||
IsManualRule: 1,
|
||||
RuleStatus: 1,
|
||||
}
|
||||
ruleconfigs = append(ruleconfigs, rule)
|
||||
|
||||
_, err := ruleHelper.LoadRules(ruleconfigs)
|
||||
if err != nil {
|
||||
t.Errorf("加载规则失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
testCases := []struct {
|
||||
name string
|
||||
ip string
|
||||
expected bool // true 表示应该匹配规则(在范围内),false 表示不应该匹配
|
||||
}{
|
||||
{"172网段-起始IP", "172.16.0.0", true},
|
||||
{"172网段-中间IP", "172.18.100.50", true},
|
||||
{"172网段-结束IP", "172.20.255.254", true},
|
||||
{"172网段-超出范围", "172.21.0.0", false},
|
||||
{"192.168网段-起始IP", "192.168.0.0", true},
|
||||
{"192.168网段-中间IP", "192.168.0.100", true},
|
||||
{"192.168网段-结束IP", "192.168.1.254", true},
|
||||
{"192.168网段-超出范围", "192.168.2.0", false},
|
||||
{"外网IP", "8.8.8.8", false},
|
||||
{"本地IP", "127.0.0.1", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
webLog := &innerbean.WebLog{
|
||||
SRC_IP: tc.ip,
|
||||
}
|
||||
|
||||
ruleMatches, err := ruleHelper.Match("MF", webLog)
|
||||
if err != nil {
|
||||
t.Errorf("规则匹配错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
matched := len(ruleMatches) > 0
|
||||
if matched != tc.expected {
|
||||
t.Errorf("IP %s: 期望匹配=%v, 实际匹配=%v", tc.ip, tc.expected, matched)
|
||||
} else {
|
||||
t.Logf("✓ IP %s: 匹配结果正确 (匹配=%v)", tc.ip, matched)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUserRule_IPInRange 测试用户原始规则的改写版本 - IPInRange 方式
|
||||
func TestUserRule_IPInRange(t *testing.T) {
|
||||
ruleHelper := &RuleHelper{}
|
||||
ruleHelper.InitRuleEngine()
|
||||
|
||||
// 用户改写后的规则 - 使用多个 IPInRange
|
||||
drls := `
|
||||
rule R835f9bf09867473dbe873027241db107 "允许特定内网网段访问" salience 10 {
|
||||
when
|
||||
RF.IPInRange(MF.SRC_IP, "172.16.0.0", "172.20.255.254") == true ||
|
||||
RF.IPInRange(MF.SRC_IP, "192.168.0.0", "192.168.1.254") == true
|
||||
then
|
||||
Retract("R835f9bf09867473dbe873027241db107");
|
||||
}`
|
||||
|
||||
var ruleconfigs []model.Rules
|
||||
rule := model.Rules{
|
||||
HostCode: "",
|
||||
RuleCode: "R835f9bf09867473dbe873027241db107",
|
||||
RuleName: "允许特定内网网段访问",
|
||||
RuleContent: drls,
|
||||
RuleContentJSON: "",
|
||||
RuleVersionName: "1.0",
|
||||
RuleVersion: 1,
|
||||
IsPublicRule: 0,
|
||||
IsManualRule: 1,
|
||||
RuleStatus: 1,
|
||||
}
|
||||
ruleconfigs = append(ruleconfigs, rule)
|
||||
|
||||
_, err := ruleHelper.LoadRules(ruleconfigs)
|
||||
if err != nil {
|
||||
t.Errorf("加载规则失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 测试 172 网段内的 IP
|
||||
webLog := &innerbean.WebLog{
|
||||
SRC_IP: "172.18.0.1",
|
||||
}
|
||||
|
||||
ruleMatches, err := ruleHelper.Match("MF", webLog)
|
||||
if err != nil {
|
||||
t.Errorf("规则匹配错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(ruleMatches) == 0 {
|
||||
t.Errorf("172.18.0.1 应该匹配规则")
|
||||
} else {
|
||||
t.Logf("✓ 规则匹配成功: %s", ruleMatches[0].RuleDescription)
|
||||
}
|
||||
|
||||
// 测试外网 IP
|
||||
webLog2 := &innerbean.WebLog{
|
||||
SRC_IP: "8.8.8.8",
|
||||
}
|
||||
|
||||
ruleMatches2, err := ruleHelper.Match("MF", webLog2)
|
||||
if err != nil {
|
||||
t.Errorf("规则匹配错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(ruleMatches2) > 0 {
|
||||
t.Errorf("8.8.8.8 不应该匹配规则")
|
||||
} else {
|
||||
t.Logf("✓ 外网IP正确未匹配")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUserRule_CIDR 测试用户规则 - CIDR 格式
|
||||
func TestUserRule_CIDR(t *testing.T) {
|
||||
ruleHelper := &RuleHelper{}
|
||||
ruleHelper.InitRuleEngine()
|
||||
|
||||
// 使用 CIDR 格式
|
||||
drls := `
|
||||
rule R835f9bf09867473dbe873027241db108 "允许CIDR网段访问" salience 10 {
|
||||
when
|
||||
RF.IPInRanges(MF.SRC_IP, "192.168.1.0/24", "10.0.0.0/8") == true
|
||||
then
|
||||
Retract("R835f9bf09867473dbe873027241db108");
|
||||
}`
|
||||
|
||||
var ruleconfigs []model.Rules
|
||||
rule := model.Rules{
|
||||
HostCode: "",
|
||||
RuleCode: "R835f9bf09867473dbe873027241db108",
|
||||
RuleName: "允许CIDR网段访问",
|
||||
RuleContent: drls,
|
||||
RuleContentJSON: "",
|
||||
RuleVersionName: "1.0",
|
||||
RuleVersion: 1,
|
||||
IsPublicRule: 0,
|
||||
IsManualRule: 1,
|
||||
RuleStatus: 1,
|
||||
}
|
||||
ruleconfigs = append(ruleconfigs, rule)
|
||||
|
||||
_, err := ruleHelper.LoadRules(ruleconfigs)
|
||||
if err != nil {
|
||||
t.Errorf("加载规则失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 测试用例
|
||||
testCases := []struct {
|
||||
name string
|
||||
ip string
|
||||
expected bool
|
||||
}{
|
||||
{"192.168.1网段", "192.168.1.100", true},
|
||||
{"10网段", "10.0.0.1", true},
|
||||
{"10网段大范围", "10.255.255.255", true},
|
||||
{"不在范围内", "192.168.2.1", false},
|
||||
{"不在范围内2", "11.0.0.1", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
webLog := &innerbean.WebLog{
|
||||
SRC_IP: tc.ip,
|
||||
}
|
||||
|
||||
ruleMatches, err := ruleHelper.Match("MF", webLog)
|
||||
if err != nil {
|
||||
t.Errorf("规则匹配错误: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
matched := len(ruleMatches) > 0
|
||||
if matched != tc.expected {
|
||||
t.Errorf("IP %s: 期望匹配=%v, 实际匹配=%v", tc.ip, tc.expected, matched)
|
||||
} else {
|
||||
t.Logf("✓ IP %s: 匹配结果正确 (匹配=%v)", tc.ip, matched)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestUserRule_ReversedLogic 测试用户规则 - 反转逻辑(禁止内网访问)
|
||||
func TestUserRule_ReversedLogic(t *testing.T) {
|
||||
ruleHelper := &RuleHelper{}
|
||||
ruleHelper.InitRuleEngine()
|
||||
|
||||
// 如果用户想要禁止这些内网IP访问
|
||||
drls := `
|
||||
rule R835f9bf09867473dbe873027241db107 "禁止特定内网网段访问" salience 10 {
|
||||
when
|
||||
RF.IPInRanges(MF.SRC_IP, "172.16.0.0-172.20.255.254", "192.168.0.0-192.168.1.254") == false
|
||||
then
|
||||
Retract("R835f9bf09867473dbe873027241db107");
|
||||
}`
|
||||
|
||||
var ruleconfigs []model.Rules
|
||||
rule := model.Rules{
|
||||
HostCode: "",
|
||||
RuleCode: "R835f9bf09867473dbe873027241db107",
|
||||
RuleName: "禁止特定内网网段访问",
|
||||
RuleContent: drls,
|
||||
RuleContentJSON: "",
|
||||
RuleVersionName: "1.0",
|
||||
RuleVersion: 1,
|
||||
IsPublicRule: 0,
|
||||
IsManualRule: 1,
|
||||
RuleStatus: 1,
|
||||
}
|
||||
ruleconfigs = append(ruleconfigs, rule)
|
||||
|
||||
_, err := ruleHelper.LoadRules(ruleconfigs)
|
||||
if err != nil {
|
||||
t.Errorf("加载规则失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 内网IP不应该匹配(因为我们是禁止内网)
|
||||
webLog1 := &innerbean.WebLog{
|
||||
SRC_IP: "172.18.0.1",
|
||||
}
|
||||
|
||||
ruleMatches1, _ := ruleHelper.Match("MF", webLog1)
|
||||
if len(ruleMatches1) > 0 {
|
||||
t.Errorf("内网IP 172.18.0.1 不应该匹配规则(应该被排除)")
|
||||
} else {
|
||||
t.Logf("✓ 内网IP正确未匹配(被禁止)")
|
||||
}
|
||||
|
||||
// 外网IP应该匹配
|
||||
webLog2 := &innerbean.WebLog{
|
||||
SRC_IP: "8.8.8.8",
|
||||
}
|
||||
|
||||
ruleMatches2, _ := ruleHelper.Match("MF", webLog2)
|
||||
if len(ruleMatches2) == 0 {
|
||||
t.Errorf("外网IP 8.8.8.8 应该匹配规则")
|
||||
} else {
|
||||
t.Logf("✓ 外网IP正确匹配")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user