check / check (push) Waiting to run
Each check servewaf runs on a request is a type in its own file behind one interface, heuristic, and is registered in one of four ordered lists in heuristics.go. Order and behaviour are unchanged. Every refusal now logs heuristic, the name of the heuristic that made it, and heuristic_figures, the figures it acted on. A ban by refusal_limit or byte_limits after the answer logs after_answer_heuristic and after_answer_heuristic_figures instead, so a line can name both. All four are in docs/request-log.md, and SPEC.md names each heuristic at its step. Model: opus-5-5
56 lines
1.7 KiB
Go
56 lines
1.7 KiB
Go
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
|
|
}
|
|
}
|