Files
smallwebwaf/internal/proxy/reputation.go
T
clawbot ddb95f5411
check / check (push) Waiting to run
DNS blocklists asked in the background, verdicts kept (closes #104)
Zones in SWWAF_DNSBL_ZONES are asked about each client (RFC 5782 names)
in the background, through the host's resolver or SWWAF_DNSBL_RESOLVER;
no request waits. Verdicts last SWWAF_REPUTATION_CACHE_TTL and are kept
in reputation.json, at most 100,000. After the blocklists,
SWWAF_REPUTATION_ACTION (limit:25) denies, limits or logs a listed
client; the log line names the zones, each raises reputation_hit, with
metrics by zone. A failed, timed-out or refused query gives no verdict,
raises source_failure, and pauses the zone a minute.

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.
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
2026-10-07 18:00:50 +00:00

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
// 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},
})
}
}