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 ( figureLimit = "limit" figureScore = "score" ) // 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.runHeuristics(ctx, afterAnswer(), rq.h.now()) } // runHeuristics runs list on the request at now, in order, and returns the // first verdict, or nil when none gives one. The log line names the heuristic // that gave it, and its figures. ctx is the request's own context. func (rq *request) runHeuristics( ctx context.Context, list []heuristic, now time.Time, ) *verdict { for _, each := range list { v := each.check(ctx, rq, now) if v != nil { rq.line.Heuristic = each.name() rq.line.HeuristicFigures = v.figures return v } } return nil }