check / check (push) Waiting to run
Zones in SWWAF_DNSBL_ZONES are asked about each client in the background, through SWWAF_DNSBL_RESOLVER or the host's resolver; no request waits. Verdicts last SWWAF_REPUTATION_CACHE_TTL, kept in reputation.json. SWWAF_REPUTATION_ACTION (limit:25) denies, limits or logs a listed client; each zone listing it raises reputation_hit. A failed query gives no verdict, raises source_failure, and pauses the zone a minute. A zone's key, its first label under dq.spamhaus.net, is masked everywhere but reputation.json. Zones compare without regard to case. Judgement call: answers in 127.255.255.0/24 or outside 127.0.0.0/8 are failures. Judgement call: the minute's pause after a failure; at most 1,000 queries at once. Judgement call: one zone given with two keys stops the start as listed twice. Rule suppressed: paralleltest on the DNSBL tests (Go's resolver shares state across synctest bubbles), funlen on the test of every logged setting. Model: opus-5-5
57 lines
2.0 KiB
Go
57 lines
2.0 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
|
)
|
|
|
|
// 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"
|
|
}
|
|
|
|
// noteListed adds sources, the URLs of the blocklists or the DNSBL zones,
|
|
// their keys masked, that list the client, to the log line's reputation,
|
|
// counts each of them in the metrics, and raises a reputation_hit alert,
|
|
// with reason, for each.
|
|
func (rq *request) noteListed(sources []string, reason string) {
|
|
rq.line.Reputation = append(rq.line.Reputation, sources...)
|
|
|
|
for _, source := range sources {
|
|
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: map[string]any{"source": source},
|
|
})
|
|
}
|
|
}
|