package proxy import ( "context" "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/reputation" ) // deny is the SWWAF_BLOCKLIST_ACTION and the SWWAF_REPUTATION_ACTION that // refuses the requests of a client a source lists. const deny = "deny" // blocklistDenied notes the blocklists that list the client, as // noteListed does, and reports whether SWWAF_BLOCKLIST_ACTION, being deny, // refuses the request. Being limit, it lowers the client's limits instead // (see limitPercentages), and being log, it does nothing more. func (rq *request) blocklistDenied() bool { listedBy := rq.h.lists.ListedBy(rq.client) rq.blocklisted = len(listedBy) > 0 rq.noteListed(listedBy, "listed by a blocklist") return rq.blocklisted && rq.h.config.BlocklistAction == deny } // dnsblDenied notes the DNSBL zones whose verdict lists the client, as // noteListed does, and reports whether SWWAF_REPUTATION_ACTION, being // deny, refuses the request. Being limit, it lowers the client's limits // instead (see limitPercentages), and being log, it does nothing more. A // zone without a verdict on the client is asked about it in the // background, and the request does not wait for the answer. ctx is the // request's own context. func (rq *request) dnsblDenied(ctx context.Context) bool { listedBy := rq.h.dnsbl.ListedBy(ctx, rq.client) rq.dnsblListed = len(listedBy) > 0 rq.noteListed(listedBy, "listed by a DNSBL zone") return rq.dnsblListed && rq.h.config.ReputationAction == deny } // abuseIPDBDenied notes AbuseIPDB, as noteHit does, with the score, when // its score of the client is a hit, and reports whether // SWWAF_REPUTATION_ACTION, being deny, refuses the request, as dnsblDenied // does for a zone. While SWWAF_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. ctx is the request's own context. func (rq *request) abuseIPDBDenied(ctx context.Context) bool { if rq.h.config.AbuseIPDBKey == "" { return false } client := 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 false } rq.abuseIPDBHit = true rq.noteHit(reputation.AbuseIPDBSource, "scored by AbuseIPDB at or over "+ "SWWAF_ABUSEIPDB_MIN_SCORE", map[string]any{ "source": reputation.AbuseIPDBSource, "score": score, }) return rq.h.config.ReputationAction == deny } // noteListed notes each of sources, the URLs of the blocklists or the // DNSBL zones, their keys masked, that list the client, as noteHit does, // with reason, and the source in the alert's detail. func (rq *request) noteListed(sources []string, reason string) { for _, source := range sources { rq.noteHit(source, reason, map[string]any{"source": source}) } } // noteHit adds source, which lists the client, to the log line's // reputation, counts it in the metrics, and raises a reputation_hit alert // with reason and detail. func (rq *request) noteHit(source, reason string, detail map[string]any) { rq.line.Reputation = append(rq.line.Reputation, source) rq.h.metrics.ReputationHit(source) rq.h.alerts.Raise(alerts.Alert{ Event: alerts.EventReputationHit, Client: rq.client, Netblock: clientGroup(rq.client), ASN: rq.line.ASN, ASName: rq.line.ASName, Country: rq.line.Country, Reason: reason, Detail: detail, }) }