package discriminator // The heuristic abuseipdb. It reads SWW_WAF_ABUSEIPDB_KEY, // SWW_WAF_ABUSEIPDB_MIN_SCORE and SWW_WAF_REPUTATION_ACTION. import ( "context" "time" "sneak.berlin/go/smallwebwaf/internal/bans" "sneak.berlin/go/smallwebwaf/internal/config" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/reputation" "sneak.berlin/go/smallwebwaf/internal/requestlog" ) // abuseIPDB adds the tag abuseipdb and notes AbuseIPDB, as noteHit does, with // the score, when its score of the client is a hit, and refuses the request // when SWW_WAF_REPUTATION_ACTION is deny, as dnsbl does for a zone. While // SWW_WAF_ABUSEIPDB_KEY is unset it does nothing. A client without a score is // checked in the background, by the request's address, if its history counts // an offence, and the request does not wait for the answer. The score is then // used for each address of the client. Its figures are the score and // SWW_WAF_ABUSEIPDB_MIN_SCORE. type abuseIPDB struct{} func (abuseIPDB) name() string { return "abuseipdb" } func (abuseIPDB) check(ctx context.Context, rq *request, _ time.Time) *verdict { if rq.h.config.AbuseIPDBKey == "" { return nil } client := rq.h.clientGroup(rq.client) held, _ := rq.h.limiter.Client(client) offender := held.History.Offences != ratelimit.Offences{} score, hit := rq.h.abuseIPDB.Hit(ctx, client, rq.client, offender) if !hit { return nil } rq.abuseIPDBHit = true rq.addTag(config.AbuseIPDBTag) rq.noteHit(bans.ReputationHit{Source: reputation.AbuseIPDBSource, Score: &score}, "scored by AbuseIPDB at or over SWW_WAF_ABUSEIPDB_MIN_SCORE") if rq.h.config.ReputationAction != deny { return nil } return &verdict{ action: requestlog.ActionDenied, figures: map[string]any{ figureScore: score, "min_score": rq.h.config.AbuseIPDBMinScore, }, } }