Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9af927c8cf | ||
|
|
93ce3b210c |
@@ -211,11 +211,9 @@ ends the handling. Nothing of a refused request reaches the app.
|
|||||||
|
|
||||||
Two terms recur below. A clear sign of attack is a request that matches a rule
|
Two terms recur below. A clear sign of attack is a request that matches a rule
|
||||||
whose action is `ban`, or asks for a trap path (steps 9 and 10). An offence is a
|
whose action is `ban`, or asks for a trap path (steps 9 and 10). An offence is a
|
||||||
request that counts against its client in its history. It is one of:
|
request that counts against its client in the client's history: a clear sign of
|
||||||
|
attack, a broken limit, or a refusal by a rule, by the Core Rule Set or for a
|
||||||
- a clear sign of attack;
|
missing or wrong token.
|
||||||
- a broken limit;
|
|
||||||
- a refusal by a rule, by the Core Rule Set or for a missing or wrong token.
|
|
||||||
|
|
||||||
1. `GET /_smallwebwaf/healthz` is answered `200` with `ok` at once, before any
|
1. `GET /_smallwebwaf/healthz` is answered `200` with `ok` at once, before any
|
||||||
check and without asking the app.
|
check and without asking the app.
|
||||||
@@ -355,8 +353,8 @@ configuration before enforcing it.
|
|||||||
`SWWAF_MODE=observe` and ask for `/.env`. The request reaches the app. Its log
|
`SWWAF_MODE=observe` and ask for `/.env`. The request reaches the app. Its log
|
||||||
line has `"would_action":"banned"` and `"rule_ids":["env-file"]`. In `enforce`
|
line has `"would_action":"banned"` and `"rule_ids":["env-file"]`. In `enforce`
|
||||||
mode the same request bans your address for seven days, and your next request
|
mode the same request bans your address for seven days, and your next request
|
||||||
makes the ban permanent. Lift the ban by editing `bans.json` or through the
|
for good. Lift the ban by editing `bans.json` or through the ban endpoint (see
|
||||||
ban endpoint (see "Bans" below).
|
"Bans" below).
|
||||||
- With `SWWAF_METRICS_TOKEN` set,
|
- With `SWWAF_METRICS_TOKEN` set,
|
||||||
`curl -H "Authorization: Bearer $TOKEN" https://<site>/_smallwebwaf/metrics`
|
`curl -H "Authorization: Bearer $TOKEN" https://<site>/_smallwebwaf/metrics`
|
||||||
answers with the metrics (see "Metrics" below).
|
answers with the metrics (see "Metrics" below).
|
||||||
@@ -419,8 +417,8 @@ The forms of values:
|
|||||||
`SWWAF_ALERT_NTFY_URL` is set, it may hold no control character. While
|
`SWWAF_ALERT_NTFY_URL` is set, it may hold no control character. While
|
||||||
`SWWAF_LOG_REMOTE_URL` is set and `SWWAF_LOG_REMOTE_APP_NAME` is not, it must
|
`SWWAF_LOG_REMOTE_URL` is set and `SWWAF_LOG_REMOTE_APP_NAME` is not, it must
|
||||||
be 1 to 48 printable ASCII characters without a space.
|
be 1 to 48 printable ASCII characters without a space.
|
||||||
- `SWWAF_MODE` (default `enforce`): `enforce`, or `observe` to ban no one and
|
- `SWWAF_MODE` (default `enforce`): `enforce`, or `observe` to refuse nothing
|
||||||
log what `enforce` would have done (see "What happens to a request" above).
|
and log what would have been refused (see "What happens to a request" above).
|
||||||
- `SWWAF_TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`,
|
- `SWWAF_TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`,
|
||||||
the private address ranges): the netblocks whose `X-Forwarded-For` is
|
the private address ranges): the netblocks whose `X-Forwarded-For` is
|
||||||
believed, normally where traefik comes from. Set but empty, it trusts nothing:
|
believed, normally where traefik comes from. Set but empty, it trusts nothing:
|
||||||
@@ -552,8 +550,8 @@ ban.
|
|||||||
Clients of the AS numbers and countries listed here get a percentage of every
|
Clients of the AS numbers and countries listed here get a percentage of every
|
||||||
rate limit and byte limit. The same rules then ban them after fewer requests. A
|
rate limit and byte limit. The same rules then ban them after fewer requests. A
|
||||||
lowered limit is rounded down. `0` is a zero allowance: the client's first
|
lowered limit is rounded down. `0` is a zero allowance: the client's first
|
||||||
request bans it. Each client is counted on its own. An AS number or a country
|
request bans it. Each client is counted on its own. No budget is shared by a
|
||||||
has no shared budget, since one abuser could use it up for everyone in it.
|
whole AS number or country, which one abuser could use up.
|
||||||
|
|
||||||
- `SWWAF_ASN_LIMIT_PERCENT` (default empty): a list of AS numbers, each with `:`
|
- `SWWAF_ASN_LIMIT_PERCENT` (default empty): a list of AS numbers, each with `:`
|
||||||
and a percentage, such as `AS14061:50,AS16276:50,AS45102:25`. An AS number
|
and a percentage, such as `AS14061:50,AS16276:50,AS45102:25`. An AS number
|
||||||
@@ -624,8 +622,8 @@ do. The defaults judge it by what it does to your service.
|
|||||||
- `SWWAF_REPUTATION_ACTION` (default `limit:25`): what is done with a client on
|
- `SWWAF_REPUTATION_ACTION` (default `limit:25`): what is done with a client on
|
||||||
a DNS blocklist, or one whose AbuseIPDB score is a hit. `deny`,
|
a DNS blocklist, or one whose AbuseIPDB score is a hit. `deny`,
|
||||||
`limit:<percent>` or `log`, as for `SWWAF_BLOCKLIST_ACTION`. Such a verdict is
|
`limit:<percent>` or `log`, as for `SWWAF_BLOCKLIST_ACTION`. Such a verdict is
|
||||||
less certain than the Spamhaus DROP list, so by default the client gets a
|
less certain than a list like DROP, so by default the client gets a quarter of
|
||||||
quarter of every limit.
|
every limit.
|
||||||
- `SWWAF_REPUTATION_CACHE_TTL` (default `24h`): how long a zone's verdict or
|
- `SWWAF_REPUTATION_CACHE_TTL` (default `24h`): how long a zone's verdict or
|
||||||
AbuseIPDB's score is used. A duration; it cannot be `off`.
|
AbuseIPDB's score is used. A duration; it cannot be `off`.
|
||||||
- `SWWAF_REPUTATION_TIMEOUT` (default `2s`): how long a query to a zone or a
|
- `SWWAF_REPUTATION_TIMEOUT` (default `2s`): how long a query to a zone or a
|
||||||
@@ -668,11 +666,10 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
`.git/`, `.gitignore` or `package.json`.
|
`.git/`, `.gitignore` or `package.json`.
|
||||||
|
|
||||||
In front of a code forge these refuse git over HTTP, uploads and views of
|
In front of a code forge these refuse git over HTTP, uploads and views of
|
||||||
ordinary files. The default rule file still bans probes for version control
|
ordinary files. The default rule file bans those probes at the site root
|
||||||
directories and secret files at the site root. A list given replaces the
|
instead. A list given replaces the default, so include them. An id must be a
|
||||||
default, so include them. An id must be a whole number above zero. An id
|
whole number above zero. An id from 900000 to 900999 is refused: those are
|
||||||
from 900000 to 900999 is refused: those are the rules that set the Core Rule
|
the rules that set the Core Rule Set up, and `smallwebwaf`'s own.
|
||||||
Set up, and `smallwebwaf`'s own.
|
|
||||||
|
|
||||||
- `SWWAF_WAF_EXEMPT_PATHS` (default empty): path prefixes the Core Rule Set does
|
- `SWWAF_WAF_EXEMPT_PATHS` (default empty): path prefixes the Core Rule Set does
|
||||||
not inspect, matched as `SWWAF_RATE_LIMIT_EXEMPT_PATHS` is.
|
not inspect, matched as `SWWAF_RATE_LIMIT_EXEMPT_PATHS` is.
|
||||||
@@ -797,8 +794,7 @@ thresholds per client are:
|
|||||||
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_MINUTE`;
|
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_MINUTE`;
|
||||||
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_HOUR`.
|
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_HOUR`.
|
||||||
|
|
||||||
The other scopes' names have another prefix before the same four endings, such
|
The other scopes have the same four names with `CLIENT` replaced:
|
||||||
as `REQUESTS_PER_MINUTE`:
|
|
||||||
|
|
||||||
- `SWWAF_ANOMALY_NET_...`: per netblock around a client. Two settings give the
|
- `SWWAF_ANOMALY_NET_...`: per netblock around a client. Two settings give the
|
||||||
netblock's length:
|
netblock's length:
|
||||||
@@ -847,8 +843,6 @@ docker run \
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fixed limits
|
|
||||||
|
|
||||||
A few limits are fixed rather than settings:
|
A few limits are fixed rather than settings:
|
||||||
|
|
||||||
- at most 100,000 GeoJS answers are kept, for 7 days each;
|
- at most 100,000 GeoJS answers are kept, for 7 days each;
|
||||||
@@ -859,13 +853,9 @@ A few limits are fixed rather than settings:
|
|||||||
## Bans
|
## Bans
|
||||||
|
|
||||||
A ban refuses every request from a netblock until it ends, with
|
A ban refuses every request from a netblock until it ends, with
|
||||||
`SWWAF_BAN_RESPONSE`. The netblock is one of:
|
`SWWAF_BAN_RESPONSE`. The netblock is the client's IPv4 address, or the netblock
|
||||||
|
around it, `SWWAF_BAN_SCOPE_V4_PREFIX` long, or the client's IPv6 group. A
|
||||||
- the client's IPv4 address;
|
banned client is not looked up and not counted by rate limiting.
|
||||||
- the netblock around it, `SWWAF_BAN_SCOPE_V4_PREFIX` long;
|
|
||||||
- the client's IPv6 group.
|
|
||||||
|
|
||||||
A banned client is not looked up and not counted by rate limiting.
|
|
||||||
|
|
||||||
`smallwebwaf` makes bans for three causes:
|
`smallwebwaf` makes bans for three causes:
|
||||||
|
|
||||||
@@ -901,8 +891,8 @@ Each ban carries notes for deciding whether to lift it:
|
|||||||
refused;
|
refused;
|
||||||
- how many earlier bans the netblock had, by cause.
|
- how many earlier bans the netblock had, by cause.
|
||||||
|
|
||||||
`smallwebwaf` keeps at most `SWWAF_MAX_BANS` of the bans it made. Past that, one
|
At most `SWWAF_MAX_BANS` bans `smallwebwaf` made are kept. Past that, one ban is
|
||||||
ban is dropped: the earliest ban of the netblock that has gone longest without a
|
dropped: the earliest ban of the netblock that has gone longest without a
|
||||||
request. Bans whose cause is `admin` are never dropped. Those are the bans you
|
request. Bans whose cause is `admin` are never dropped. Those are the bans you
|
||||||
make, and the bans `smallwebwaf` made that you keep by setting their cause.
|
make, and the bans `smallwebwaf` made that you keep by setting their cause.
|
||||||
|
|
||||||
@@ -1006,17 +996,13 @@ scanner-agent user_agent ban (?i)\b(sqlmap|nikto|nuclei|wpscan)\b
|
|||||||
takes time linear in the text it reads. It matches anywhere in the target
|
takes time linear in the text it reads. It matches anywhere in the target
|
||||||
unless anchored with `^` and `$`. `(?i)` at its front makes it ignore case.
|
unless anchored with `^` and `$`. `(?i)` at its front makes it ignore case.
|
||||||
|
|
||||||
These stop the start, with a message naming the file and line:
|
A line that is not a rule stops the start, with a message naming the file and
|
||||||
|
line. So does a bad `header:` name, a rule for `Host` or `Transfer-Encoding`, a
|
||||||
- a line that is not a rule;
|
regex that does not compile, or an id used twice. While it runs, `smallwebwaf`
|
||||||
- a bad `header:` name, or a rule for `Host` or `Transfer-Encoding`;
|
watches the directory. It reads the files again once the directory has had no
|
||||||
- a regex that does not compile;
|
change for 2 seconds, so that a file still being written is read whole. If the
|
||||||
- an id used twice.
|
files then hold such an error, the rules stay as they were. The log and a
|
||||||
|
`file_error` alert name the file and line.
|
||||||
While it runs, `smallwebwaf` watches the directory. It reads the files again
|
|
||||||
once the directory has had no change for 2 seconds, so that a file still being
|
|
||||||
written is read whole. If the files then hold such an error, the rules stay as
|
|
||||||
they were. The log and a `file_error` alert name the file and line.
|
|
||||||
|
|
||||||
The image ships one file, `share/rules.d/00-default.rules`, installed as
|
The image ships one file, `share/rules.d/00-default.rules`, installed as
|
||||||
`/etc/smallwebwaf/rules.d/00-default.rules`. Its rules:
|
`/etc/smallwebwaf/rules.d/00-default.rules`. Its rules:
|
||||||
@@ -1071,18 +1057,14 @@ setting undoes them.
|
|||||||
address or `localhost` (rules 931100 and 934110). Git credential helpers ask
|
address or `localhost` (rules 931100 and 934110). Git credential helpers ask
|
||||||
to be sent back to such a URL.
|
to be sent back to such a URL.
|
||||||
- Ten query parameters are not checked against three lists: system files
|
- Ten query parameters are not checked against three lists: system files
|
||||||
(930120), shell paths (932160) and command names (932260). They are:
|
(930120), shell paths (932160) and command names (932260). They are `path`,
|
||||||
- `path`, `files`, `skip-to` and `sub_path`;
|
`files`, `skip-to`, `sub_path`, `ref`, `sha`, `branch`, `workflow`,
|
||||||
- `ref`, `sha`, `branch` and `workflow`;
|
`artifactName` and `redirect_to`. A file such as `.gitignore` or a branch such
|
||||||
- `artifactName` and `redirect_to`.
|
as `docker-build` then gets through there. So does an attack only those three
|
||||||
|
rules catch, such as `|cat /etc/passwd`. Path traversal and SQL injection are
|
||||||
A file such as `.gitignore` or a branch such as `docker-build` then gets
|
still refused there. Names are matched without regard to case. An app that
|
||||||
through there. So does an attack only those three rules catch, such as
|
uses one of these parameters as a file on the server, or passes it to a shell,
|
||||||
`|cat /etc/passwd`. Path traversal and SQL injection are still refused
|
has to check it itself.
|
||||||
there. Names are matched without regard to case. An app that uses one of
|
|
||||||
these parameters as a file on the server, or passes it to a shell, has to
|
|
||||||
check it itself.
|
|
||||||
|
|
||||||
- The cookies `gitea_flash` and `redirect_to` are not read. `Referer` is not
|
- The cookies `gitea_flash` and `redirect_to` are not read. `Referer` is not
|
||||||
checked for a Unix command given without arguments (932340), or for Java
|
checked for a Unix command given without arguments (932340), or for Java
|
||||||
starting a process (944110).
|
starting a process (944110).
|
||||||
@@ -1140,13 +1122,9 @@ refused ones included:
|
|||||||
{"type":"request","time":"2026-10-03T12:00:00.123Z","instance":"fsn1app1/gitea","client_ip":"203.0.113.9","method":"GET","scheme":"https","host":"app.example","path":"/","query":"","protocol":"HTTP/1.1","status":200,"request_bytes":0,"response_bytes":5120,"referer":"","user_agent":"curl/8.9.1","request_id":"7Q2NHZ4KJ3VXW5YB6R3MEFTD2A","peer_ip":"172.18.0.2","forwarded_for":"203.0.113.9","client_group":"203.0.113.9/32","asn":"AS64496","as_name":"Example Net","country":"DE","request_headers":{"accept":"*/*"},"response_content_type":"text/html; charset=utf-8","upstream_status":200,"action":"forward","counts":{"minute":1,"hour":12,"day":40,"minute_bytes":5120,"hour_bytes":61440,"day_bytes":204800},"duration_total":3.217,"duration_checks":0.041,"duration_upstream_connect":0.052,"duration_upstream_first_byte":2.874,"duration_upstream_total":3.104}
|
{"type":"request","time":"2026-10-03T12:00:00.123Z","instance":"fsn1app1/gitea","client_ip":"203.0.113.9","method":"GET","scheme":"https","host":"app.example","path":"/","query":"","protocol":"HTTP/1.1","status":200,"request_bytes":0,"response_bytes":5120,"referer":"","user_agent":"curl/8.9.1","request_id":"7Q2NHZ4KJ3VXW5YB6R3MEFTD2A","peer_ip":"172.18.0.2","forwarded_for":"203.0.113.9","client_group":"203.0.113.9/32","asn":"AS64496","as_name":"Example Net","country":"DE","request_headers":{"accept":"*/*"},"response_content_type":"text/html; charset=utf-8","upstream_status":200,"action":"forward","counts":{"minute":1,"hour":12,"day":40,"minute_bytes":5120,"hour_bytes":61440,"day_bytes":204800},"duration_total":3.217,"duration_checks":0.041,"duration_upstream_connect":0.052,"duration_upstream_first_byte":2.874,"duration_upstream_total":3.104}
|
||||||
```
|
```
|
||||||
|
|
||||||
A field that does not apply to a request is left out. Every line has:
|
A field that does not apply to a request is left out. Every line has `type`, the
|
||||||
|
fields from `time` to `user_agent`, `request_id`, `peer_ip`, `client_group`,
|
||||||
- `type`;
|
`asn`, `as_name`, `country`, `action` and `duration_total`.
|
||||||
- the fields from `time` to `user_agent`;
|
|
||||||
- `request_id`, `peer_ip` and `client_group`;
|
|
||||||
- `asn`, `as_name` and `country`;
|
|
||||||
- `action` and `duration_total`.
|
|
||||||
|
|
||||||
- `time` is when the request arrived, in UTC. `instance` is
|
- `time` is when the request arrived, in UTC. `instance` is
|
||||||
`SWWAF_INSTANCE_NAME`. `scheme` is the `X-Forwarded-Proto` a trusted proxy
|
`SWWAF_INSTANCE_NAME`. `scheme` is the `X-Forwarded-Proto` a trusted proxy
|
||||||
@@ -1201,20 +1179,16 @@ A field that does not apply to a request is left out. Every line has:
|
|||||||
limit; `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit; or
|
limit; `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit; or
|
||||||
`error_burst`. `offence` is then `limit`. A request whose bytes broke a limit
|
`error_burst`. `offence` is then `limit`. A request whose bytes broke a limit
|
||||||
was passed on whole, so its `action` stays `forward`.
|
was passed on whole, so its `action` stays `forward`.
|
||||||
- `reputation` lists the sources the client is on, in this order:
|
- `reputation` lists the sources the client is on: blocklist URLs, the CrowdSec
|
||||||
- blocklist URLs;
|
decision list's URL, DNS blocklist zones, then `abuseipdb`.
|
||||||
- the CrowdSec decision list's URL;
|
|
||||||
- DNS blocklist zones;
|
|
||||||
- `abuseipdb`.
|
|
||||||
- `ban_expires` is when the client's ban ends, for a request that made one or
|
- `ban_expires` is when the client's ban ends, for a request that made one or
|
||||||
was refused under one: a time, or `permanent`.
|
was refused under one: a time, or `permanent`.
|
||||||
- The timings are in milliseconds. `duration_total` runs from the request's
|
- The timings are in milliseconds. `duration_total` runs from the request's
|
||||||
headers being read to its log line. `duration_checks` runs to the end of the
|
headers being read to its log line. `duration_checks` runs to the end of the
|
||||||
checks, and `duration_waf` is the part of that the Core Rule Set took. The
|
checks, and `duration_waf` is the part of that the Core Rule Set took. The
|
||||||
three `duration_upstream_` timings run from the hand-over to the app:
|
three `duration_upstream_` timings run from the hand-over to the app:
|
||||||
- `connect`, until there was a connection;
|
`connect` until there was a connection, `first_byte` until the first byte of
|
||||||
- `first_byte`, until the first byte of the answer;
|
the answer, and `total` until the end.
|
||||||
- `total`, until the end.
|
|
||||||
|
|
||||||
No body is logged, and no header but those above. `smallwebwaf`'s own messages
|
No body is logged, and no header but those above. `smallwebwaf`'s own messages
|
||||||
(start, the settings, stop, errors) share the stream as JSON lines marked
|
(start, the settings, stop, errors) share the stream as JSON lines marked
|
||||||
@@ -1257,15 +1231,11 @@ its event is in `SWWAF_ALERT_EVENTS`:
|
|||||||
- `reputation_hit`: a request from a client on a blocklist, the CrowdSec
|
- `reputation_hit`: a request from a client on a blocklist, the CrowdSec
|
||||||
decision list or a DNS blocklist, or with an AbuseIPDB score that is a hit.
|
decision list or a DNS blocklist, or with an AbuseIPDB score that is a hit.
|
||||||
One alert per source.
|
One alert per source.
|
||||||
- `source_failure`: one of:
|
- `source_failure`: GeoJS failing, a list that cannot be fetched, a DNS
|
||||||
- GeoJS failing;
|
blocklist or AbuseIPDB failing or refusing a query, or the day's AbuseIPDB
|
||||||
- a list that cannot be fetched;
|
checks used up.
|
||||||
- a DNS blocklist or AbuseIPDB failing or refusing a query;
|
- `file_error`: a rule file or state file edit with an error, a state file that
|
||||||
- the day's AbuseIPDB checks used up.
|
cannot be written, or a replacement lookup database that cannot be read.
|
||||||
- `file_error`: one of:
|
|
||||||
- a rule file or state file edit with an error;
|
|
||||||
- a state file that cannot be written;
|
|
||||||
- a replacement lookup database that cannot be read.
|
|
||||||
|
|
||||||
The bans you make raise no alert. In `observe` mode, a request that would have
|
The bans you make raise no alert. In `observe` mode, a request that would have
|
||||||
made a ban still raises the ban's alert, with `mode` set to `observe` in its
|
made a ban still raises the ban's alert, with `mode` set to `observe` in its
|
||||||
@@ -1341,9 +1311,9 @@ line for each of these that the alert has:
|
|||||||
- the file, the source, the error and the mode from `detail`;
|
- the file, the source, the error and the mode from `detail`;
|
||||||
- the suppressed repeats, if any.
|
- the suppressed repeats, if any.
|
||||||
|
|
||||||
Slack gets the title in bold and the text below it, with `&`, `<` and `>`
|
Slack is posted the title in bold and the text below it, with `&`, `<` and `>`
|
||||||
escaped. ntfy gets the text, with the title in `Title`, the token if set, and a
|
escaped. ntfy is posted the text, with the title in `Title`, the token if set,
|
||||||
priority and a tag by event:
|
and a priority and a tag by event:
|
||||||
|
|
||||||
- `ban`: priority `default`, tag `no_entry`.
|
- `ban`: priority `default`, tag `no_entry`.
|
||||||
- `permanent_ban`: priority `high`, tag `no_entry`.
|
- `permanent_ban`: priority `high`, tag `no_entry`.
|
||||||
@@ -1357,9 +1327,8 @@ A repeat is an alert for the same event on the same netblock, source, file or
|
|||||||
anomaly scope, less than `SWWAF_ALERT_COOLDOWN` after the last. It is held back
|
anomaly scope, less than `SWWAF_ALERT_COOLDOWN` after the last. It is held back
|
||||||
and counted, and the next alert sent gives the count. Past
|
and counted, and the next alert sent gives the count. Past
|
||||||
`SWWAF_ALERT_MAX_PER_HOUR` alerts in an hour, the rest are held back and counted
|
`SWWAF_ALERT_MAX_PER_HOUR` alerts in an hour, the rest are held back and counted
|
||||||
by event. Once the hour ends, one alert with the event `summary` gives those
|
by event. Once the hour ends, one alert with the event `summary` sums them up,
|
||||||
counts. It also gives the repeats a cooldown held back that no later alert
|
with the repeats of the cooldowns that ran out.
|
||||||
reported.
|
|
||||||
|
|
||||||
Each destination has a queue of at most 1000 alerts, sent one at a time, so a
|
Each destination has a queue of at most 1000 alerts, sent one at a time, so a
|
||||||
slow destination holds up nothing. A `2xx` answer takes an alert. A `4xx` other
|
slow destination holds up nothing. A `2xx` answer takes an alert. A `4xx` other
|
||||||
@@ -1449,11 +1418,9 @@ keeps it as `exported_instance` unless the scrape sets `honor_labels: true`.
|
|||||||
- its offences by kind: `limit`, `attack`, `rule_blocked`, `waf_blocked` and
|
- its offences by kind: `limit`, `attack`, `rule_blocked`, `waf_blocked` and
|
||||||
`token_refused`.
|
`token_refused`.
|
||||||
- `lookups.json`: GeoJS's answers, one per line.
|
- `lookups.json`: GeoJS's answers, one per line.
|
||||||
- `reputation.json`:
|
- `reputation.json`: the last good copy of each list fetched from a URL, with
|
||||||
- the last good copy of each list fetched from a URL, with when it was last
|
when it was last tried; each DNS blocklist verdict still in use; and
|
||||||
tried;
|
AbuseIPDB's scores and the checks spent today.
|
||||||
- each DNS blocklist verdict still in use;
|
|
||||||
- AbuseIPDB's scores, and the checks spent today.
|
|
||||||
- `alerts.json`: the alert cooldowns, the hour under way, the alerts waiting for
|
- `alerts.json`: the alert cooldowns, the hour under way, the alerts waiting for
|
||||||
each destination, and the anomaly counters.
|
each destination, and the anomaly counters.
|
||||||
|
|
||||||
@@ -1496,8 +1463,8 @@ AS number and country. These addresses are never sent:
|
|||||||
- one under a ban;
|
- one under a ban;
|
||||||
- a private, loopback or link-local address, which no source can place.
|
- a private, loopback or link-local address, which no source can place.
|
||||||
|
|
||||||
For an IPv6 visitor, GeoJS is asked about the first address of its group. Each
|
An IPv6 visitor is asked about by the first address of its group. Each answer is
|
||||||
answer is kept for seven days, in memory and in `lookups.json`.
|
kept for seven days, in memory and in `lookups.json`.
|
||||||
|
|
||||||
A request waits for its client's first answer only when a setting needs the
|
A request waits for its client's first answer only when a setting needs the
|
||||||
answer before the request goes on. It waits up to `SWWAF_LOOKUP_TIMEOUT`. The
|
answer before the request goes on. It waits up to `SWWAF_LOOKUP_TIMEOUT`. The
|
||||||
@@ -1523,11 +1490,11 @@ address it does not hold counts as from an unknown country. `smallwebwaf` reads
|
|||||||
the whole file at start. It reads the file again once it has gone 2 seconds
|
the whole file at start. It reads the file again once it has gone 2 seconds
|
||||||
without a change after you replace it. A replacement it cannot read is logged
|
without a change after you replace it. A replacement it cannot read is logged
|
||||||
and alerted, and the file read before stays in use. Mount the directory that
|
and alerted, and the file read before stays in use. Mount the directory that
|
||||||
holds the file, not the file itself. Many editors and download tools save a file
|
holds the file rather than the file itself: many editors and download tools
|
||||||
by replacing it, and a container keeps seeing the old file when only the file is
|
replace a file when they save it, and a container keeps seeing the old one when
|
||||||
mounted. IPinfo releases the database under the Creative Commons
|
the file alone is mounted. IPinfo releases the database under the Creative
|
||||||
Attribution-ShareAlike 4.0 licence. It asks for a link to IPinfo on the site
|
Commons Attribution-ShareAlike 4.0 licence. It asks for a link to IPinfo on the
|
||||||
that uses it, such as "IP address data is powered by IPinfo".
|
site that uses it, such as "IP address data is powered by IPinfo".
|
||||||
|
|
||||||
## Blocklists
|
## Blocklists
|
||||||
|
|
||||||
@@ -1548,8 +1515,8 @@ list again before its time. A fetch fails when:
|
|||||||
`AS<number>:<percent>`.
|
`AS<number>:<percent>`.
|
||||||
|
|
||||||
The copy fetched before stays in use. The failure is logged and raised as a
|
The copy fetched before stays in use. The failure is logged and raised as a
|
||||||
`source_failure` alert. The log, the alerts, the ban notes and the metrics name
|
`source_failure` alert. Each list is named by its URL in the log, the alerts,
|
||||||
each list by its URL, so keep a secret out of it.
|
the ban notes and the metrics, so keep a secret out of it.
|
||||||
|
|
||||||
`deny` suits lists of networks that send nothing legitimate, such as DROP.
|
`deny` suits lists of networks that send nothing legitimate, such as DROP.
|
||||||
`limit:<percent>` suits lists of addresses shared with ordinary visitors, such
|
`limit:<percent>` suits lists of addresses shared with ordinary visitors, such
|
||||||
@@ -1596,23 +1563,18 @@ client that has committed an offence is checked. An offence is one of:
|
|||||||
|
|
||||||
The check runs in the background, at the client's first request after the
|
The check runs in the background, at the client's first request after the
|
||||||
offence that no ban refuses, so no request waits. The score applies to the
|
offence that no ban refuses, so no request waits. The score applies to the
|
||||||
client's later requests. For an IPv6 client, AbuseIPDB is asked about the
|
client's later requests. An IPv6 group is checked by the address of the request
|
||||||
address of the request that set off the check, and the score is used for the
|
that has it checked, and the score is used for the whole group. A score at or
|
||||||
whole group. A score at or over `SWWAF_ABUSEIPDB_MIN_SCORE` is a hit, and
|
over `SWWAF_ABUSEIPDB_MIN_SCORE` is a hit, and `SWWAF_REPUTATION_ACTION` says
|
||||||
`SWWAF_REPUTATION_ACTION` says what is done. Each score is kept for
|
what is done. Each score is kept for `SWWAF_REPUTATION_CACHE_TTL`.
|
||||||
`SWWAF_REPUTATION_CACHE_TTL`.
|
|
||||||
|
|
||||||
At most `SWWAF_ABUSEIPDB_DAILY_BUDGET` checks are made in a day, counted in UTC
|
At most `SWWAF_ABUSEIPDB_DAILY_BUDGET` checks are made in a day, counted in UTC
|
||||||
and kept in `reputation.json`. A restart does not make the budget whole again.
|
and kept in `reputation.json`. A restart does not make the budget whole again.
|
||||||
The check that spends the last of the budget is logged and alerted. No client is
|
The check that spends the last of the budget is logged and alerted. No client is
|
||||||
checked until the day ends. A check fails when:
|
checked until the day ends. A check fails when AbuseIPDB answers other than
|
||||||
|
`200`, gives no score, or does not answer within `SWWAF_REPUTATION_TIMEOUT`. A
|
||||||
- AbuseIPDB answers other than `200`;
|
failure is logged and alerted, and no client is checked for a minute. The key is
|
||||||
- the answer gives no score;
|
sent in the `Key` header and nowhere else, and is never logged.
|
||||||
- no answer comes within `SWWAF_REPUTATION_TIMEOUT`.
|
|
||||||
|
|
||||||
A failure is logged and alerted, and no client is checked for a minute. The key
|
|
||||||
is sent in the `Key` header and nowhere else, and is never logged.
|
|
||||||
|
|
||||||
## CrowdSec
|
## CrowdSec
|
||||||
|
|
||||||
@@ -1627,15 +1589,13 @@ A request is refused when a decision still in force names its client. The
|
|||||||
client's netblock is banned until the decision ends, with the cause `crowdsec`.
|
client's netblock is banned until the decision ends, with the cause `crowdsec`.
|
||||||
Only a client that sends a request is banned, so a long list does not fill
|
Only a client that sends a request is banned, so a long list does not fill
|
||||||
`bans.json`. Such a ban is never made permanent, and does not lengthen the
|
`bans.json`. Such a ban is never made permanent, and does not lengthen the
|
||||||
netblock's next ban for a broken limit. If you lift a ban while the decision is
|
netblock's next ban for a broken limit. A ban you lift while the decision is in
|
||||||
in force, the client's next request makes another. Delete the decision in
|
force is followed by another at the client's next request. Delete the decision
|
||||||
CrowdSec first, such as with `cscli decisions delete --ip 203.0.113.9`, and lift
|
in CrowdSec first, such as with `cscli decisions delete --ip 203.0.113.9`, and
|
||||||
the ban once the list has been fetched again. A fetch fails when:
|
lift the ban once the list has been fetched again. A fetch fails when the engine
|
||||||
|
answers other than `200`, a redirect included. It also fails when the fetch does
|
||||||
- the engine answers other than `200`, a redirect included;
|
not finish within a minute, the answer is longer than 16 MiB, or the answer is
|
||||||
- the fetch does not finish within a minute;
|
not a list of decisions.
|
||||||
- the answer is longer than 16 MiB;
|
|
||||||
- the answer is not a list of decisions.
|
|
||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
|
|
||||||
@@ -1676,8 +1636,8 @@ candidates one by one.
|
|||||||
or `SIGINT`. Then it stops, writing the state files. Run as
|
or `SIGINT`. Then it stops, writing the state files. Run as
|
||||||
`smallwebwaf healthcheck`, it is the image's health check instead.
|
`smallwebwaf healthcheck`, it is the image's health check instead.
|
||||||
- `internal/config`: reads the settings, the one place they are read.
|
- `internal/config`: reads the settings, the one place they are read.
|
||||||
- `internal/proxy`: what happens to each request. It works out the client and
|
- `internal/proxy`: what happens to each request. It works out the client, runs
|
||||||
runs the checks in `check`. It passes the request to the app with the standard
|
the checks in `check`, and passes the request to the app with the standard
|
||||||
library's `httputil.ReverseProxy`, within the timeouts and size limits. It
|
library's `httputil.ReverseProxy`, within the timeouts and size limits. It
|
||||||
answers its own endpoints in `answerAdmin`. Once the request has ended, it
|
answers its own endpoints in `answerAdmin`. Once the request has ended, it
|
||||||
counts bytes, refusals and anomalies, and writes the log line.
|
counts bytes, refusals and anomalies, and writes the log line.
|
||||||
@@ -1723,16 +1683,13 @@ Besides the standard library, it uses:
|
|||||||
|
|
||||||
This repository adheres to the
|
This repository adheres to the
|
||||||
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
[Scripts to Rule Them All](https://github.com/github/scripts-to-rule-them-all)
|
||||||
standard. The scripts in `script/` are the entrypoints for working on it, and
|
standard: the scripts in `script/` are the entrypoints for working on it, and
|
||||||
the `Makefile` targets are thin shims that call them. The scripts are POSIX sh.
|
the `Makefile` targets are thin shims that call them. The scripts are POSIX sh.
|
||||||
The tests and the linter run in Docker, as phases of the `Dockerfile`, and never
|
The tests and the linter run in Docker, as phases of the `Dockerfile`, and never
|
||||||
on the host.
|
on the host.
|
||||||
|
|
||||||
- `script/bootstrap`: installs what the other scripts need on the host.
|
- `script/bootstrap`: installs what the other scripts need on the host: `make`,
|
||||||
`make bootstrap`. They need:
|
`git`, `curl`, Go for `gofmt`, node and yarn, and prettier. `make bootstrap`.
|
||||||
- `make`, `git` and `curl`;
|
|
||||||
- Go, for `gofmt`;
|
|
||||||
- node, yarn and prettier.
|
|
||||||
- `script/setup`: readies a fresh clone: `script/bootstrap`, then
|
- `script/setup`: readies a fresh clone: `script/bootstrap`, then
|
||||||
`script/install-precommit`. `make setup`.
|
`script/install-precommit`. `make setup`.
|
||||||
- `script/projectname`: prints `smallwebwaf`, which the other scripts tag their
|
- `script/projectname`: prints `smallwebwaf`, which the other scripts tag their
|
||||||
|
|||||||
Reference in new issue
Block a user