package servewaf // The heuristic rule_files. It reads the rules of the rule files, which // SWWAF_RULES_DIR and SWWAF_RULES_ENABLED name. import ( "context" "time" "sneak.berlin/go/smallwebwaf/internal/bans" "sneak.berlin/go/smallwebwaf/internal/requestlog" "sneak.berlin/go/smallwebwaf/internal/rules" ) // ruleFiles checks the request against the rules of the rule files at now, // notes the ids of those it matches in the log line, and refuses the request // when the last rule it matches refuses it: with ActionRuleBlocked for a block // rule, and ActionBanned for a ban rule. A ban rule bans the client's netblock // for a clear sign of attack, or in observe mode raises the alert for the ban // it would have made. Either rule's match is noted as an offence, for the // client's history. Its figures are the rule's id and action. type ruleFiles struct{} func (ruleFiles) name() string { return "rule_files" } func (ruleFiles) check(_ context.Context, rq *request, now time.Time) *verdict { matched := rq.h.rules.Match(rq.in) for _, rule := range matched { rq.line.RuleIDs = append(rq.line.RuleIDs, rule.ID) rq.h.metrics.RuleMatched(rule.ID, rule.Action) } if len(matched) == 0 { return nil } // Only the last rule matched can refuse the request. last := matched[len(matched)-1] figures := map[string]any{"rule_id": last.ID, "rule_action": last.Action} switch last.Action { case rules.ActionBlock: rq.ruleBlocked = true return &verdict{action: requestlog.ActionRuleBlocked, figures: figures} case rules.ActionBan: rq.attack = true rq.banForAttack(now, bans.Notes{RuleID: last.ID, Target: last.Target}) return &verdict{action: requestlog.ActionBanned, figures: figures} default: return nil } }