check / check (push) Waiting to run
The IPv6 group that is one client, the size of the table of clients and the level of the process's own lines become settings. clientGroup reads the group length from them, so limits, bans, history, lookups, AbuseIPDB scores and per-client anomaly counters all follow it; ratelimit.New takes the table size; the process logger takes the level once the settings are read, and request lines, written apart from it, are never held back. Judgement call: SWWAF_IPV6_GROUP_PREFIX accepts 32 to 128, the issue's example range. Model: opus-5-5
99 lines
3.6 KiB
Go
99 lines
3.6 KiB
Go
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 := 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 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: rq.h.clientGroup(rq.client),
|
|
ASN: rq.line.ASN,
|
|
ASName: rq.line.ASName,
|
|
Country: rq.line.Country,
|
|
Reason: reason,
|
|
Detail: detail,
|
|
})
|
|
}
|