2026-04-02 23:35:53 +08:00
|
|
|
|
package rules
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
"regexp"
|
|
|
|
|
|
|
|
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Rule 定义合规规则结构
|
|
|
|
|
|
type Rule struct {
|
|
|
|
|
|
ID string `yaml:"id"`
|
|
|
|
|
|
Name string `yaml:"name"`
|
|
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
|
|
Severity string `yaml:"severity"`
|
|
|
|
|
|
Matchers []Matcher `yaml:"matchers"`
|
|
|
|
|
|
Action Action `yaml:"action"`
|
|
|
|
|
|
Audit Audit `yaml:"audit"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Matcher 定义规则匹配器
|
|
|
|
|
|
type Matcher struct {
|
|
|
|
|
|
Type string `yaml:"type"`
|
|
|
|
|
|
Pattern string `yaml:"pattern"`
|
|
|
|
|
|
Target string `yaml:"target"`
|
|
|
|
|
|
Scope string `yaml:"scope"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Action 定义规则动作
|
|
|
|
|
|
type Action struct {
|
|
|
|
|
|
Primary string `yaml:"primary"`
|
|
|
|
|
|
Secondary string `yaml:"secondary"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Audit 定义审计配置
|
|
|
|
|
|
type Audit struct {
|
|
|
|
|
|
EventName string `yaml:"event_name"`
|
|
|
|
|
|
EventCategory string `yaml:"event_category"`
|
|
|
|
|
|
EventSubCategory string `yaml:"event_sub_category"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RulesConfig YAML规则配置结构
|
|
|
|
|
|
type RulesConfig struct {
|
|
|
|
|
|
Rules []Rule `yaml:"rules"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RuleLoader 规则加载器
|
|
|
|
|
|
type RuleLoader struct {
|
|
|
|
|
|
ruleIDPattern *regexp.Regexp
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewRuleLoader 创建新的规则加载器
|
|
|
|
|
|
func NewRuleLoader() *RuleLoader {
|
|
|
|
|
|
// 规则ID格式: {Category}-{SubCategory}[-{Detail}]
|
|
|
|
|
|
// Category: 大写字母, 2-4字符
|
|
|
|
|
|
// SubCategory: 大写字母, 2-10字符
|
|
|
|
|
|
// Detail: 可选, 大写字母+数字+连字符, 1-20字符
|
2026-04-03 09:58:13 +08:00
|
|
|
|
pattern, err := regexp.Compile(`^[A-Z]{2,4}-[A-Z]{2,10}(-[A-Z0-9-]{1,20})?$`)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果正则表达式无效,使用一个永远不匹配的pattern作为fallback
|
|
|
|
|
|
pattern = regexp.MustCompile("a^") // 永远不匹配的无效正则
|
|
|
|
|
|
}
|
2026-04-02 23:35:53 +08:00
|
|
|
|
|
|
|
|
|
|
return &RuleLoader{
|
|
|
|
|
|
ruleIDPattern: pattern,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LoadFromFile 从YAML文件加载规则
|
|
|
|
|
|
func (l *RuleLoader) LoadFromFile(filePath string) ([]Rule, error) {
|
|
|
|
|
|
// 检查文件是否存在
|
|
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
|
|
|
|
return nil, fmt.Errorf("file not found: %s", filePath)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 读取文件内容
|
|
|
|
|
|
data, err := os.ReadFile(filePath)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("failed to read file: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析YAML
|
|
|
|
|
|
var config RulesConfig
|
|
|
|
|
|
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("failed to parse YAML: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证规则
|
|
|
|
|
|
for _, rule := range config.Rules {
|
|
|
|
|
|
if err := l.validateRule(rule); err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return config.Rules, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// validateRule 验证规则完整性
|
|
|
|
|
|
func (l *RuleLoader) validateRule(rule Rule) error {
|
|
|
|
|
|
// 检查必需字段
|
|
|
|
|
|
if rule.ID == "" {
|
|
|
|
|
|
return fmt.Errorf("missing required field: id")
|
|
|
|
|
|
}
|
|
|
|
|
|
if rule.Name == "" {
|
|
|
|
|
|
return fmt.Errorf("missing required field: name for rule %s", rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if rule.Severity == "" {
|
|
|
|
|
|
return fmt.Errorf("missing required field: severity for rule %s", rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(rule.Matchers) == 0 {
|
|
|
|
|
|
return fmt.Errorf("missing required field: matchers for rule %s", rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if rule.Action.Primary == "" {
|
|
|
|
|
|
return fmt.Errorf("missing required field: action.primary for rule %s", rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证规则ID格式
|
|
|
|
|
|
if !l.ValidateRuleID(rule.ID) {
|
|
|
|
|
|
return fmt.Errorf("invalid rule ID format: %s (expected format: {Category}-{SubCategory}[-{Detail}])", rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证每个匹配器
|
|
|
|
|
|
for i, matcher := range rule.Matchers {
|
|
|
|
|
|
if matcher.Type == "" {
|
|
|
|
|
|
return fmt.Errorf("missing required field: matchers[%d].type for rule %s", i, rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
if matcher.Pattern == "" {
|
|
|
|
|
|
return fmt.Errorf("missing required field: matchers[%d].pattern for rule %s", i, rule.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 验证正则表达式是否有效
|
|
|
|
|
|
if _, err := regexp.Compile(matcher.Pattern); err != nil {
|
|
|
|
|
|
return fmt.Errorf("invalid regex pattern in matchers[%d] for rule %s: %w", i, rule.ID, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ValidateRuleID 验证规则ID格式
|
|
|
|
|
|
func (l *RuleLoader) ValidateRuleID(ruleID string) bool {
|
|
|
|
|
|
return l.ruleIDPattern.MatchString(ruleID)
|
|
|
|
|
|
}
|