check / check (push) In progress
Each request gets sorted tags from the lookup, new address lists, the reputation sources and the rule matches, logged as `tags`. New settings: `SWW_TAG_LISTS`, `SWW_TAG_DENY`, `SWW_TAG_LIMIT_PERCENT`. They replace `SWW_ASN_LIMIT_PERCENT`, `SWW_COUNTRY_LIMIT_PERCENT` and `SWW_DENIED_COUNTRIES`. New heuristics `tag_lists` and `tag_deny`; the latter runs before the rate limits and again after the Core Rule Set. Model: opus-5-5
140 lines
4.2 KiB
Go
140 lines
4.2 KiB
Go
package discriminator
|
|
|
|
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 discriminator 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
|
|
// SWW_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.
|
|
// tag_deny runs twice: before the rate limits, on the tags known by then, and
|
|
// last, on the tag rule_files and core_rule_set can add.
|
|
func afterLookup() []heuristic {
|
|
return []heuristic{
|
|
tagLists{},
|
|
countries{},
|
|
blocklists{},
|
|
crowdSec{},
|
|
dnsbl{},
|
|
abuseIPDB{},
|
|
tagDeny{},
|
|
rateLimits{},
|
|
trapPaths{},
|
|
ruleFiles{},
|
|
headerLimits{},
|
|
coreRuleSet{},
|
|
tagDeny{},
|
|
}
|
|
}
|
|
|
|
// afterAnswer are the heuristics that run on a request once discriminator 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
|
|
}
|