Blocklists and an AS percentage file fetched by URL (closes #29)
check / check (push) Waiting to run
check / check (push) Waiting to run
SWWAF_BLOCKLIST_URLS names lists of addresses and netblocks, fetched every SWWAF_BLOCKLIST_REFRESH (24h, never under 1h); an IPv4-mapped line stands for its IPv4 address or netblock. reputation.json keeps each list's last try, failed or not, which a restart waits on as a running instance does, and its last good copy, whole, used while a fetch fails. SWWAF_BLOCKLIST_ACTION denies, limits or only logs a listed client; the log line names the lists, each raises reputation_hit, and a failed fetch raises source_failure. SWWAF_ASN_LIMIT_PERCENT_URL is fetched the same way and counts as SWWAF_ASN_LIMIT_PERCENT does, the lower winning. Judgement call: a failed fetch is retried after the refresh, not sooner. Not done: ban notes do not name the lists yet. Model: opus-5-5
This commit is contained in:
@@ -26,29 +26,33 @@ Slack and ntfy, and remote log sending. So is the stage after that: the AS
|
||||
number and country of every client, looked up through GeoJS or in the IPinfo
|
||||
Lite database file, the byte limits, the biased thresholds, lower limits for the
|
||||
AS numbers and countries you list, and the anomaly thresholds, alerts for
|
||||
unusual traffic that refuse nothing. `smallwebwaf` passes each request to the
|
||||
app and the app's answer back, unchanged, within its timeouts and size limits,
|
||||
works out each client's address, looks up its AS number and country unless you
|
||||
switch that off, bans a client that sends too many requests or too many bytes,
|
||||
not counting those for the paths you choose, with lower limits for the clients
|
||||
of the AS numbers and countries you list, refuses a client that comes from a
|
||||
country you refuse or from a network you refuse, lets the networks you choose
|
||||
unusual traffic that refuse nothing. So is the first part of the stage after
|
||||
that: the blocklists you name by URL, which it fetches and keeps, and a file of
|
||||
AS numbers' percentages, fetched the same way. `smallwebwaf` passes each request
|
||||
to the app and the app's answer back, unchanged, within its timeouts and size
|
||||
limits, works out each client's address, looks up its AS number and country
|
||||
unless you switch that off, bans a client that sends too many requests or too
|
||||
many bytes, not counting those for the paths you choose, with lower limits for
|
||||
the clients of the AS numbers and countries you list, refuses a client that
|
||||
comes from a country you refuse or from a network you refuse, refuses, limits or
|
||||
only notes a client a blocklist you name lists, lets the networks you choose
|
||||
through, checks each request against the rule files and bans a client whose
|
||||
request is a clear sign of attack, keeps its bans, each client's counters and
|
||||
history, and GeoJS's answers in JSON files across restarts, takes in your edits
|
||||
of those files, such as a ban you make, keep or lift, and of the rule files
|
||||
while it runs, writes a JSON log line for every request, sends its log lines to
|
||||
a syslog server too if you name one, sends an alert to a webhook, to Slack and
|
||||
to ntfy, each if you name one, for each ban it makes or makes permanent, for
|
||||
traffic over an anomaly threshold you set, for GeoJS failing, for a rule file or
|
||||
state file with an error and for a replacement of the lookup database it cannot
|
||||
read, serves Prometheus metrics to a scraper that holds the metrics token, lets
|
||||
an admin who holds the admin token list, add and lift bans and ask what it knows
|
||||
of a client, and in `observe` mode passes on the requests it would refuse,
|
||||
logging what it would have done with them. It comes as the image the app's own
|
||||
image is built on. The rest of the design comes after that, in the order of the
|
||||
build order in [`SPEC.md`](SPEC.md). The survey of existing tools that led to
|
||||
the design is in [`EVALUATION.md`](EVALUATION.md).
|
||||
history, GeoJS's answers and the last good copy of each list it fetches in JSON
|
||||
files across restarts, takes in your edits of those files, such as a ban you
|
||||
make, keep or lift, and of the rule files while it runs, writes a JSON log line
|
||||
for every request, sends its log lines to a syslog server too if you name one,
|
||||
sends an alert to a webhook, to Slack and to ntfy, each if you name one, for
|
||||
each ban it makes or makes permanent, for traffic over an anomaly threshold you
|
||||
set, for a client a blocklist lists, for GeoJS failing or a list it cannot
|
||||
fetch, for a rule file or state file with an error and for a replacement of the
|
||||
lookup database it cannot read, serves Prometheus metrics to a scraper that
|
||||
holds the metrics token, lets an admin who holds the admin token list, add and
|
||||
lift bans and ask what it knows of a client, and in `observe` mode passes on the
|
||||
requests it would refuse, logging what it would have done with them. It comes as
|
||||
the image the app's own image is built on. The rest of the design comes after
|
||||
that, in the order of the build order in [`SPEC.md`](SPEC.md). The survey of
|
||||
existing tools that led to the design is in [`EVALUATION.md`](EVALUATION.md).
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -124,18 +128,20 @@ in `bin/state` unless `SWWAF_STATE_DIR` is set, and the default rule file of
|
||||
or `SWWAF_RATE_LIMIT_EXEMPT_NETS`, and a request for a path
|
||||
`SWWAF_RATE_LIMIT_EXEMPT_PATHS` exempts.
|
||||
- Gives the clients of the AS numbers and countries the biased thresholds list,
|
||||
`SWWAF_ASN_LIMIT_PERCENT` and `SWWAF_COUNTRY_LIMIT_PERCENT`, the percentage
|
||||
they give of every rate limit and byte limit, so that the same rules ban them
|
||||
after fewer requests, and, while `SWWAF_UNKNOWN_LIMIT_PERCENT` is below 100,
|
||||
every client without a country that percentage. A client to which several
|
||||
apply gets the lowest. `SWWAF_ASN_BYTES_PERCENT` and
|
||||
`SWWAF_COUNTRY_BYTES_PERCENT` give the AS numbers and countries they list a
|
||||
percentage of the byte limits in place of the other two. Each client is
|
||||
counted on its own, against its own lowered limits: no budget is shared by a
|
||||
whole AS number or country, which one abuser could use up and so lock out
|
||||
everyone else there. The log line of each request the rate limits count gives
|
||||
its client's percentages below 100 and the settings that gave them, and so do
|
||||
the notes of a ban for a lowered limit, and its alert.
|
||||
`SWWAF_ASN_LIMIT_PERCENT`, the file `SWWAF_ASN_LIMIT_PERCENT_URL` names and
|
||||
`SWWAF_COUNTRY_LIMIT_PERCENT`, the percentage they give of every rate limit
|
||||
and byte limit, so that the same rules ban them after fewer requests, and,
|
||||
while `SWWAF_UNKNOWN_LIMIT_PERCENT` is below 100, every client without a
|
||||
country that percentage. A client to which several apply gets the lowest. For
|
||||
the byte limits, `SWWAF_ASN_BYTES_PERCENT` gives an AS number it lists a
|
||||
percentage in place of those `SWWAF_ASN_LIMIT_PERCENT` and the file give it,
|
||||
and `SWWAF_COUNTRY_BYTES_PERCENT` gives a country it lists one in place of the
|
||||
one `SWWAF_COUNTRY_LIMIT_PERCENT` gives it. Each client is counted on its own,
|
||||
against its own lowered limits: no budget is shared by a whole AS number or
|
||||
country, which one abuser could use up and so lock out everyone else there.
|
||||
The log line of each request the rate limits count gives its client's
|
||||
percentages below 100 and the settings that gave them, and so do the notes of
|
||||
a ban for a lowered limit, and its alert.
|
||||
- Bans a client that breaks a rate limit or a byte limit, as "Bans" in
|
||||
[`SPEC.md`](SPEC.md) describes: the first ban lasts an hour, and a limit
|
||||
broken again within a day of a ban ending bans for three times as long as that
|
||||
@@ -191,34 +197,43 @@ in `bin/state` unless `SWWAF_STATE_DIR` is set, and the default rule file of
|
||||
link-local address has no country and is never looked up:
|
||||
`SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` refuses it unless it is in
|
||||
`SWWAF_ALLOW_NETS`, and `SWWAF_DENIED_COUNTRIES` does not refuse it.
|
||||
- Checks the client's own address against the blocklists `SWWAF_BLOCKLIST_URLS`
|
||||
names, after the country lists and before the rate limits (see "Blocklists"
|
||||
below). With `SWWAF_BLOCKLIST_ACTION` at `deny`, its default, a request from a
|
||||
client a blocklist lists is refused with `SWWAF_BAN_RESPONSE` before its body
|
||||
is read; it is not counted for the rate limits, and makes no ban. With
|
||||
`limit:<percent>` the client gets that percentage of every rate limit and byte
|
||||
limit, the lowest of its percentages applying, as for a biased threshold, and
|
||||
with `log` nothing more is done. Whatever the action, the request's log line
|
||||
names the lists, and each raises an alert.
|
||||
- Checks the client's own address against the static lists, the three netblock
|
||||
settings below, before anything else, its lookup included. A client in
|
||||
`SWWAF_ALLOW_NETS` skips bans, the country lists, the rate limits, the byte
|
||||
limits and the rule files, and is not looked up; the timeouts and size limits
|
||||
still apply. A client in `SWWAF_DENY_NETS` is refused with
|
||||
`SWWAF_ALLOW_NETS` skips bans, the country lists, the blocklists, the rate
|
||||
limits, the byte limits and the rule files, and is not looked up; the timeouts
|
||||
and size limits still apply. A client in `SWWAF_DENY_NETS` is refused with
|
||||
`SWWAF_BAN_RESPONSE` before its body is read, and the request is not counted
|
||||
for the rate limits; an address in `SWWAF_ALLOW_NETS` too is let through. A
|
||||
client in `SWWAF_RATE_LIMIT_EXEMPT_NETS` is neither counted nor refused by the
|
||||
rate limits, and has no bytes counted by the byte limits; the country lists,
|
||||
the rule files and bans still apply to it.
|
||||
- In `observe` mode, with `SWWAF_MODE=observe`, refuses none of the requests
|
||||
that `SWWAF_DENY_NETS`, a ban, the country lists, a rate limit or a rule would
|
||||
refuse: it passes them to the app, and their log lines name what `enforce`
|
||||
mode would have done (see `would_action` in "Request log" below). The checks
|
||||
run, and requests and bytes are counted, as in `enforce` mode, with three
|
||||
differences: neither a broken rate limit or byte limit nor a `ban` rule makes
|
||||
a ban; a broken limit does not set the client's counters back to zero, so each
|
||||
request over a rate limit is logged as one that would be refused, and each
|
||||
whose bytes keep the client over a byte limit as breaking it; and a request
|
||||
under a ban does not make it permanent. As in `enforce` mode, the bytes
|
||||
counted are only those of the requests `enforce` mode would have passed to the
|
||||
app. A ban it would have made, or made permanent, raises the alert `enforce`
|
||||
mode would have raised, marked as what would have happened (see "Alerts"
|
||||
below). The bans in `bans.json` are kept, and refuse requests again when
|
||||
`smallwebwaf` next runs in `enforce` mode, as long as they last. The timeouts
|
||||
and size limits still apply, since they protect `smallwebwaf` and the app
|
||||
themselves, and a request for one of `smallwebwaf`'s own endpoints without its
|
||||
token is still answered `401`. It is for trying a configuration before
|
||||
that `SWWAF_DENY_NETS`, a ban, the country lists, a blocklist, a rate limit or
|
||||
a rule would refuse: it passes them to the app, and their log lines name what
|
||||
`enforce` mode would have done (see `would_action` in "Request log" below).
|
||||
The checks run, and requests and bytes are counted, as in `enforce` mode, with
|
||||
three differences: neither a broken rate limit or byte limit nor a `ban` rule
|
||||
makes a ban; a broken limit does not set the client's counters back to zero,
|
||||
so each request over a rate limit is logged as one that would be refused, and
|
||||
each whose bytes keep the client over a byte limit as breaking it; and a
|
||||
request under a ban does not make it permanent. As in `enforce` mode, the
|
||||
bytes counted are only those of the requests `enforce` mode would have passed
|
||||
to the app. A ban it would have made, or made permanent, raises the alert
|
||||
`enforce` mode would have raised, marked as what would have happened (see
|
||||
"Alerts" below). The bans in `bans.json` are kept, and refuse requests again
|
||||
when `smallwebwaf` next runs in `enforce` mode, as long as they last. The
|
||||
timeouts and size limits still apply, since they protect `smallwebwaf` and the
|
||||
app themselves, and a request for one of `smallwebwaf`'s own endpoints without
|
||||
its token is still answered `401`. It is for trying a configuration before
|
||||
enforcing it.
|
||||
- Answers `GET /_smallwebwaf/healthz` itself with `200` and `ok`, before any
|
||||
check and without asking the app, for the image's health check.
|
||||
@@ -239,13 +254,14 @@ in `bin/state` unless `SWWAF_STATE_DIR` is set, and the default rule file of
|
||||
`SWWAF_LOG_REMOTE_URL` names one (see "Sending the log to a syslog server"
|
||||
below).
|
||||
- Sends an alert for each ban it makes or makes permanent, for a count over an
|
||||
anomaly threshold, for GeoJS failing, for a rule file or state file with an
|
||||
error, and for a replacement of the lookup database it cannot read, holding
|
||||
back repeats and, past an hourly limit, rolling the rest into one summary, to
|
||||
each destination you name: as a JSON object to the webhook
|
||||
`SWWAF_ALERT_WEBHOOK_URL` names, as a message to the Slack incoming webhook
|
||||
`SWWAF_ALERT_SLACK_WEBHOOK_URL` names, and as a message to the ntfy topic
|
||||
`SWWAF_ALERT_NTFY_URL` names (see "Alerts" below).
|
||||
anomaly threshold, for a client a blocklist lists, for GeoJS failing or a list
|
||||
it cannot fetch, for a rule file or state file with an error, and for a
|
||||
replacement of the lookup database it cannot read, holding back repeats and,
|
||||
past an hourly limit, rolling the rest into one summary, to each destination
|
||||
you name: as a JSON object to the webhook `SWWAF_ALERT_WEBHOOK_URL` names, as
|
||||
a message to the Slack incoming webhook `SWWAF_ALERT_SLACK_WEBHOOK_URL` names,
|
||||
and as a message to the ntfy topic `SWWAF_ALERT_NTFY_URL` names (see "Alerts"
|
||||
below).
|
||||
- Counts requests and their bytes over a minute and an hour, per client, per
|
||||
netblock around a client, per AS number, for the whole service and per named
|
||||
netblock, and sends an `anomaly` alert for a count over the anomaly threshold
|
||||
@@ -308,8 +324,8 @@ effective settings are logged at start.
|
||||
- `SWWAF_REQUEST_MAX_BYTES` (default `100M`): the largest request body.
|
||||
- `SWWAF_RESPONSE_MAX_BYTES` (default `5G`): the largest response body.
|
||||
- `SWWAF_ALLOW_NETS` (default empty): netblocks whose clients skip bans, the
|
||||
country lists, the rate limits, the byte limits and the rule files, such as
|
||||
your monitoring or your own networks.
|
||||
country lists, the blocklists, the rate limits, the byte limits and the rule
|
||||
files, such as your monitoring or your own networks.
|
||||
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients the
|
||||
rate limits and the byte limits do not apply to, such as a machine that talks
|
||||
to the app all day.
|
||||
@@ -349,9 +365,10 @@ effective settings are logged at start.
|
||||
`SWWAF_LOOKUP_DB_PATH` names, or `off`, which looks up no client and sends no
|
||||
address to GeoJS. With `off`, a country list that is not empty,
|
||||
`SWWAF_ADD_LOOKUP_HEADERS` set to `true`, a biased threshold that lowers a
|
||||
limit, a list of them that is not empty or `SWWAF_UNKNOWN_LIMIT_PERCENT` below
|
||||
100, or an anomaly threshold per AS number that is not `off`, stops the start,
|
||||
with a message naming it and `SWWAF_LOOKUP_SOURCE`.
|
||||
limit, a list of them that is not empty, `SWWAF_ASN_LIMIT_PERCENT_URL` set or
|
||||
`SWWAF_UNKNOWN_LIMIT_PERCENT` below 100, or an anomaly threshold per AS number
|
||||
that is not `off`, stops the start, with a message naming it and
|
||||
`SWWAF_LOOKUP_SOURCE`.
|
||||
- `SWWAF_LOOKUP_DB_PATH` (default empty): the IPinfo Lite database file, in its
|
||||
`.mmdb` form, for `SWWAF_LOOKUP_SOURCE=file`. `file` without it, or it with
|
||||
any other `SWWAF_LOOKUP_SOURCE`, the default included, stops the start, with a
|
||||
@@ -389,11 +406,33 @@ effective settings are logged at start.
|
||||
client without a country gets: one the lookup cannot place, one on a private,
|
||||
loopback or link-local address, which is never looked up, and one whose answer
|
||||
from GeoJS has not come in time.
|
||||
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
||||
file of AS numbers, each with its percentage of every rate limit and byte
|
||||
limit, one such as `AS14061:50` to a line, whose percentages count as those of
|
||||
`SWWAF_ASN_LIMIT_PERCENT` do, so that one list can serve several instances.
|
||||
For an AS number both give a percentage, the lower applies, and an AS number
|
||||
the file lists twice gets the lower of its two. It is fetched and kept as a
|
||||
blocklist is (see "Blocklists" below). A URL `SWWAF_BLOCKLIST_URLS` names too
|
||||
stops the start.
|
||||
- `SWWAF_BLOCKLIST_URLS` (default empty): the blocklists, as `http` or `https`
|
||||
URLs without a user or a fragment, such as
|
||||
`https://www.spamhaus.org/drop/drop.txt` (see "Blocklists" below). A URL
|
||||
listed twice stops the start.
|
||||
- `SWWAF_BLOCKLIST_REFRESH` (default `24h`): how long after a list was last
|
||||
fetched, or a fetch of it failed, it is fetched again, for the blocklists and
|
||||
`SWWAF_ASN_LIMIT_PERCENT_URL`. Less than `1h` stops the start: the Spamhaus
|
||||
lists may be fetched no more than once an hour.
|
||||
- `SWWAF_BLOCKLIST_ACTION` (default `deny`): what is done with a client a
|
||||
blocklist lists: `deny` refuses its requests with `SWWAF_BAN_RESPONSE`,
|
||||
`limit:<percent>`, such as `limit:25`, gives it that percentage of every rate
|
||||
limit and byte limit, and `log` does nothing more than note the lists in the
|
||||
log line and raise the alert.
|
||||
- `SWWAF_BAN_RESPONSE` (default `403`): how a refused client is answered, one
|
||||
that is banned, breaks a rate limit, matches a `ban` rule, is in
|
||||
`SWWAF_DENY_NETS` or comes from a refused country: `403`, `429`, or `close` to
|
||||
close the connection without an answer. Behind traefik, `close` does not leave
|
||||
the client unanswered: traefik answers `502`, as it does whenever its backend
|
||||
`SWWAF_DENY_NETS`, comes from a refused country or is in a blocklist while
|
||||
`SWWAF_BLOCKLIST_ACTION` is `deny`: `403`, `429`, or `close` to close the
|
||||
connection without an answer. Behind traefik, `close` does not leave the
|
||||
client unanswered: traefik answers `502`, as it does whenever its backend
|
||||
drops a connection. A `block` rule always answers `403`.
|
||||
- `SWWAF_LIMIT_BAN_DURATION` (default `1h`): the ban for a first broken rate
|
||||
limit or byte limit.
|
||||
@@ -486,8 +525,8 @@ effective settings are logged at start.
|
||||
`SWWAF_INSTANCE_NAME`, which ntfy is sent in the title.
|
||||
- `SWWAF_ALERT_EVENTS` (default
|
||||
`ban,permanent_ban,waf_block,anomaly,reputation_hit,source_failure,file_error`):
|
||||
the events alerts are sent for. `waf_block` and `reputation_hit` come with the
|
||||
features that raise them; nothing raises them yet.
|
||||
the events alerts are sent for. `waf_block` comes with the Core Rule Set;
|
||||
nothing raises it yet.
|
||||
- `SWWAF_ALERT_COOLDOWN` (default `15m`): how long a repeat of an alert is held
|
||||
back (see "Alerts" below).
|
||||
- `SWWAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour;
|
||||
@@ -536,9 +575,10 @@ an AS number or a country, `:` and a percentage; an AS number or a country
|
||||
listed twice in one of them stops the start. `off` switches a timeout, a size
|
||||
limit, a rate limit, a byte limit, an anomaly threshold, `SWWAF_ALERT_COOLDOWN`
|
||||
or `SWWAF_ALERT_MAX_PER_HOUR` off; `SWWAF_CLIENT_REQUEST_HEADER_MAX_BYTES`,
|
||||
`SWWAF_LOOKUP_TIMEOUT`, `SWWAF_UNKNOWN_LIMIT_PERCENT`, the ban settings, the
|
||||
state settings, `SWWAF_METRICS_TOP_N`, `SWWAF_LOG_REMOTE_BUFFER`,
|
||||
`SWWAF_ANOMALY_NET_V4_PREFIX` and `SWWAF_ANOMALY_NET_V6_PREFIX` cannot be off.
|
||||
`SWWAF_LOOKUP_TIMEOUT`, `SWWAF_UNKNOWN_LIMIT_PERCENT`,
|
||||
`SWWAF_BLOCKLIST_REFRESH`, the ban settings, the state settings,
|
||||
`SWWAF_METRICS_TOP_N`, `SWWAF_LOG_REMOTE_BUFFER`, `SWWAF_ANOMALY_NET_V4_PREFIX`
|
||||
and `SWWAF_ANOMALY_NET_V6_PREFIX` cannot be off.
|
||||
|
||||
Several limits are fixed rather than settings. At most 20,000 clients are kept,
|
||||
with their counters and history, and an IPv6 client is counted by its /64. At
|
||||
@@ -626,26 +666,29 @@ which every line has.
|
||||
app's, as passed on, or those of `smallwebwaf`'s own answer.
|
||||
- `request_bytes` and `response_bytes` count body bytes.
|
||||
- `action` is `forward` for a request passed to the app, `denied` for one
|
||||
refused because its client is in `SWWAF_DENY_NETS`, `banned` for one refused
|
||||
because a ban covers its client or because it matched a `ban` rule, which bans
|
||||
its client, `country_denied` for one refused for its client's country,
|
||||
`rate_limited` for one that broke a rate limit and banned its client,
|
||||
`rule_blocked` for one a `block` rule refused, `too_large` for a request or
|
||||
response over its size limit, `timed_out` for one that ran out of time,
|
||||
`upstream_error` when the app could not be reached or its answer broke off,
|
||||
and `admin` for one `smallwebwaf` answered at its own endpoint.
|
||||
refused because its client is in `SWWAF_DENY_NETS`, or in a blocklist while
|
||||
`SWWAF_BLOCKLIST_ACTION` is `deny`, `banned` for one refused because a ban
|
||||
covers its client or because it matched a `ban` rule, which bans its client,
|
||||
`country_denied` for one refused for its client's country, `rate_limited` for
|
||||
one that broke a rate limit and banned its client, `rule_blocked` for one a
|
||||
`block` rule refused, `too_large` for a request or response over its size
|
||||
limit, `timed_out` for one that ran out of time, `upstream_error` when the app
|
||||
could not be reached or its answer broke off, and `admin` for one
|
||||
`smallwebwaf` answered at its own endpoint.
|
||||
- `would_action` is there in `observe` mode for a request that
|
||||
`SWWAF_DENY_NETS`, a ban, the country lists, a rate limit or a rule would have
|
||||
refused in `enforce` mode, and names the action that refusal would have had:
|
||||
`denied`, `banned`, `country_denied`, `rate_limited` or `rule_blocked`.
|
||||
`action` then names what was done: `forward` for a request passed to the app,
|
||||
and another action, such as `too_large`, for one a size or time limit refused.
|
||||
`SWWAF_DENY_NETS`, a ban, the country lists, a blocklist, a rate limit or a
|
||||
rule would have refused in `enforce` mode, and names the action that refusal
|
||||
would have had: `denied`, `banned`, `country_denied`, `rate_limited` or
|
||||
`rule_blocked`. `action` then names what was done: `forward` for a request
|
||||
passed to the app, and another action, such as `too_large`, for one a size or
|
||||
time limit refused.
|
||||
- `limit_percent` is there for a request the rate limits count whose client a
|
||||
biased threshold gives less than the whole of the rate limits, and gives the
|
||||
percentage it gets, with `limit_percent_setting` naming the setting that gave
|
||||
it, such as `SWWAF_ASN_LIMIT_PERCENT`. `bytes_percent` and
|
||||
`bytes_percent_setting` are the same for the byte limits. Each is left out
|
||||
when the client gets the whole of those limits.
|
||||
biased threshold, or `SWWAF_BLOCKLIST_ACTION` for a blocklist that lists it,
|
||||
gives less than the whole of the rate limits, and gives the percentage it
|
||||
gets, with `limit_percent_setting` naming the setting that gave it, such as
|
||||
`SWWAF_ASN_LIMIT_PERCENT`, or `SWWAF_ASN_LIMIT_PERCENT_URL` for the file it
|
||||
names. `bytes_percent` and `bytes_percent_setting` are the same for the byte
|
||||
limits. Each is left out when the client gets the whole of those limits.
|
||||
- `counts` gives the client's requests in the minute, the hour and the day as
|
||||
the rate limits count them, this request included: in each window, those in
|
||||
the bucket under way and a share of those in the bucket before, so a count can
|
||||
@@ -653,11 +696,11 @@ which every line has.
|
||||
broke it. It is left out for a request the rate limits do not count: the
|
||||
health check, one from a client in `SWWAF_ALLOW_NETS` or
|
||||
`SWWAF_RATE_LIMIT_EXEMPT_NETS`, one for a path that
|
||||
`SWWAF_RATE_LIMIT_EXEMPT_PATHS` exempts, and one that `SWWAF_DENY_NETS`, a ban
|
||||
or the country lists refuse, or would refuse in `observe` mode. Its
|
||||
`minute_bytes`, `hour_bytes` and `day_bytes` give the client's bytes in each
|
||||
window as the byte limits count them, in the same way: for a request whose
|
||||
bytes they count, with its own, once it has ended; for any other, those
|
||||
`SWWAF_RATE_LIMIT_EXEMPT_PATHS` exempts, and one that `SWWAF_DENY_NETS`, a
|
||||
ban, the country lists or a blocklist refuse, or would refuse in `observe`
|
||||
mode. Its `minute_bytes`, `hour_bytes` and `day_bytes` give the client's bytes
|
||||
in each window as the byte limits count them, in the same way: for a request
|
||||
whose bytes they count, with its own, once it has ended; for any other, those
|
||||
counted before it.
|
||||
- `rule_ids` is there for a request that matched rules of the rule files, and
|
||||
lists their ids in the order they matched, up to the one that refused it.
|
||||
@@ -668,6 +711,12 @@ which every line has.
|
||||
several. `offence` is then `limit`. A request whose bytes broke a byte limit
|
||||
is not refused: its `action` is what it would have been otherwise, such as
|
||||
`forward`.
|
||||
- `reputation` is there for a request whose client a blocklist lists, and gives
|
||||
the URLs of the blocklists that list it, in the order `SWWAF_BLOCKLIST_URLS`
|
||||
names them, whatever `SWWAF_BLOCKLIST_ACTION` says. It is left out for a
|
||||
client the blocklists are not checked for: one in `SWWAF_ALLOW_NETS`, and one
|
||||
`SWWAF_DENY_NETS`, a ban or the country lists refuse first, or would in
|
||||
`observe` mode.
|
||||
- `ban_expires` is there for a request that made a ban or was refused under one,
|
||||
or in `observe` mode would have been refused under one, and gives when the ban
|
||||
ends, in the same form as `time`, or `permanent`.
|
||||
@@ -737,7 +786,11 @@ it, as below. An alert is for one of these events, and is sent when
|
||||
- `anomaly`: a count of requests or bytes over an anomaly threshold, raised by
|
||||
each request that ends with the count over it, in `observe` mode as in
|
||||
`enforce` mode. It refuses and bans nothing.
|
||||
- `source_failure`: GeoJS failing or refusing `smallwebwaf`.
|
||||
- `reputation_hit`: a request whose client a blocklist lists, one alert for each
|
||||
blocklist that lists it, whatever `SWWAF_BLOCKLIST_ACTION` says, in `observe`
|
||||
mode as in `enforce` mode.
|
||||
- `source_failure`: GeoJS failing or refusing `smallwebwaf`, or a fetch of a
|
||||
list failing (see "Blocklists" below).
|
||||
- `file_error`: a rule file edited while it runs that has an error, an edit of a
|
||||
state file set aside as `<name>.bad`, a state file it could not write while
|
||||
running, or a replacement of the lookup database it could not read, which it
|
||||
@@ -800,25 +853,29 @@ is sent on one line:
|
||||
- `client` is the address of the client whose request raised the alert, and
|
||||
`netblock` the netblock of the ban, or for an `anomaly`, the netblock counted:
|
||||
the client's own, the netblock around it or a named netblock, and none for an
|
||||
AS number or the whole service; both are empty for `source_failure` and
|
||||
AS number or the whole service, or for a `reputation_hit`, the client's own,
|
||||
as `client_group` gives it; both are empty for `source_failure` and
|
||||
`file_error`. `asn`, `as_name` and `country` are, for a ban, the client's as
|
||||
the ban's notes give them when the alert is raised: empty, as in this alert,
|
||||
when GeoJS had not answered about the client by then; for an `anomaly`, the
|
||||
client's as the lookup gave them by the time its request ended.
|
||||
client's as the lookup gave them by the time its request ended; for a
|
||||
`reputation_hit`, the client's as its request's log line gives them.
|
||||
- `reason` is a short sentence; for a ban, the ban's `reason` in `bans.json`;
|
||||
for an `anomaly`, what was counted over which threshold, such as
|
||||
`requests per minute of the netblock 203.0.113.0/24 over the threshold of 1000`.
|
||||
`requests per minute of the netblock 203.0.113.0/24 over the threshold of 1000`;
|
||||
for a `reputation_hit`, `listed by a blocklist`.
|
||||
- `detail` is what is particular to the event: for a ban, its `cause`, when it
|
||||
ends as `ban_expires`, in the form the request log gives it, and its `notes`,
|
||||
as `bans.json` gives them; for an `anomaly`, the `scope`, `client`, `net`,
|
||||
`asn`, `total` or `watch`, as the settings name them, the `asn` counted for
|
||||
`asn` and the `name` of the named netblock for `watch`, the `window`, `minute`
|
||||
or `hour`, the `kind`, `requests` or `bytes`, the `count`, which is weighted
|
||||
as the rate limits weigh theirs, and the `threshold`; for `source_failure`,
|
||||
the `source`, `geojs`, the `error`, and when GeoJS is asked again,
|
||||
`asking_again_in`; for `file_error`, the `file`, which for an edit set aside
|
||||
is the file it was renamed to, and the `error`, which for a file that does not
|
||||
parse names where in it the error is.
|
||||
as the rate limits weigh theirs, and the `threshold`; for a `reputation_hit`,
|
||||
the `source`, the URL of the blocklist; for `source_failure`, the `source`,
|
||||
`geojs` or the URL of the list, the `error`, and for GeoJS, when it is asked
|
||||
again, `asking_again_in`; for `file_error`, the `file`, which for an edit set
|
||||
aside is the file it was renamed to, and the `error`, which for a file that
|
||||
does not parse names where in it the error is.
|
||||
- `suppressed_repeats` is how many repeats the cooldown held back before this
|
||||
alert, and for a `summary`, those no other alert gives (see below).
|
||||
|
||||
@@ -863,14 +920,15 @@ and Slack this JSON object, shown indented; it is sent on one line:
|
||||
}
|
||||
```
|
||||
|
||||
An alert for the same event as the last one sent, on the same netblock, or for a
|
||||
`file_error` about the same file, or for a `source_failure` about the same
|
||||
source, or for an `anomaly` in the same scope, with the same netblock, AS number
|
||||
or name, whatever its window and kind, less than `SWWAF_ALERT_COOLDOWN` after
|
||||
it, is a repeat: it is held back and counted, and the next alert sent for them
|
||||
gives that count as `suppressed_repeats`. As each hour of the clock, in UTC,
|
||||
ends, the cooldowns that have run out are dropped, and the repeats they held
|
||||
back, which no alert sent since has given, go in that hour's summary.
|
||||
An alert for the same event as the last one sent, on the same netblock, and for
|
||||
a `reputation_hit` about the same blocklist, or for a `file_error` about the
|
||||
same file, or for a `source_failure` about the same source, or for an `anomaly`
|
||||
in the same scope, with the same netblock, AS number or name, whatever its
|
||||
window and kind, less than `SWWAF_ALERT_COOLDOWN` after it, is a repeat: it is
|
||||
held back and counted, and the next alert sent for them gives that count as
|
||||
`suppressed_repeats`. As each hour of the clock, in UTC, ends, the cooldowns
|
||||
that have run out are dropped, and the repeats they held back, which no alert
|
||||
sent since has given, go in that hour's summary.
|
||||
|
||||
Past `SWWAF_ALERT_MAX_PER_HOUR` alerts in an hour, the hour's other alerts are
|
||||
held back and counted by event. An alert held back this way starts no cooldown.
|
||||
@@ -897,11 +955,12 @@ restart the alerts waiting are sent, and the cooldowns go on.
|
||||
|
||||
## State files
|
||||
|
||||
`smallwebwaf` keeps its state in memory and a copy of it in four JSON files in
|
||||
`smallwebwaf` keeps its state in memory and a copy of it in five JSON files in
|
||||
`SWWAF_STATE_DIR`, `/var/lib/smallwebwaf` by default, as "Persistent state" in
|
||||
[`SPEC.md`](SPEC.md) describes. Each has a top-level `version`, 1, and lists its
|
||||
entries by client address, but for the alerts waiting, and the anomaly counters,
|
||||
which are listed by scope first, with times in UTC.
|
||||
entries by client address, but for the alerts waiting, the anomaly counters,
|
||||
which are listed by scope first, and the copies of the lists, listed by URL,
|
||||
with times in UTC.
|
||||
|
||||
- `bans.json`: every ban with its notes, indented to be read. A permanent ban's
|
||||
`expires` is `null`. A ban's `cause` is `limit` for a broken rate limit or
|
||||
@@ -927,25 +986,32 @@ which are listed by scope first, with times in UTC.
|
||||
`grep` shows everything about one.
|
||||
- `lookups.json`: GeoJS's answers, one to a line, each with the client's AS
|
||||
number, AS name and country, when GeoJS gave it and when it was last used.
|
||||
- `reputation.json`: each list fetched from a URL (see "Blocklists" below),
|
||||
indented to be read, under `lists`: its `url`, when it was last `tried`, the
|
||||
fetch failed or not, and its last good copy: when that was `fetched`, and its
|
||||
`lines`, as fetched, comment lines included, each on a line of its own, both
|
||||
left out while no fetch of it has succeeded. As the file is read, the lists
|
||||
the settings no longer name are dropped.
|
||||
- `alerts.json`: the state of the alerts (see "Alerts" above), indented to be
|
||||
read: under `cooldowns`, for each event and netblock, or event and `file` or
|
||||
`source`, or for an `anomaly`, its `scope` with its `netblock`, `asn` or
|
||||
`name`, or event alone, when the last alert was sent, `sent`, and the repeats
|
||||
held back since, `suppressed_repeats`; under `hour`, the hour under way, from
|
||||
its `start`, the alerts `sent` in it and those `held_back` for its summary, by
|
||||
event; under `waiting`, for each destination you name, `webhook`, `slack` or
|
||||
`ntfy`, the alerts still waiting to be sent to it, the oldest first, each as
|
||||
the webhook is sent it; and under `anomaly_counters`, each anomaly counter:
|
||||
its `scope`, as an `anomaly` alert names it, with the `netblock` of a client,
|
||||
of a netblock around a client or of a named netblock, the `asn` of an AS
|
||||
number and the `name` of a named netblock, and its two buckets of requests in
|
||||
the minute and the hour, `minute` and `hour`, and of bytes, `minute_bytes` and
|
||||
`hour_bytes`, each left out while it is empty. As an hour ends, the cooldowns
|
||||
that have run out are dropped, and the hour's summary gives the repeats they
|
||||
held back. As the file is read, the alerts waiting for a destination you no
|
||||
longer name are dropped. A file whose `waiting` is a list, as it was before
|
||||
alerts went to Slack and ntfy too, stops the start: put the list under
|
||||
`"webhook"`, or remove the file.
|
||||
read: under `cooldowns`, for each event and netblock, with the `source` too
|
||||
for a `reputation_hit`, or event and `file` or `source`, or for an `anomaly`,
|
||||
its `scope` with its `netblock`, `asn` or `name`, or event alone, when the
|
||||
last alert was sent, `sent`, and the repeats held back since,
|
||||
`suppressed_repeats`; under `hour`, the hour under way, from its `start`, the
|
||||
alerts `sent` in it and those `held_back` for its summary, by event; under
|
||||
`waiting`, for each destination you name, `webhook`, `slack` or `ntfy`, the
|
||||
alerts still waiting to be sent to it, the oldest first, each as the webhook
|
||||
is sent it; and under `anomaly_counters`, each anomaly counter: its `scope`,
|
||||
as an `anomaly` alert names it, with the `netblock` of a client, of a netblock
|
||||
around a client or of a named netblock, the `asn` of an AS number and the
|
||||
`name` of a named netblock, and its two buckets of requests in the minute and
|
||||
the hour, `minute` and `hour`, and of bytes, `minute_bytes` and `hour_bytes`,
|
||||
each left out while it is empty. As an hour ends, the cooldowns that have run
|
||||
out are dropped, and the hour's summary gives the repeats they held back. As
|
||||
the file is read, the alerts waiting for a destination you no longer name are
|
||||
dropped. A file whose `waiting` is a list, as it was before alerts went to
|
||||
Slack and ntfy too, stops the start: put the list under `"webhook"`, or remove
|
||||
the file.
|
||||
|
||||
`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
|
||||
@@ -957,27 +1023,30 @@ whole. A write that fails is logged, raised as a `file_error` alert while
|
||||
changed since the last write.
|
||||
|
||||
At start the files are read back: each client keeps its counts, so a restart
|
||||
gives it no fresh allowance, each anomaly counter keeps its counts, and each ban
|
||||
gives it no fresh allowance, each anomaly counter keeps its counts, each ban
|
||||
keeps refusing every client in its netblock until it ends, even after
|
||||
`SWWAF_BAN_SCOPE_V4_PREFIX` has changed. A netblock whose address has bits past
|
||||
its length, such as `203.0.113.9/24`, is read as the netblock it is in,
|
||||
`203.0.113.0/24`. Buckets and answers whose time has passed are dropped, and so
|
||||
is an anomaly counter left with no bucket. A missing file is empty state, as on
|
||||
a first start. A file that does not parse, or has another `version`, stops the
|
||||
start with a message naming the file, and the line and column where Go's JSON
|
||||
decoder gives them; so does a state directory `smallwebwaf` cannot write. So
|
||||
does an entry without a field it needs, named with the entry's place in the
|
||||
file: a ban's `netblock`, `start` or `expires`, which is `null` for a permanent
|
||||
ban; a client's `client`, or the `start` of a window in which it has requests or
|
||||
bytes; an answer's `client`, `country`, which is `""` for a client GeoJS cannot
|
||||
place, or `answered`; a cooldown's `event` or `sent`; an alert waiting's `event`
|
||||
or `time`; an anomaly counter's `netblock`, unless it counts an AS number or the
|
||||
`SWWAF_BAN_SCOPE_V4_PREFIX` has changed, and the copy of each list stays in use
|
||||
until a fetch of it succeeds. A netblock whose address has bits past its length,
|
||||
such as `203.0.113.9/24`, is read as the netblock it is in, `203.0.113.0/24`.
|
||||
Buckets and answers whose time has passed are dropped, and so is an anomaly
|
||||
counter left with no bucket. A missing file is empty state, as on a first start.
|
||||
A file that does not parse, or has another `version`, stops the start with a
|
||||
message naming the file, and the line and column where Go's JSON decoder gives
|
||||
them; so does a state directory `smallwebwaf` cannot write. So does an entry
|
||||
without a field it needs, named with the entry's place in the file: a ban's
|
||||
`netblock`, `start` or `expires`, which is `null` for a permanent ban; a
|
||||
client's `client`, or the `start` of a window in which it has requests or bytes;
|
||||
an answer's `client`, `country`, which is `""` for a client GeoJS cannot place,
|
||||
or `answered`; a cooldown's `event` or `sent`; an alert waiting's `event` or
|
||||
`time`; an anomaly counter's `netblock`, unless it counts an AS number or the
|
||||
whole service, its `asn`, for an AS number, its `name`, for a named netblock, or
|
||||
the `start` of a window in which it has requests or bytes. So does a ban whose
|
||||
the `start` of a window in which it has requests or bytes; a list's `url`,
|
||||
`fetched` or `lines`, which is `[]` for an empty list. So does a ban whose
|
||||
`cause` is not `limit`, `attack` or `admin`, alerts waiting for a destination
|
||||
that is not `webhook`, `slack` or `ntfy`, and an anomaly counter whose `scope`
|
||||
is not `client`, `net`, `asn`, `total` or `watch`. An answer's `asn` or
|
||||
`as_name` left out reads as empty.
|
||||
that is not `webhook`, `slack` or `ntfy`, an anomaly counter whose `scope` is
|
||||
not `client`, `net`, `asn`, `total` or `watch`, and a copy of a list with a line
|
||||
that would make its fetch fail. An answer's `asn` or `as_name` left out reads as
|
||||
empty.
|
||||
|
||||
While it runs, `smallwebwaf` watches `SWWAF_STATE_DIR` and takes in your edit of
|
||||
a state file as soon as you save it: what the file then holds replaces what
|
||||
@@ -987,14 +1056,14 @@ writes a file it takes in any edit made since, so your edit is not overwritten;
|
||||
a change `smallwebwaf` made after you opened the file, such as a new ban, is
|
||||
lost when you save over it. An edit that would stop the start, because it does
|
||||
not parse, has another `version`, leaves out a field an entry needs, gives a ban
|
||||
another `cause`, names another destination or gives an anomaly counter another
|
||||
`scope`, does not stop the running `smallwebwaf`: it keeps what it holds, and at
|
||||
the file's next write renames your file to `<name>.bad`, such as
|
||||
`bans.json.bad`, writes the file again from memory, logs the file and where the
|
||||
error is, and raises a `file_error` alert for it. It waits for that write
|
||||
because an editor's file can be read before the editor has finished writing it.
|
||||
Mend the `.bad` file and move it back. A file you remove is written again at its
|
||||
next write.
|
||||
another `cause`, names another destination, gives an anomaly counter another
|
||||
`scope` or gives a list's copy a line that would make its fetch fail, does not
|
||||
stop the running `smallwebwaf`: it keeps what it holds, and at the file's next
|
||||
write renames your file to `<name>.bad`, such as `bans.json.bad`, writes the
|
||||
file again from memory, logs the file and where the error is, and raises a
|
||||
`file_error` alert for it. It waits for that write because an editor's file can
|
||||
be read before the editor has finished writing it. Mend the `.bad` file and move
|
||||
it back. A file you remove is written again at its next write.
|
||||
|
||||
To ban a netblock, add an entry to `bans.json` with its `netblock`, its `start`
|
||||
and its `expires`, `null` for a ban that never ends; its `reason` and its
|
||||
@@ -1163,6 +1232,12 @@ scraped, and keeps this one as `exported_instance` unless the scrape sets
|
||||
database in use was read; and
|
||||
`smallwebwaf_lookup_database_read_failures_total`: the replacements of it that
|
||||
could not be read.
|
||||
- By `source`, the URL of each list `SWWAF_BLOCKLIST_URLS` or
|
||||
`SWWAF_ASN_LIMIT_PERCENT_URL` names: `smallwebwaf_reputation_hits_total`: the
|
||||
requests whose client the blocklist lists, a series that comes with the first;
|
||||
`smallwebwaf_reputation_failures_total`: the fetches of the list that failed;
|
||||
and `smallwebwaf_reputation_last_fetch_timestamp_seconds`: when the copy of it
|
||||
in use was fetched, `0` while there is none.
|
||||
- `smallwebwaf_tracked_clients`: the clients in the table of clients.
|
||||
- `smallwebwaf_state_file_writes_total`,
|
||||
`smallwebwaf_state_file_write_failures_total`,
|
||||
@@ -1354,9 +1429,9 @@ goes through the candidates one by one.
|
||||
readable JSON files, written regularly and at every stop, so a restart loses
|
||||
nothing. Edit a file, or add a rule file, and the running `smallwebwaf` picks
|
||||
up the change. Nothing is read from disk while serving a request. The files
|
||||
for the bans, the clients, the GeoJS answers and the alerts are built, with an
|
||||
edit taken in while running (see "State files" above); the others come with
|
||||
their features.
|
||||
for the bans, the clients, the GeoJS answers, the copies of the lists and the
|
||||
alerts are built, with an edit taken in while running (see "State files"
|
||||
above); the rest comes with its features.
|
||||
- Health checks, the metrics, and listing, adding and lifting bans or asking why
|
||||
a given address was refused, all on the one port every request uses: under
|
||||
`/_smallwebwaf/` on the app's own address, through traefik like any other
|
||||
@@ -1376,7 +1451,7 @@ For each request `smallwebwaf`:
|
||||
the deny list or currently banned;
|
||||
- looks up its AS number and country, and refuses it if that country is denied,
|
||||
or is not among the only ones allowed;
|
||||
- checks for a cached reputation verdict;
|
||||
- checks it against the blocklists, and for a cached reputation verdict;
|
||||
- picks the client's limit percentage from those;
|
||||
- checks the minute, hour and day request counters against the limits, and bans
|
||||
the client if it breaks one;
|
||||
@@ -1533,6 +1608,55 @@ country: `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` refuses it unless you list it in
|
||||
`SWWAF_UNKNOWN_LIMIT_PERCENT` sets its limits. Such addresses are never sent to
|
||||
GeoJS.
|
||||
|
||||
## Blocklists
|
||||
|
||||
`SWWAF_BLOCKLIST_URLS` names blocklists: text files of addresses and netblocks,
|
||||
one to a line, written as the Spamhaus DROP list,
|
||||
`https://www.spamhaus.org/drop/drop.txt`, is. Anything after a `;` or a `#` on a
|
||||
line is left out, and so is a line left blank. A bare address stands for itself
|
||||
alone, as in the settings. An IPv4-mapped address or netblock, such as
|
||||
`::ffff:192.0.2.0/120`, is read as the IPv4 one it stands for, here
|
||||
`192.0.2.0/24`, since a client's IPv4 address is checked as IPv4; a mapped
|
||||
netblock shorter than `/96` stands for none, and is not a netblock. None is
|
||||
named by default: a list judges a client by what others saw it do, while the
|
||||
defaults judge it by what it does to your service.
|
||||
|
||||
`smallwebwaf` fetches each list, and the file `SWWAF_ASN_LIMIT_PERCENT_URL`
|
||||
names, `SWWAF_BLOCKLIST_REFRESH` after it last fetched it or tried to, 24 hours
|
||||
by default and never less than one, one list after another. Since
|
||||
`reputation.json` keeps when each list was last tried, the fetch failed or not,
|
||||
at start `smallwebwaf` fetches at once only a list it has never tried, and one
|
||||
it last tried that long ago; any other waits its turn, so that restarts do not
|
||||
fetch a list more often. A fetch fails when the server answers other than `200`,
|
||||
when it does not finish within a minute, when the list is longer than 16 MiB, or
|
||||
when a line of it is not an address or a netblock, or for
|
||||
`SWWAF_ASN_LIMIT_PERCENT_URL`, not an AS number, `:` and a percentage. The copy
|
||||
fetched before then stays in use, and the failure is counted, logged and raised
|
||||
as a `source_failure` alert. 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.
|
||||
|
||||
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
|
||||
with one a list lists, as "What it does so far" above describes. `deny` suits
|
||||
lists of networks that send nothing legitimate, such as DROP; `limit:<percent>`
|
||||
suits lists of addresses shared with ordinary visitors, such as those of Tor's
|
||||
exits.
|
||||
|
||||
The Spamhaus DROP list is The Spamhaus Project's, https://www.spamhaus.org. Its
|
||||
terms, on its DROP page, ask that a product using it credit The Spamhaus Project
|
||||
and keep the list's date and copyright lines with the data, which the copy in
|
||||
`reputation.json` does; a service that uses DROP through `smallwebwaf` uses data
|
||||
from The Spamhaus Project, and should say so. They also ask that it be fetched
|
||||
automatically no more than once an hour, once a day being more than enough in
|
||||
most cases, and Spamhaus may block an address that fetches it more often. Each
|
||||
`smallwebwaf` fetches its own copy, on its own schedule, so on a host where
|
||||
several apps run it, sharing one address, their fetches can come less than an
|
||||
hour apart whatever `SWWAF_BLOCKLIST_REFRESH` is: each fetches a list again that
|
||||
long after its own last try, so those first started within the same hour, with
|
||||
the same refresh, keep fetching within the same hour.
|
||||
|
||||
## How the code is laid out
|
||||
|
||||
- `cmd/smallwebwaf`: the binary, which only calls `internal/smallwebwaf`.
|
||||
@@ -1546,15 +1670,15 @@ GeoJS.
|
||||
standard library's `httputil.ReverseProxy` within the timeouts and size
|
||||
limits, and writes the request's log line. Its `check` method is where a
|
||||
request is refused before anything reaches the app: for `SWWAF_DENY_NETS`, for
|
||||
a ban, for the country lists, for a rate limit, which bans the client, for a
|
||||
`block` or `ban` rule, the latter banning the client, and for an announced
|
||||
body over the size limit; in `observe` mode, only for the size limit, with
|
||||
what it would have refused for noted in the log line. A request under
|
||||
`/_smallwebwaf/` that `check` lets through is answered by `answerAdmin`
|
||||
instead of reaching the app. Once the answer to a request passed to the app
|
||||
has ended, `countBytes` counts its bytes for the byte limits, and once any
|
||||
request but the health check has ended, `countAnomalies` counts it for the
|
||||
anomaly thresholds.
|
||||
a ban, for the country lists, for a blocklist, for a rate limit, which bans
|
||||
the client, for a `block` or `ban` rule, the latter banning the client, and
|
||||
for an announced body over the size limit; in `observe` mode, only for the
|
||||
size limit, with what it would have refused for noted in the log line. A
|
||||
request under `/_smallwebwaf/` that `check` lets through is answered by
|
||||
`answerAdmin` instead of reaching the app. Once the answer to a request passed
|
||||
to the app has ended, `countBytes` counts its bytes for the byte limits, and
|
||||
once any request but the health check has ended, `countAnomalies` counts it
|
||||
for the anomaly thresholds.
|
||||
- `internal/metrics`: the metrics, counted as the other parts tell it what
|
||||
happened, and served in the Prometheus text format.
|
||||
- `internal/bans`: the ban ledger: each netblock's bans with their notes, how
|
||||
@@ -1567,6 +1691,10 @@ GeoJS.
|
||||
client's history and to the notes of its bans; or in the lookup database,
|
||||
which it reads again when the file is replaced. `internal/lookup/lookuptest`
|
||||
writes lookup databases for the tests.
|
||||
- `internal/reputation`: fetches the blocklists and the file
|
||||
`SWWAF_ASN_LIMIT_PERCENT_URL` names as they are due, keeps the last good copy
|
||||
of each, and tells which blocklists list an address and what percentage the
|
||||
file gives an AS number.
|
||||
- `internal/ratelimit`: the table of clients: counts each client's requests and
|
||||
bytes, tells when they take it over a rate limit or a byte limit, and keeps
|
||||
each client's history.
|
||||
|
||||
Reference in New Issue
Block a user