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
134 lines
4.0 KiB
Go
134 lines
4.0 KiB
Go
package servewaf
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// To add a heuristic, write one file with a type that implements heuristic,
|
|
// and add one line for it to one of the lists below. Its settings go in
|
|
// config.Config, and the comment at the top of its file names them.
|
|
|
|
// heuristic is one of the checks that decide what servewaf does with a request.
|
|
type heuristic interface {
|
|
// name is the heuristic's name, which the log line gives.
|
|
name() string
|
|
// check looks at the request at now, and returns nil to let it through.
|
|
// ctx is the request's own context.
|
|
check(ctx context.Context, rq *request, now time.Time) *verdict
|
|
}
|
|
|
|
// verdict is a heuristic refusing a request, or banning its client: the action
|
|
// the log line names, one of requestlog's Action constants, and the figures the
|
|
// heuristic acted on, by name, such as the count and the limit.
|
|
type verdict struct {
|
|
action string
|
|
figures map[string]any
|
|
}
|
|
|
|
// The names of the figures that more than one heuristic gives.
|
|
const (
|
|
figureWindow = "window"
|
|
figureCount = "count"
|
|
figureLimit = "limit"
|
|
figureScore = "score"
|
|
figureNetblock = "netblock"
|
|
)
|
|
|
|
// The lists of heuristics, each in the order its heuristics run. A client in
|
|
// SWWAF_ALLOW_NETS skips them all.
|
|
|
|
// beforeLookup are the heuristics that run before the client's AS number and
|
|
// country are looked up, so that a client they refuse is not looked up.
|
|
func beforeLookup() []heuristic {
|
|
return []heuristic{
|
|
denyNets{},
|
|
activeBan{},
|
|
}
|
|
}
|
|
|
|
// afterLookup are the heuristics that run once the lookup has been made.
|
|
func afterLookup() []heuristic {
|
|
return []heuristic{
|
|
countries{},
|
|
blocklists{},
|
|
crowdSec{},
|
|
dnsbl{},
|
|
abuseIPDB{},
|
|
rateLimits{},
|
|
trapPaths{},
|
|
ruleFiles{},
|
|
headerLimits{},
|
|
coreRuleSet{},
|
|
}
|
|
}
|
|
|
|
// afterAnswer are the heuristics that run on a request once servewaf has
|
|
// answered it, before its decision line is written. They ban the client, and do
|
|
// not change what the request got: their verdict's action is not used.
|
|
func afterAnswer() []heuristic {
|
|
return []heuristic{
|
|
refusalLimit{},
|
|
}
|
|
}
|
|
|
|
// onReport are the heuristics that run on an allowed request when rproxy's
|
|
// report of it comes, before its report line is written. Like afterAnswer, they
|
|
// only ban.
|
|
func onReport() []heuristic {
|
|
return []heuristic{
|
|
byteLimits{},
|
|
}
|
|
}
|
|
|
|
// runAfterAnswer runs the heuristics of afterAnswer on the request, at the time
|
|
// it is called.
|
|
func (rq *request) runAfterAnswer(ctx context.Context) {
|
|
rq.runBanningHeuristics(ctx, afterAnswer())
|
|
}
|
|
|
|
// runHeuristics runs list, beforeLookup or afterLookup, on the request at now,
|
|
// and returns the first verdict, or nil when none gives one. The log line's
|
|
// heuristic and heuristic_figures name the heuristic that gave it, and give its
|
|
// figures. ctx is the request's own context.
|
|
func (rq *request) runHeuristics(
|
|
ctx context.Context, list []heuristic, now time.Time,
|
|
) *verdict {
|
|
name, v := rq.firstVerdict(ctx, list, now)
|
|
if v != nil {
|
|
rq.line.Heuristic = name
|
|
rq.line.HeuristicFigures = v.figures
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
// runBanningHeuristics runs list, afterAnswer or onReport, on the request at
|
|
// the time it is called. The log line's after_answer_heuristic and
|
|
// after_answer_heuristic_figures name the heuristic that gave a verdict, and
|
|
// give its figures, so that heuristic and heuristic_figures still name the one
|
|
// that refused the request. ctx is the request's own context.
|
|
func (rq *request) runBanningHeuristics(ctx context.Context, list []heuristic) {
|
|
name, v := rq.firstVerdict(ctx, list, rq.h.now())
|
|
if v != nil {
|
|
rq.line.AfterAnswerHeuristic = name
|
|
rq.line.AfterAnswerHeuristicFigures = v.figures
|
|
}
|
|
}
|
|
|
|
// firstVerdict runs list on the request at now, in order, until a heuristic
|
|
// gives a verdict, and returns that heuristic's name and its verdict, or "" and
|
|
// nil when none gives one. ctx is the request's own context.
|
|
func (rq *request) firstVerdict(
|
|
ctx context.Context, list []heuristic, now time.Time,
|
|
) (string, *verdict) {
|
|
for _, each := range list {
|
|
v := each.check(ctx, rq, now)
|
|
if v != nil {
|
|
return each.name(), v
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|