Files
logs/internal/logic/controllers/rule_validation.go
2026-09-21 10:46:34 +08:00

137 lines
4.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controllers
import (
"encoding/json"
"fmt"
"net"
"regexp"
"strings"
"git.apinb.com/ops/logs/internal/ingest"
"git.apinb.com/ops/logs/internal/models"
)
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
}
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
}