165 lines
4.0 KiB
Go
165 lines
4.0 KiB
Go
|
|
package rules
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestRuleLoader_ValidYaml 测试加载有效YAML
|
||
|
|
func TestRuleLoader_ValidYaml(t *testing.T) {
|
||
|
|
// 创建临时有效YAML文件
|
||
|
|
tmpfile, err := os.CreateTemp("", "valid_rule_*.yaml")
|
||
|
|
require.NoError(t, err)
|
||
|
|
defer os.Remove(tmpfile.Name())
|
||
|
|
|
||
|
|
validYAML := `
|
||
|
|
rules:
|
||
|
|
- id: "CRED-EXPOSE-RESPONSE"
|
||
|
|
name: "响应体凭证泄露检测"
|
||
|
|
description: "检测 API 响应中是否包含可复用的供应商凭证片段"
|
||
|
|
severity: "P0"
|
||
|
|
matchers:
|
||
|
|
- type: "regex_match"
|
||
|
|
pattern: "(sk-|ak-|api_key|secret|token).*[a-zA-Z0-9]{20,}"
|
||
|
|
target: "response_body"
|
||
|
|
scope: "all"
|
||
|
|
action:
|
||
|
|
primary: "block"
|
||
|
|
secondary: "alert"
|
||
|
|
audit:
|
||
|
|
event_name: "CRED-EXPOSE-RESPONSE"
|
||
|
|
event_category: "CRED"
|
||
|
|
event_sub_category: "EXPOSE"
|
||
|
|
`
|
||
|
|
_, err = tmpfile.WriteString(validYAML)
|
||
|
|
require.NoError(t, err)
|
||
|
|
tmpfile.Close()
|
||
|
|
|
||
|
|
// 测试加载
|
||
|
|
loader := NewRuleLoader()
|
||
|
|
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||
|
|
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.NotNil(t, rules)
|
||
|
|
assert.Len(t, rules, 1)
|
||
|
|
|
||
|
|
rule := rules[0]
|
||
|
|
assert.Equal(t, "CRED-EXPOSE-RESPONSE", rule.ID)
|
||
|
|
assert.Equal(t, "P0", rule.Severity)
|
||
|
|
assert.Equal(t, "block", rule.Action.Primary)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRuleLoader_InvalidYaml 测试加载无效YAML
|
||
|
|
func TestRuleLoader_InvalidYaml(t *testing.T) {
|
||
|
|
// 创建临时无效YAML文件
|
||
|
|
tmpfile, err := os.CreateTemp("", "invalid_rule_*.yaml")
|
||
|
|
require.NoError(t, err)
|
||
|
|
defer os.Remove(tmpfile.Name())
|
||
|
|
|
||
|
|
invalidYAML := `
|
||
|
|
rules:
|
||
|
|
- id: "CRED-EXPOSE-RESPONSE"
|
||
|
|
name: "响应体凭证泄露检测"
|
||
|
|
severity: "P0"
|
||
|
|
# 缺少必需的matchers字段
|
||
|
|
action:
|
||
|
|
primary: "block"
|
||
|
|
`
|
||
|
|
_, err = tmpfile.WriteString(invalidYAML)
|
||
|
|
require.NoError(t, err)
|
||
|
|
tmpfile.Close()
|
||
|
|
|
||
|
|
// 测试加载
|
||
|
|
loader := NewRuleLoader()
|
||
|
|
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||
|
|
|
||
|
|
assert.Error(t, err)
|
||
|
|
assert.Nil(t, rules)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRuleLoader_MissingFields 测试缺少必需字段
|
||
|
|
func TestRuleLoader_MissingFields(t *testing.T) {
|
||
|
|
// 创建缺少必需字段的YAML
|
||
|
|
tmpfile, err := os.CreateTemp("", "missing_fields_*.yaml")
|
||
|
|
require.NoError(t, err)
|
||
|
|
defer os.Remove(tmpfile.Name())
|
||
|
|
|
||
|
|
// 缺少 id 字段
|
||
|
|
missingIDYAML := `
|
||
|
|
rules:
|
||
|
|
- name: "响应体凭证泄露检测"
|
||
|
|
severity: "P0"
|
||
|
|
matchers:
|
||
|
|
- type: "regex_match"
|
||
|
|
action:
|
||
|
|
primary: "block"
|
||
|
|
`
|
||
|
|
_, err = tmpfile.WriteString(missingIDYAML)
|
||
|
|
require.NoError(t, err)
|
||
|
|
tmpfile.Close()
|
||
|
|
|
||
|
|
loader := NewRuleLoader()
|
||
|
|
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||
|
|
|
||
|
|
assert.Error(t, err)
|
||
|
|
assert.Nil(t, rules)
|
||
|
|
assert.Contains(t, err.Error(), "missing required field: id")
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRuleLoader_FileNotFound 测试文件不存在
|
||
|
|
func TestRuleLoader_FileNotFound(t *testing.T) {
|
||
|
|
loader := NewRuleLoader()
|
||
|
|
rules, err := loader.LoadFromFile("/nonexistent/path/rules.yaml")
|
||
|
|
|
||
|
|
assert.Error(t, err)
|
||
|
|
assert.Nil(t, rules)
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRuleLoader_ValidateRuleFormat 测试规则格式验证
|
||
|
|
func TestRuleLoader_ValidateRuleFormat(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
ruleID string
|
||
|
|
valid bool
|
||
|
|
}{
|
||
|
|
{"标准格式", "CRED-EXPOSE-RESPONSE", true},
|
||
|
|
{"带Detail格式", "CRED-EXPOSE-RESPONSE-DETAIL", true},
|
||
|
|
{"双连字符", "CRED--EXPOSE-RESPONSE", false},
|
||
|
|
{"小写字母", "cred-expose-response", false},
|
||
|
|
{"单字符Category", "C-EXPOSE-RESPONSE", false},
|
||
|
|
}
|
||
|
|
|
||
|
|
loader := NewRuleLoader()
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
valid := loader.ValidateRuleID(tt.ruleID)
|
||
|
|
assert.Equal(t, tt.valid, valid)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRuleLoader_EmptyRules 测试空规则列表
|
||
|
|
func TestRuleLoader_EmptyRules(t *testing.T) {
|
||
|
|
tmpfile, err := os.CreateTemp("", "empty_rules_*.yaml")
|
||
|
|
require.NoError(t, err)
|
||
|
|
defer os.Remove(tmpfile.Name())
|
||
|
|
|
||
|
|
emptyYAML := `
|
||
|
|
rules: []
|
||
|
|
`
|
||
|
|
_, err = tmpfile.WriteString(emptyYAML)
|
||
|
|
require.NoError(t, err)
|
||
|
|
tmpfile.Close()
|
||
|
|
|
||
|
|
loader := NewRuleLoader()
|
||
|
|
rules, err := loader.LoadFromFile(tmpfile.Name())
|
||
|
|
|
||
|
|
assert.NoError(t, err)
|
||
|
|
assert.NotNil(t, rules)
|
||
|
|
assert.Len(t, rules, 0)
|
||
|
|
}
|