Ban notes name the reputation sources that listed the client (closes #109)
check / check (push) Waiting to run
check / check (push) Waiting to run
A ban's notes, in bans.json and in its alert, gain `reputation`: each blocklist, DNSBL zone or AbuseIPDB that listed the client when the ban was made, as its `source`, named and ordered as in the request log's `reputation`, with AbuseIPDB's `score`. It is left out when none did. README.md shows it in a bans.json example. Notes now hold a list, so bans can no longer be compared with ==: the tests compare them with reflect.DeepEqual. Judgement call: the score is a pointer, so a score of 0, a hit while SWWAF_ABUSEIPDB_MIN_SCORE is 0, is still written. Model: opus-5-5
This commit is contained in:
@@ -163,15 +163,16 @@ in `bin/state` unless `SWWAF_STATE_DIR` is set, and the default rule file of
|
||||
and the requests or bytes counted in it, the client's percentage of that kind
|
||||
of limit and the setting that gave it when a biased threshold lowered the
|
||||
limit, the request that broke it, the client's AS number, AS name and country
|
||||
once they are looked up, the netblock's requests since it was first seen, how
|
||||
many of them the ban has refused, and how many bans the netblock had before,
|
||||
for a broken limit, for a clear sign of attack and by an admin. At most
|
||||
`SWWAF_MAX_BANS` bans `smallwebwaf` made are kept, past, active and permanent;
|
||||
past that, the earliest such ban of the netblock that has gone longest without
|
||||
a request is dropped first. The bans whose cause is `admin`, those you make or
|
||||
keep, are kept besides, and never dropped. `bans.json` shows the bans and
|
||||
their notes, a restart lifts none, and you make, keep or lift a ban by editing
|
||||
it (see "State files" below).
|
||||
once they are looked up, the blocklists, DNSBL zones and AbuseIPDB, with its
|
||||
score, that listed the client when the ban was made, the netblock's requests
|
||||
since it was first seen, how many of them the ban has refused, and how many
|
||||
bans the netblock had before, for a broken limit, for a clear sign of attack
|
||||
and by an admin. At most `SWWAF_MAX_BANS` bans `smallwebwaf` made are kept,
|
||||
past, active and permanent; past that, the earliest such ban of the netblock
|
||||
that has gone longest without a request is dropped first. The bans whose cause
|
||||
is `admin`, those you make or keep, are kept besides, and never dropped.
|
||||
`bans.json` shows the bans and their notes, a restart lifts none, and you
|
||||
make, keep or lift a ban by editing it (see "State files" below).
|
||||
- Checks each request against the rules of the rule files (see "Rule files"
|
||||
below) after the rate limits, and before its body is read. A `log` rule that
|
||||
matches is noted in the log line; a `block` rule refuses the request with
|
||||
@@ -1074,7 +1075,11 @@ with times in UTC.
|
||||
ban for a broken limit is `requests` or `bytes`, what the limit is on. For a
|
||||
limit a biased threshold lowered, the reason and the notes' `limit` give the
|
||||
lowered limit, and the notes' `limit_percent` and `limit_percent_setting` the
|
||||
client's percentage of that kind of limit and the setting that gave it.
|
||||
client's percentage of that kind of limit and the setting that gave it. The
|
||||
notes' `reputation` gives each blocklist, DNSBL zone or AbuseIPDB that listed
|
||||
the client when the ban was made, as its `source`, named and ordered as in the
|
||||
request log's `reputation`, with AbuseIPDB's `score` of the client. It is left
|
||||
out when none did, and the example below shows it.
|
||||
- `clients.json`: each client's two buckets of requests in the minute, the hour
|
||||
and the day, its two buckets of bytes in each, `minute_bytes`, `hour_bytes`
|
||||
and `day_bytes`, and its history: when it was first and last seen, its AS
|
||||
@@ -1121,6 +1126,60 @@ with times in UTC.
|
||||
Slack and ntfy too, stops the start: put the list under `"webhook"`, or remove
|
||||
the file.
|
||||
|
||||
This `bans.json` holds a ban for a broken rate limit on a client that a DNSBL
|
||||
zone lists and AbuseIPDB scores at 100, whose limits `SWWAF_REPUTATION_ACTION`,
|
||||
at its default of `limit:25`, lowered to a quarter:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"bans": [
|
||||
{
|
||||
"netblock": "203.0.113.9/32",
|
||||
"start": "2026-10-06T12:00:41.5Z",
|
||||
"expires": "2026-10-06T13:00:41.5Z",
|
||||
"cause": "limit",
|
||||
"reason": "requests per minute over the limit of 250",
|
||||
"notes": {
|
||||
"asn": "AS64496",
|
||||
"as_name": "Example Net",
|
||||
"country": "DE",
|
||||
"kind": "requests",
|
||||
"limit": 250,
|
||||
"window": "minute",
|
||||
"count": 251,
|
||||
"limit_percent": 25,
|
||||
"limit_percent_setting": "SWWAF_REPUTATION_ACTION",
|
||||
"reputation": [
|
||||
{
|
||||
"source": "dnsbl.dronebl.org"
|
||||
},
|
||||
{
|
||||
"source": "abuseipdb",
|
||||
"score": 100
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"time": "2026-10-06T12:00:41.5Z",
|
||||
"method": "GET",
|
||||
"host": "app.example",
|
||||
"path": "/owner/repo/commits/branch/main?page=812",
|
||||
"status": 403,
|
||||
"user_agent": "scraper/1.0"
|
||||
},
|
||||
"requests": 512,
|
||||
"refused": 0,
|
||||
"earlier_bans": {
|
||||
"limit": 0,
|
||||
"attack": 0,
|
||||
"admin": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`bans.json` is written `SWWAF_STATE_WRITE_DELAY` after a ban is made, lifted
|
||||
through `DELETE /_smallwebwaf/bans/<client>`, or made permanent, with every such
|
||||
change in between, and every file every `SWWAF_STATE_COUNTER_INTERVAL` and when
|
||||
@@ -1759,8 +1818,8 @@ fetched before then stays in use, and the failure is counted, logged and raised
|
||||
as a `source_failure` alert; a fetch cut off as `smallwebwaf` stops is not a
|
||||
failure. The last good copy of each list is kept whole, comment lines included,
|
||||
in `reputation.json` (see "State files" above), so that a restart keeps it in
|
||||
use too. Each list is named by its URL, in the request log, the alerts and the
|
||||
metrics, so keep a secret out of it.
|
||||
use too. Each list is named by its URL, in the request log, the alerts, the
|
||||
notes of bans and the metrics, so keep a secret out of it.
|
||||
|
||||
A client in `SWWAF_ALLOW_NETS` is not checked. Any other is checked by its own
|
||||
address after the country lists, and `SWWAF_BLOCKLIST_ACTION` says what is done
|
||||
@@ -1829,10 +1888,10 @@ it, such as `<key>.xbl.dq.spamhaus.net`. The key of a zone under
|
||||
`dq.spamhaus.net` is its first label, and `smallwebwaf` shows `********` in its
|
||||
place wherever it names the zone, as `********.xbl.dq.spamhaus.net`: in the
|
||||
settings logged at start, an error that stops the start, its own messages, the
|
||||
request log, the alerts and the metrics. Only `reputation.json` keeps the zone
|
||||
with its key. A key in the name of any other zone is shown as given. Several
|
||||
zones refuse queries that come through a public resolver; `SWWAF_DNSBL_RESOLVER`
|
||||
names another resolver to ask through.
|
||||
request log, the alerts, the notes of bans and the metrics. Only
|
||||
`reputation.json` keeps the zone with its key. A key in the name of any other
|
||||
zone is shown as given. Several zones refuse queries that come through a public
|
||||
resolver; `SWWAF_DNSBL_RESOLVER` names another resolver to ask through.
|
||||
|
||||
## AbuseIPDB
|
||||
|
||||
|
||||
Reference in New Issue
Block a user