Files
logs/internal/logic/controllers/rule_validation.go

137 lines
4.9 KiB
Go
Raw Normal View History

package controllers
import (
"encoding/json"
"fmt"
2026-09-21 10:46:34 +08:00
"net"
"regexp"
"strings"
2026-09-21 10:46:34 +08:00
"git.apinb.com/ops/logs/internal/ingest"
"git.apinb.com/ops/logs/internal/models"
)
2026-09-21 10:46:34 +08:00
var trapOIDPattern = regexp.MustCompile(`^\d+(?:\.\d+)*$`)
func validateSyslogRule(rule *models.SyslogRule) error {
regexFields := []struct {
name string
pattern string
}{
{name: "keyword_regex", pattern: rule.KeywordRegex},
{name: "message_regex", pattern: rule.MessageRegex},
{name: "recovery_match_regex", pattern: rule.RecoveryMatchRegex},
{name: "resource_uid_extract_regex", pattern: rule.ResourceUIDExtractRegex},
}
for _, field := range regexFields {
if err := validateOptionalRegex(field.name, field.pattern); err != nil {
return err
}
}
if raw := strings.TrimSpace(rule.SeverityMappingJSON); raw != "" {
var mapping map[string]any
if err := json.Unmarshal([]byte(raw), &mapping); err != nil {
return fmt.Errorf("severity_mapping_json 必须是字符串到字符串的 JSON 对象:%v;请修正 severity_mapping_json 后重试", err)
}
if mapping == nil {
return fmt.Errorf("severity_mapping_json 必须是字符串到字符串的 JSON 对象,不能是 null;请改为 JSON 对象后重试")
}
for pattern, severity := range mapping {
if _, ok := severity.(string); !ok {
return fmt.Errorf("severity_mapping_json 的映射键 %q 对应值必须是字符串;请修正该映射值后重试", pattern)
}
if _, err := regexp.Compile(pattern); err != nil {
return fmt.Errorf("severity_mapping_json 的映射键 %q 不是有效正则表达式:%v;请修正该映射键后重试", pattern, err)
}
}
}
if strings.TrimSpace(rule.DeviceNameContains) == "" &&
strings.TrimSpace(rule.SourceMatch) == "" &&
strings.TrimSpace(rule.KeywordRegex) == "" &&
strings.TrimSpace(rule.MessageRegex) == "" {
return fmt.Errorf("Syslog 规则的匹配条件全部为空,运行时永远不会命中;请至少填写 device_name_contains、source_match、keyword_regex、message_regex 中的一项")
}
if strings.TrimSpace(rule.RecoveryMatchRegex) != "" && strings.TrimSpace(rule.LifecycleKey) == "" {
return fmt.Errorf("配置 recovery_match_regex 时 lifecycle_key 不能为空")
}
return nil
}
func validateTrapRule(rule *models.TrapRule) error {
if err := validateOptionalRegex("varbind_match_regex", rule.VarbindMatchRegex); err != nil {
return err
}
if err := validateOptionalRegex("recovery_match_regex", rule.RecoveryMatchRegex); err != nil {
return err
}
if strings.TrimSpace(rule.OIDPrefix) == "" && strings.TrimSpace(rule.VarbindMatchRegex) == "" {
return fmt.Errorf("Trap 规则的匹配条件全部为空,运行时永远不会命中;请至少填写 oid_prefix、varbind_match_regex 中的一项")
}
if strings.TrimSpace(rule.RecoveryMatchRegex) != "" && strings.TrimSpace(rule.LifecycleKey) == "" {
return fmt.Errorf("配置 recovery_match_regex 时 lifecycle_key 不能为空")
}
return nil
}
2026-09-21 10:46:34 +08:00
func validateTrapShield(shield *models.TrapShield) error {
shield.Name = strings.TrimSpace(shield.Name)
shield.SourceIPCIDR = strings.TrimSpace(shield.SourceIPCIDR)
shield.InterfaceHint = strings.TrimSpace(shield.InterfaceHint)
shield.TimeWindowsJSON = strings.TrimSpace(shield.TimeWindowsJSON)
if shield.Name == "" {
return fmt.Errorf("Trap 屏蔽规则名称不能为空")
}
if shield.SourceIPCIDR != "" {
if strings.Contains(shield.SourceIPCIDR, "/") {
if _, _, err := net.ParseCIDR(shield.SourceIPCIDR); err != nil {
return fmt.Errorf("源 IP / CIDR 格式无效:%v", err)
}
} else if net.ParseIP(shield.SourceIPCIDR) == nil {
return fmt.Errorf("源 IP / CIDR 格式无效")
}
}
normalizedPrefixes := make([]string, 0, len(shield.OIDPrefixes))
seenPrefixes := make(map[string]struct{}, len(shield.OIDPrefixes))
for _, raw := range shield.OIDPrefixes {
prefix := normalizeTrapOIDInput(raw)
if prefix == "" {
continue
}
if !trapOIDPattern.MatchString(prefix) {
return fmt.Errorf("Trap OID %q 格式无效", raw)
}
if _, exists := seenPrefixes[prefix]; exists {
continue
}
seenPrefixes[prefix] = struct{}{}
normalizedPrefixes = append(normalizedPrefixes, prefix)
}
shield.OIDPrefixes = normalizedPrefixes
if shield.SourceIPCIDR == "" && len(shield.OIDPrefixes) == 0 && shield.InterfaceHint == "" {
return fmt.Errorf("Trap 屏蔽范围不能为空;请至少设置源 IP / CIDR、Trap 类型或接口提示中的一项")
}
if err := ingest.ValidateTimeWindowsJSON(shield.TimeWindowsJSON); err != nil {
return err
}
return nil
}
func normalizeTrapOIDInput(value string) string {
return strings.Trim(strings.TrimSpace(value), ".")
}
func validateOptionalRegex(field, pattern string) error {
pattern = strings.TrimSpace(pattern)
if pattern == "" {
return nil
}
if _, err := regexp.Compile(pattern); err != nil {
return fmt.Errorf("%s 不是有效正则表达式:%v;请修正 %s 后重试", field, err, field)
}
return nil
}