package engine import ( "fmt" "net" "strings" "git.dern.ovh/infrastructure/caddy-opnsense-blocker/internal/config" "git.dern.ovh/infrastructure/caddy-opnsense-blocker/internal/model" ) type Evaluator struct{} func NewEvaluator() *Evaluator { return &Evaluator{} } func (e *Evaluator) Evaluate(record model.AccessLogRecord, profile config.ProfileConfig, override model.ManualOverride) model.Decision { switch override { case model.ManualOverrideForceAllow: return model.Decision{Action: model.DecisionActionAllow, Reasons: []string{"manual_override_force_allow"}} case model.ManualOverrideForceBlock: return model.Decision{Action: model.DecisionActionBlock, Reasons: []string{"manual_override_force_block"}} } if record.Status < profile.MinStatus || record.Status > profile.MaxStatus { return model.Decision{Action: model.DecisionActionNone} } ip := net.ParseIP(record.ClientIP) if ip == nil { return model.Decision{Action: model.DecisionActionReview, Reasons: []string{"invalid_client_ip"}} } if profile.IsExcluded(ip) { return model.Decision{Action: model.DecisionActionAllow, Reasons: []string{"excluded_cidr"}} } if rule, ok := profile.MatchKnownAgent(ip, record.UserAgent); ok { if rule.Decision == "allow" { return model.Decision{Action: model.DecisionActionAllow, Reasons: []string{fmt.Sprintf("known_agent_allow:%s", rule.Name)}} } return blockDecision(profile.AutoBlock, []string{fmt.Sprintf("known_agent_deny:%s", rule.Name)}) } blockReasons := make([]string, 0, 3) for _, prefix := range profile.SuspiciousPrefixes() { if strings.HasPrefix(record.Path, prefix) { blockReasons = append(blockReasons, fmt.Sprintf("suspicious_path_prefix:%s", prefix)) break } } if profile.BlockUnexpectedPosts && strings.EqualFold(record.Method, "POST") && !profile.IsAllowedPostPath(record.Path) { blockReasons = append(blockReasons, "unexpected_post") } if profile.BlockPHPPaths && strings.HasSuffix(record.Path, ".php") { blockReasons = append(blockReasons, "php_path") } return blockDecision(profile.AutoBlock, blockReasons) } func blockDecision(autoBlock bool, reasons []string) model.Decision { if len(reasons) == 0 { return model.Decision{Action: model.DecisionActionNone} } if autoBlock { return model.Decision{Action: model.DecisionActionBlock, Reasons: reasons} } return model.Decision{Action: model.DecisionActionReview, Reasons: reasons} }