README reworked after review: every finding fixed, every sentence reread
check / check (push) Waiting to run
check / check (push) Waiting to run
The eighteen findings of the review on the PR are fixed: statements the code does not bear out, the stacked "a client a blocklist lists" form, late verbs, sentences over thirty words, comma chains where bullets belong, sentences a newcomer reads twice, and one project-plan remark. Every other sentence was reread against the same rules, and the ones that still put the object before the subject were rewritten. Model: fable-5-1
This commit is contained in:
@@ -8,8 +8,8 @@ scanners and other abusers before they reach the app.
|
|||||||
A web application firewall reads each request on its way to a web app and
|
A web application firewall reads each request on its way to a web app and
|
||||||
refuses the ones that look like abuse. `smallwebwaf` is also a reverse proxy: it
|
refuses the ones that look like abuse. `smallwebwaf` is also a reverse proxy: it
|
||||||
accepts requests on the app's behalf, passes them to the app, and passes the
|
accepts requests on the app's behalf, passes them to the app, and passes the
|
||||||
app's answers back. A request it does not refuse reaches the app as it came. The
|
app's answers back. A request it does not refuse reaches the app as it came,
|
||||||
only headers it changes are the ones that tell the app who the client is.
|
apart from a few headers (see "How it sits in front of the app" below).
|
||||||
|
|
||||||
It needs no setting. Every setting has a default chosen for a service on the
|
It needs no setting. Every setting has a default chosen for a service on the
|
||||||
open internet. The design is in [`SPEC.md`](SPEC.md). The survey of existing
|
open internet. The design is in [`SPEC.md`](SPEC.md). The survey of existing
|
||||||
@@ -23,21 +23,23 @@ tools that led to it is in [`EVALUATION.md`](EVALUATION.md).
|
|||||||
limit is refused and banned. The first ban lasts an hour. Each repeat within a
|
limit is refused and banned. The first ban lasts an hour. Each repeat within a
|
||||||
day makes the next ban three times as long. A ban that would last more than
|
day makes the next ban three times as long. A ban that would last more than
|
||||||
seven days is permanent.
|
seven days is permanent.
|
||||||
- Scanners that probe for secrets and known weak spots: `/.env`, `/.git/config`,
|
- Scanners that probe for secrets, such as `/.env` or `/.git/config`. The
|
||||||
or a WordPress login on a site that runs no WordPress. The default rule file
|
default rule file bans such a client for seven days at its first probe. Its
|
||||||
bans such a client for seven days at its first probe. Its next request during
|
next request during the ban makes the ban permanent. Probes for things your
|
||||||
the ban makes the ban permanent. You can add rules of your own, and name trap
|
app does not serve, such as a WordPress login, need a rule of your own or an
|
||||||
paths that do the same.
|
entry in `SWWAF_TRAP_PATHS`, which does the same.
|
||||||
- Attacks carried in a request's URL or headers, such as SQL injection and path
|
- Attacks carried in a request's URL or headers, such as SQL injection and path
|
||||||
traversal. The OWASP Core Rule Set is a published set of rules that recognise
|
traversal. The OWASP Core Rule Set is a published set of rules that recognise
|
||||||
common web attacks. Coraza, a Go engine that runs those rules, inspects every
|
common web attacks. Coraza, a Go engine that runs those rules, inspects each
|
||||||
request. A request the rules take for an attack is refused.
|
request the earlier checks let through. A request the rules take for an attack
|
||||||
|
is refused.
|
||||||
- Abuse from networks and countries you do not want. You can:
|
- Abuse from networks and countries you do not want. You can:
|
||||||
- refuse whole netblocks and countries;
|
- refuse whole netblocks and countries;
|
||||||
- give the clients of chosen networks and countries a fraction of every
|
- give the clients of chosen networks and countries a fraction of every
|
||||||
limit, so that they are banned sooner;
|
limit, so that they are banned sooner;
|
||||||
- refuse or limit clients that a blocklist, a DNS blocklist, AbuseIPDB or
|
- refuse or limit clients on a blocklist or a DNS blocklist, or with a high
|
||||||
your own CrowdSec engine has already seen misbehave.
|
AbuseIPDB score;
|
||||||
|
- ban clients named by your own CrowdSec engine.
|
||||||
- Slow and oversized requests. Every request and every answer has a time limit
|
- Slow and oversized requests. Every request and every answer has a time limit
|
||||||
and a size limit.
|
and a size limit.
|
||||||
|
|
||||||
@@ -125,6 +127,11 @@ docker run --detach --name app \
|
|||||||
<registry>/app@sha256:<digest>
|
<registry>/app@sha256:<digest>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
With no setting, the address of every new visitor is sent to GeoJS, a web
|
||||||
|
service, to find its country and network. `SWWAF_LOOKUP_SOURCE=off` sends
|
||||||
|
nothing. `SWWAF_LOOKUP_SOURCE=file` looks addresses up in a database file on
|
||||||
|
your own host (see "GeoJS and the lookup database" below).
|
||||||
|
|
||||||
`deploy/example-app` is such an app. `make example-app` builds it and checks it
|
`deploy/example-app` is such an app. `make example-app` builds it and checks it
|
||||||
(see "Checking that it works" below).
|
(see "Checking that it works" below).
|
||||||
|
|
||||||
@@ -165,7 +172,8 @@ after Ubuntu's own commands.
|
|||||||
must trust `127.0.0.1` and `::1` for forwarded headers, besides the private
|
must trust `127.0.0.1` and `::1` for forwarded headers, besides the private
|
||||||
address ranges. An app left at the usual default, the private ranges alone,
|
address ranges. An app left at the usual default, the private ranges alone,
|
||||||
sees every visitor as `127.0.0.1`. Each request also carries its id in
|
sees every visitor as `127.0.0.1`. Each request also carries its id in
|
||||||
`X-Request-ID`, the same id as in its log line.
|
`X-Request-ID`, the same id as in its log line. Any `X-Client-ASN` or
|
||||||
|
`X-Client-Country` header a client sent is removed.
|
||||||
- Both processes start with the container's environment variables. That is why
|
- Both processes start with the container's environment variables. That is why
|
||||||
every setting of `smallwebwaf` starts with `SWWAF_`. A token given as an
|
every setting of `smallwebwaf` starts with `SWWAF_`. A token given as an
|
||||||
environment variable is readable by the app. A token given as a file that only
|
environment variable is readable by the app. A token given as a file that only
|
||||||
@@ -179,9 +187,9 @@ after Ubuntu's own commands.
|
|||||||
accepts connections at `SWWAF_UPSTREAM_URL`. It runs every second from the
|
accepts connections at `SWWAF_UPSTREAM_URL`. It runs every second from the
|
||||||
container's start until it first passes, for up to a minute, and every 30
|
container's start until it first passes, for up to a minute, and every 30
|
||||||
seconds after that. traefik sends a container no requests until it is healthy.
|
seconds after that. traefik sends a container no requests until it is healthy.
|
||||||
- `SWWAF_LISTEN_ADDR` can move `smallwebwaf` to another port. The app then
|
- `SWWAF_LISTEN_ADDR` can move `smallwebwaf` to another port. The app must not
|
||||||
leaves that port free instead, the health check follows it, and traefik's
|
listen on that port. The health check follows the setting, and traefik's label
|
||||||
label must name it. Keep the address part empty: `:9000`, never
|
must name the new port. Keep the address part empty: `:9000`, never
|
||||||
`127.0.0.1:9000`. traefik reaches `smallwebwaf` on the container's address,
|
`127.0.0.1:9000`. traefik reaches `smallwebwaf` on the container's address,
|
||||||
and the health check on `127.0.0.1`.
|
and the health check on `127.0.0.1`.
|
||||||
- When `smallwebwaf` or the app exits, runit starts it again a second later. The
|
- When `smallwebwaf` or the app exits, runit starts it again a second later. The
|
||||||
@@ -193,18 +201,24 @@ after Ubuntu's own commands.
|
|||||||
`smallwebwaf` stops taking requests, gives those in progress five seconds to
|
`smallwebwaf` stops taking requests, gives those in progress five seconds to
|
||||||
finish, and writes its state files. Those requests can finish only while the
|
finish, and writes its state files. Those requests can finish only while the
|
||||||
app still answers. An app that exits at once on `SIGTERM` ends them: the
|
app still answers. An app that exits at once on `SIGTERM` ends them: the
|
||||||
client gets `502`, or a cut-off answer. An app that should let them finish
|
client gets `502`, or a cut-off answer. To let them finish, the app must keep
|
||||||
keeps answering for the whole five seconds.
|
answering for five seconds after `SIGTERM`.
|
||||||
|
|
||||||
## What happens to a request
|
## What happens to a request
|
||||||
|
|
||||||
`smallwebwaf` handles each request in this order. The first step that refuses it
|
`smallwebwaf` handles each request in this order. The first step that refuses it
|
||||||
ends the handling. Nothing of a refused request reaches the app.
|
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
|
||||||
|
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 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
|
||||||
|
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.
|
||||||
2. The client is worked out. A netblock is a range of addresses, written as an
|
2. The client is worked out. A netblock is a range of addresses, written as an
|
||||||
address and a prefix length: `203.0.113.0/24`. `SWWAF_TRUSTED_PROXIES` names
|
address and a prefix length: `203.0.113.0/24`. `SWWAF_TRUSTED_PROXIES` lists
|
||||||
the netblocks traefik comes from, by default the private address ranges.
|
the netblocks traefik comes from, by default the private address ranges.
|
||||||
- A TCP peer outside those netblocks is the client. Its forwarded headers
|
- A TCP peer outside those netblocks is the client. Its forwarded headers
|
||||||
are replaced.
|
are replaced.
|
||||||
@@ -212,9 +226,9 @@ ends the handling. Nothing of a refused request reaches the app.
|
|||||||
first address outside those netblocks is the client.
|
first address outside those netblocks is the client.
|
||||||
- If every address in the header is inside them, the leftmost is the client.
|
- If every address in the header is inside them, the leftmost is the client.
|
||||||
With no header, the peer is.
|
With no header, the peer is.
|
||||||
- An IPv6 client is one whole group: the netblock around its address that
|
- An IPv6 client is the whole netblock around its address, a /64 by default;
|
||||||
`SWWAF_IPV6_GROUP_PREFIX` sets, a /64 by default. One abuser usually holds
|
`SWWAF_IPV6_GROUP_PREFIX` sets its length. One abuser usually holds a
|
||||||
a whole /64.
|
whole /64.
|
||||||
3. A client in `SWWAF_ALLOW_NETS` skips every check below. A client in
|
3. A client in `SWWAF_ALLOW_NETS` skips every check below. A client in
|
||||||
`SWWAF_DENY_NETS` is refused.
|
`SWWAF_DENY_NETS` is refused.
|
||||||
4. A client under a ban is refused. If the ban was made for a clear sign of
|
4. A client under a ban is refused. If the ban was made for a clear sign of
|
||||||
@@ -226,18 +240,19 @@ ends the handling. Nothing of a refused request reaches the app.
|
|||||||
6. The country lists are checked. A client from a country in
|
6. The country lists are checked. A client from a country in
|
||||||
`SWWAF_DENIED_COUNTRIES` is refused. When
|
`SWWAF_DENIED_COUNTRIES` is refused. When
|
||||||
`SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` is set, a client from any other country
|
`SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` is set, a client from any other country
|
||||||
is refused, and so is a client without a country.
|
is refused, and so is a client without a country. Such a refusal is not an
|
||||||
|
offence.
|
||||||
7. The reputation sources are checked, in this order:
|
7. The reputation sources are checked, in this order:
|
||||||
- the blocklists `SWWAF_BLOCKLIST_URLS` names;
|
- the blocklists in `SWWAF_BLOCKLIST_URLS`;
|
||||||
- the decision list of the CrowdSec engine `SWWAF_CROWDSEC_LAPI_URL` names;
|
- the decision list of the CrowdSec engine at `SWWAF_CROWDSEC_LAPI_URL`;
|
||||||
- the DNS blocklists `SWWAF_DNSBL_ZONES` names;
|
- the DNS blocklists in `SWWAF_DNSBL_ZONES`;
|
||||||
- AbuseIPDB, while `SWWAF_ABUSEIPDB_KEY` is set.
|
- AbuseIPDB, while `SWWAF_ABUSEIPDB_KEY` is set.
|
||||||
|
|
||||||
A source that lists the client refuses it, lowers its limits, or only notes
|
A source the client is on refuses it, lowers its limits, or only notes it,
|
||||||
it, as `SWWAF_BLOCKLIST_ACTION` and `SWWAF_REPUTATION_ACTION` say. A
|
as `SWWAF_BLOCKLIST_ACTION` and `SWWAF_REPUTATION_ACTION` say. A CrowdSec
|
||||||
CrowdSec decision bans the client until the decision ends. No request waits
|
decision bans the client until the decision ends. No request waits for a
|
||||||
for a query. DNS blocklists and AbuseIPDB are asked in the background, and
|
query. DNS blocklists and AbuseIPDB are asked in the background, and their
|
||||||
their answer applies to the client's later requests.
|
answer applies to the client's later requests.
|
||||||
|
|
||||||
8. Rate limiting counts the request, unless the client is in
|
8. Rate limiting counts the request, unless the client is in
|
||||||
`SWWAF_RATE_LIMIT_EXEMPT_NETS` or the path is under
|
`SWWAF_RATE_LIMIT_EXEMPT_NETS` or the path is under
|
||||||
@@ -268,21 +283,24 @@ ends the handling. Nothing of a refused request reaches the app.
|
|||||||
- The request goes into the client's history and the anomaly counters, any
|
- The request goes into the client's history and the anomaly counters, any
|
||||||
alert due is raised, and the log line is written.
|
alert due is raised, and the log line is written.
|
||||||
|
|
||||||
In `observe` mode (`SWWAF_MODE=observe`), steps 3 to 11 refuse nothing and ban
|
In `observe` mode (`SWWAF_MODE=observe`), `smallwebwaf` makes no ban of its own.
|
||||||
no one. A request they would have refused is passed to the app, and its log line
|
It refuses a request only for a size or time limit, or for a missing or wrong
|
||||||
says what `enforce` mode would have done. The size and time limits still apply,
|
token. A request it would otherwise have refused is passed to the app, and its
|
||||||
and so does the token check. Observe mode is for trying a configuration before
|
log line says what `enforce` mode would have done. Observe mode is for trying a
|
||||||
enforcing it.
|
configuration before enforcing it.
|
||||||
|
|
||||||
## What a refused visitor sees
|
## What a refused visitor sees
|
||||||
|
|
||||||
- `SWWAF_BAN_RESPONSE` answers most refusals: a banned client, a broken rate
|
- `SWWAF_BAN_RESPONSE` answers most refusals. By default it is `403` with the
|
||||||
limit, a `ban` rule, a trap path, `SWWAF_DENY_NETS`, the country lists, and a
|
|
||||||
reputation source whose action is `deny`. By default it is `403` with the
|
|
||||||
plain text `Forbidden`. It can be `429` instead, or `close`, which closes the
|
plain text `Forbidden`. It can be `429` instead, or `close`, which closes the
|
||||||
connection without an answer. Behind traefik, `close` still answers the
|
connection without an answer. Behind traefik, `close` still answers the
|
||||||
client: traefik sends `502`, as it does whenever its backend drops a
|
client: traefik sends `502`, as it does whenever its backend drops a
|
||||||
connection.
|
connection. It answers these refusals:
|
||||||
|
- a banned client;
|
||||||
|
- a broken rate limit;
|
||||||
|
- a `ban` rule or a trap path;
|
||||||
|
- `SWWAF_DENY_NETS` and the country lists;
|
||||||
|
- a reputation source whose action is `deny`.
|
||||||
- A `block` rule and the Core Rule Set always answer `403`.
|
- A `block` rule and the Core Rule Set always answer `403`.
|
||||||
- The size and time limits answer with their own statuses:
|
- The size and time limits answer with their own statuses:
|
||||||
- `413` for a request body over `SWWAF_REQUEST_MAX_BYTES`;
|
- `413` for a request body over `SWWAF_REQUEST_MAX_BYTES`;
|
||||||
@@ -305,13 +323,20 @@ enforcing it.
|
|||||||
|
|
||||||
- Every request gets one JSON line on stdout, which `docker logs` shows. Its
|
- Every request gets one JSON line on stdout, which `docker logs` shows. Its
|
||||||
`action` field says what was done, such as `forward`, `banned`, `rate_limited`
|
`action` field says what was done, such as `forward`, `banned`, `rate_limited`
|
||||||
or `waf_blocked`. Other fields say why: the rules that matched, the Core Rule
|
or `waf_blocked`. Other fields say why (see "Request log" below):
|
||||||
Set's score, the limit broken, the reputation sources that listed the client,
|
- the rules that matched;
|
||||||
and when its ban ends. See "Request log" below.
|
- the Core Rule Set's score;
|
||||||
|
- the limit broken;
|
||||||
|
- the reputation sources the client is on;
|
||||||
|
- when the client's ban ends.
|
||||||
- Every ban is in `bans.json` in the state directory, with notes on why it was
|
- Every ban is in `bans.json` in the state directory, with notes on why it was
|
||||||
made. See "Bans" below.
|
made. See "Bans" below.
|
||||||
- Every ban, Core Rule Set match, reputation hit, anomaly and failure of a
|
- These raise an alert, sent to the destinations you name (see "Alerts" below):
|
||||||
source or a file raises an alert. See "Alerts" below.
|
- a ban `smallwebwaf` makes;
|
||||||
|
- a Core Rule Set match;
|
||||||
|
- a reputation hit;
|
||||||
|
- a count over an anomaly threshold;
|
||||||
|
- a failure of a source or a file.
|
||||||
- With `SWWAF_ADMIN_TOKEN` set, `GET /_smallwebwaf/clients/<ip>` answers with
|
- With `SWWAF_ADMIN_TOKEN` set, `GET /_smallwebwaf/clients/<ip>` answers with
|
||||||
everything `smallwebwaf` knows of a client, and `GET /_smallwebwaf/bans` with
|
everything `smallwebwaf` knows of a client, and `GET /_smallwebwaf/bans` with
|
||||||
every ban. See "Admin endpoints" below.
|
every ban. See "Admin endpoints" below.
|
||||||
@@ -386,9 +411,9 @@ The forms of values:
|
|||||||
`https` URL with a host and an optional port, and nothing more: no path, user
|
`https` URL with a host and an optional port, and nothing more: no path, user
|
||||||
or query.
|
or query.
|
||||||
- `SWWAF_INSTANCE_NAME` (default: the container's host name, the first 12
|
- `SWWAF_INSTANCE_NAME` (default: the container's host name, the first 12
|
||||||
characters of the container's id): the name every log line, alert and metric
|
characters of the container's id): the `instance` name in every log line,
|
||||||
gives as `instance`. Set it to a name that survives deploys and tells
|
alert and metric. Set it to a name that survives deploys and tells instances
|
||||||
instances apart, such as `fsn1app1/gitea`. It must be valid UTF-8. While
|
apart, such as `fsn1app1/gitea`. It must be valid UTF-8. While
|
||||||
`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.
|
||||||
@@ -408,8 +433,8 @@ The forms of values:
|
|||||||
### Time and size limits
|
### Time and size limits
|
||||||
|
|
||||||
Each request has limits on both sides: between the client and `smallwebwaf`, and
|
Each request has limits on both sides: between the client and `smallwebwaf`, and
|
||||||
between `smallwebwaf` and the app. Bodies stream through in both directions and
|
between `smallwebwaf` and the app. Bodies stream through in both directions,
|
||||||
are never held whole in memory.
|
except the part the Core Rule Set reads while `SWWAF_WAF_BODY_LIMIT` is set.
|
||||||
|
|
||||||
- `SWWAF_CLIENT_REQUEST_TIMEOUT` (default `60s`): how long a client may take to
|
- `SWWAF_CLIENT_REQUEST_TIMEOUT` (default `60s`): how long a client may take to
|
||||||
send its request line and headers, and then, from the end of the headers, its
|
send its request line and headers, and then, from the end of the headers, its
|
||||||
@@ -446,9 +471,9 @@ fit.
|
|||||||
such as your monitoring or your own network. The size and time limits still
|
such as your monitoring or your own network. The size and time limits still
|
||||||
apply to them. They are not looked up. They still need a token for the admin
|
apply to them. They are not looked up. They still need a token for the admin
|
||||||
endpoints.
|
endpoints.
|
||||||
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients rate
|
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
||||||
limiting and the byte limits neither count nor refuse, such as a machine that
|
exempt from rate limiting and the byte limits, such as a machine that talks to
|
||||||
talks to the app all day. Everything else still applies to them.
|
the app all day. Everything else still applies to them.
|
||||||
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
||||||
A client in `SWWAF_ALLOW_NETS` too is let through.
|
A client in `SWWAF_ALLOW_NETS` too is let through.
|
||||||
|
|
||||||
@@ -459,10 +484,10 @@ fit.
|
|||||||
requests a client may make in each window. A count or `off`. The defaults are
|
requests a client may make in each window. A count or `off`. The defaults are
|
||||||
several times what one busy person produces. A browser loading a heavy page
|
several times what one busy person produces. A browser loading a heavy page
|
||||||
makes a few hundred requests, and several people often share one address.
|
makes a few hundred requests, and several people often share one address.
|
||||||
- `SWWAF_RATE_LIMIT_EXEMPT_PATHS` (default empty): path prefixes rate limiting
|
- `SWWAF_RATE_LIMIT_EXEMPT_PATHS` (default empty): path prefixes exempt from
|
||||||
does not count, such as `/assets/`. Their bytes are not counted either. The
|
rate limiting, such as `/assets/`. Their bytes are not counted either. The
|
||||||
path is matched as sent, before any query and not percent-decoded, character
|
path is matched exactly as sent, without the query, and is not
|
||||||
for character. `/assets/` matches `/assets/app.js`. It does not match
|
percent-decoded. `/assets/` matches `/assets/app.js`. It does not match
|
||||||
`/Assets/app.js`, `/%61ssets/app.js` or `/static/assets/app.js`. There are no
|
`/Assets/app.js`, `/%61ssets/app.js` or `/static/assets/app.js`. There are no
|
||||||
wildcards. A path holding `..`, a backslash or an encoded slash (`%2F`) is
|
wildcards. A path holding `..`, a backslash or an encoded slash (`%2F`) is
|
||||||
never exempt, since the app may read it as a path outside the prefix.
|
never exempt, since the app may read it as a path outside the prefix.
|
||||||
@@ -481,9 +506,9 @@ fit.
|
|||||||
|
|
||||||
- `SWWAF_LOOKUP_SOURCE` (default `geojs`): `geojs`, `file` or `off`. `geojs`
|
- `SWWAF_LOOKUP_SOURCE` (default `geojs`): `geojs`, `file` or `off`. `geojs`
|
||||||
asks the GeoJS web service, which is then told the address of every new
|
asks the GeoJS web service, which is then told the address of every new
|
||||||
visitor. `file` looks clients up in the IPinfo Lite database file
|
visitor. `file` looks clients up in the IPinfo Lite database file at
|
||||||
`SWWAF_LOOKUP_DB_PATH` names, and sends nothing off the host. `off` looks up
|
`SWWAF_LOOKUP_DB_PATH`, and sends nothing off the host. `off` looks up no
|
||||||
no client. With `off`, a setting that needs lookups stops the start:
|
client. With `off`, a setting that needs lookups stops the start:
|
||||||
- a country list;
|
- a country list;
|
||||||
- `SWWAF_ADD_LOOKUP_HEADERS`;
|
- `SWWAF_ADD_LOOKUP_HEADERS`;
|
||||||
- an entry in `SWWAF_ASN_LIMIT_PERCENT`, `SWWAF_COUNTRY_LIMIT_PERCENT`,
|
- an entry in `SWWAF_ASN_LIMIT_PERCENT`, `SWWAF_COUNTRY_LIMIT_PERCENT`,
|
||||||
@@ -536,14 +561,16 @@ whole AS number or country, which one abuser could use up.
|
|||||||
- `SWWAF_ASN_BYTES_PERCENT` and `SWWAF_COUNTRY_BYTES_PERCENT` (default empty):
|
- `SWWAF_ASN_BYTES_PERCENT` and `SWWAF_COUNTRY_BYTES_PERCENT` (default empty):
|
||||||
the same for the byte limits alone. For the AS numbers and countries they
|
the same for the byte limits alone. For the AS numbers and countries they
|
||||||
list, they take the place of the two settings above.
|
list, they take the place of the two settings above.
|
||||||
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage every client
|
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage of every limit
|
||||||
without a country gets: one the lookup cannot place, one on a private address,
|
for a client without a country:
|
||||||
and one whose answer from GeoJS has not come in time.
|
- one the lookup cannot place;
|
||||||
|
- one on a private address;
|
||||||
|
- one whose answer from GeoJS has not come in time.
|
||||||
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
||||||
text file of such entries, one to a line, such as `AS14061:50`. One list can
|
text file of such entries, one to a line, such as `AS14061:50`. One list can
|
||||||
then serve several instances. It is fetched and kept as a blocklist is. Where
|
then serve several instances. It is fetched and kept as a blocklist is. Where
|
||||||
it and `SWWAF_ASN_LIMIT_PERCENT` both give a percentage, the lower applies. A
|
it and `SWWAF_ASN_LIMIT_PERCENT` both give a percentage, the lower applies. A
|
||||||
URL that `SWWAF_BLOCKLIST_URLS` names too stops the start.
|
URL that is also in `SWWAF_BLOCKLIST_URLS` stops the start.
|
||||||
|
|
||||||
A client to which several percentages apply gets the lowest. The log line of
|
A client to which several percentages apply gets the lowest. The log line of
|
||||||
each counted request, and the notes of each ban, say which setting lowered the
|
each counted request, and the notes of each ban, say which setting lowered the
|
||||||
@@ -557,21 +584,21 @@ do. The defaults judge it by what it does to your service.
|
|||||||
- `SWWAF_BLOCKLIST_URLS` (default empty): blocklists to fetch, such as
|
- `SWWAF_BLOCKLIST_URLS` (default empty): blocklists to fetch, such as
|
||||||
`https://www.spamhaus.org/drop/drop.txt`. Each is an `http` or `https` URL
|
`https://www.spamhaus.org/drop/drop.txt`. Each is an `http` or `https` URL
|
||||||
without a user or a fragment. A URL listed twice is refused.
|
without a user or a fragment. A URL listed twice is refused.
|
||||||
- `SWWAF_BLOCKLIST_REFRESH` (default `24h`): how long after a list was last
|
- `SWWAF_BLOCKLIST_REFRESH` (default `24h`): how long a list waits after its
|
||||||
fetched, or a fetch failed, it is fetched again. A duration of `1h` or more;
|
last fetch, or failed try, before it is fetched again. A duration of `1h` or
|
||||||
it cannot be `off`. The Spamhaus lists may be fetched no more than once an
|
more; it cannot be `off`. The Spamhaus lists may be fetched no more than once
|
||||||
hour.
|
an hour.
|
||||||
- `SWWAF_BLOCKLIST_ACTION` (default `deny`): what is done with a client a
|
- `SWWAF_BLOCKLIST_ACTION` (default `deny`): what is done with a client on a
|
||||||
blocklist lists. `deny` refuses its requests. `limit:<percent>`, such as
|
blocklist. `deny` refuses its requests. `limit:<percent>`, such as `limit:25`,
|
||||||
`limit:25`, gives it that percentage of every limit. `log` only notes the list
|
gives it that percentage of every limit. `log` only notes the list in the log
|
||||||
in the log line and raises an alert.
|
line and raises an alert.
|
||||||
- `SWWAF_DNSBL_ZONES` (default empty): the DNS blocklists to ask about each
|
- `SWWAF_DNSBL_ZONES` (default empty): the DNS blocklists to ask about each
|
||||||
client, such as `dnsbl.dronebl.org`. A DNS blocklist lists an address by
|
client, such as `dnsbl.dronebl.org`. A DNS blocklist answers a DNS query for a
|
||||||
answering a DNS query for a name made from it. A zone is a DNS name of at most
|
name made from an address, and so says whether the address is on it. A zone is
|
||||||
189 characters. A Spamhaus zone carries its key, as in
|
a DNS name of at most 189 characters. A Spamhaus zone carries its key, as in
|
||||||
`<key>.xbl.dq.spamhaus.net`, and is shown with the key masked. A zone listed
|
`<key>.xbl.dq.spamhaus.net`, and is shown with the key masked. A zone listed
|
||||||
twice is refused, whatever its case or key. Do not name a zone meant for mail:
|
twice is refused, whatever its case or key. Do not name a zone meant for mail:
|
||||||
such a zone lists the residential addresses ordinary visitors come from.
|
such a zone holds the residential addresses ordinary visitors come from.
|
||||||
- `SWWAF_DNSBL_RESOLVER` (default unset): the resolver to ask the zones through,
|
- `SWWAF_DNSBL_RESOLVER` (default unset): the resolver to ask the zones through,
|
||||||
such as `192.0.2.53` or `[2001:db8::53]:5353`. An IP address with an optional
|
such as `192.0.2.53` or `[2001:db8::53]:5353`. An IP address with an optional
|
||||||
port, 53 when none is given. Unset, the host's resolver is used. Several zones
|
port, 53 when none is given. Unset, the host's resolver is used. Several zones
|
||||||
@@ -587,13 +614,13 @@ do. The defaults judge it by what it does to your service.
|
|||||||
you run, such as `http://172.17.0.1:8080`. An `http` or `https` URL without a
|
you run, such as `http://172.17.0.1:8080`. An `http` or `https` URL without a
|
||||||
user or a fragment. Its decision list is at `v1/decisions` under that URL, and
|
user or a fragment. Its decision list is at `v1/decisions` under that URL, and
|
||||||
is fetched every minute. Set without the key, it stops the start. So does a
|
is fetched every minute. Set without the key, it stops the start. So does a
|
||||||
decision list URL that `SWWAF_BLOCKLIST_URLS` or `SWWAF_ASN_LIMIT_PERCENT_URL`
|
decision list URL that is also in `SWWAF_BLOCKLIST_URLS`, or that is
|
||||||
names too.
|
`SWWAF_ASN_LIMIT_PERCENT_URL`.
|
||||||
- `SWWAF_CROWDSEC_LAPI_KEY` (default unset): the key the engine gave for
|
- `SWWAF_CROWDSEC_LAPI_KEY` (default unset): the key the engine gave for
|
||||||
`smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it. Set without the
|
`smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it. Set without the
|
||||||
URL, it stops the start.
|
URL, it stops the start.
|
||||||
- `SWWAF_REPUTATION_ACTION` (default `limit:25`): what is done with a client a
|
- `SWWAF_REPUTATION_ACTION` (default `limit:25`): what is done with a client on
|
||||||
DNS blocklist lists, or 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 a list like DROP, so by default the client gets a quarter of
|
less certain than a list like DROP, so by default the client gets a quarter of
|
||||||
every limit.
|
every limit.
|
||||||
@@ -653,8 +680,8 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
in part. Any other body, such as a git push, reaches the app uninspected. See
|
in part. Any other body, such as a git push, reaches the app uninspected. See
|
||||||
"The Core Rule Set" below.
|
"The Core Rule Set" below.
|
||||||
- `SWWAF_ERROR_BURST_THRESHOLD` (default `30`): the most refusals of one client
|
- `SWWAF_ERROR_BURST_THRESHOLD` (default `30`): the most refusals of one client
|
||||||
in a minute after a rule match, a trap path, a Core Rule Set match or a token
|
in a minute for a rule match, a trap path, a Core Rule Set match or a failed
|
||||||
check. One more bans the client, as a broken rate limit does. A count or
|
token check. One more bans the client, as a broken rate limit does. A count or
|
||||||
`off`. The app's own `401` and `404` answers are not counted.
|
`off`. The app's own `401` and `404` answers are not counted.
|
||||||
|
|
||||||
### How long bans last and what they cover
|
### How long bans last and what they cover
|
||||||
@@ -664,16 +691,16 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
and the Core Rule Set always answer `403`.
|
and the Core Rule Set always answer `403`.
|
||||||
- `SWWAF_LIMIT_BAN_DURATION` (default `1h`): the ban for a first broken rate
|
- `SWWAF_LIMIT_BAN_DURATION` (default `1h`): the ban for a first broken rate
|
||||||
limit, byte limit or error burst. A duration; it cannot be `off`.
|
limit, byte limit or error burst. A duration; it cannot be `off`.
|
||||||
- `SWWAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`): a limit broken again within
|
- `SWWAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`): a client that breaks a limit
|
||||||
this time after a ban ended bans for three times as long as that ban. A
|
again within this time after its ban ended is banned three times as long. A
|
||||||
duration; it cannot be `off`.
|
duration; it cannot be `off`.
|
||||||
- `SWWAF_MAX_BAN_DURATION` (default `7d`): a ban for a broken limit that would
|
- `SWWAF_MAX_BAN_DURATION` (default `7d`): a ban for a broken limit that would
|
||||||
be longer is permanent instead. A duration; it cannot be `off`.
|
be longer is permanent instead. A duration; it cannot be `off`.
|
||||||
- `SWWAF_ATTACK_BAN_DURATION` (default `7d`): the ban for a first clear sign of
|
- `SWWAF_ATTACK_BAN_DURATION` (default `7d`): the ban for a first clear sign of
|
||||||
attack. A duration; it cannot be `off`.
|
attack. A duration; it cannot be `off`.
|
||||||
- `SWWAF_MAX_BANS` (default `5000`): the most bans `smallwebwaf` made that are
|
- `SWWAF_MAX_BANS` (default `5000`): the most bans `smallwebwaf` made that are
|
||||||
kept, past, active and permanent. A whole number above zero. Bans you make or
|
kept, whether past, active or permanent. A whole number above zero. Bans whose
|
||||||
keep are kept besides, and never dropped.
|
cause is `admin` are kept besides and never dropped (see "Bans" below).
|
||||||
- `SWWAF_BAN_SCOPE_V4_PREFIX` (default `32`): the length of the netblock around
|
- `SWWAF_BAN_SCOPE_V4_PREFIX` (default `32`): the length of the netblock around
|
||||||
an IPv4 client that a ban covers, from 0 to 32. `24` bans the surrounding /24.
|
an IPv4 client that a ban covers, from 0 to 32. `24` bans the surrounding /24.
|
||||||
An IPv6 ban covers the client's group.
|
An IPv6 ban covers the client's group.
|
||||||
@@ -683,9 +710,9 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
- `SWWAF_STATE_DIR` (default `/var/lib/smallwebwaf`): the directory of the state
|
- `SWWAF_STATE_DIR` (default `/var/lib/smallwebwaf`): the directory of the state
|
||||||
files, an absolute path. A directory that does not exist or cannot be written
|
files, an absolute path. A directory that does not exist or cannot be written
|
||||||
stops the start.
|
stops the start.
|
||||||
- `SWWAF_STATE_WRITE_DELAY` (default `10s`): how long after a ban is made,
|
- `SWWAF_STATE_WRITE_DELAY` (default `10s`): how long `bans.json` waits to be
|
||||||
lifted or made permanent `bans.json` is written, with every change in between.
|
written after a ban is made, lifted or made permanent. Every change in between
|
||||||
A duration; it cannot be `off`.
|
goes into the same write. A duration; it cannot be `off`.
|
||||||
- `SWWAF_STATE_COUNTER_INTERVAL` (default `15m`): how often every state file is
|
- `SWWAF_STATE_COUNTER_INTERVAL` (default `15m`): how often every state file is
|
||||||
written. A duration; it cannot be `off`.
|
written. A duration; it cannot be `off`.
|
||||||
|
|
||||||
@@ -693,14 +720,14 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
|
|
||||||
- `SWWAF_LOG_REQUEST_HEADERS` (default
|
- `SWWAF_LOG_REQUEST_HEADERS` (default
|
||||||
`accept,accept-language,accept-encoding,content-type,origin,range`): the
|
`accept,accept-language,accept-encoding,content-type,origin,range`): the
|
||||||
request headers whose values the log line gives, in either case.
|
request headers to record in the log line, in either case. `Authorization`,
|
||||||
`Authorization`, `Cookie` and `Set-Cookie` are never logged, even if listed.
|
`Cookie` and `Set-Cookie` are never logged, even if listed. `Host` and
|
||||||
`Host` and `Transfer-Encoding` are refused: Go's HTTP server takes them out of
|
`Transfer-Encoding` are refused: Go's HTTP server takes them out of the
|
||||||
the request. The host is the field `host`.
|
request. The host is the field `host`.
|
||||||
- `SWWAF_LOG_LEVEL` (default `info`): the least severe of `smallwebwaf`'s own
|
- `SWWAF_LOG_LEVEL` (default `info`): the least severe of `smallwebwaf`'s own
|
||||||
messages that are written: `debug`, `info`, `warn` or `error`. No message is
|
messages that are written: `debug`, `info`, `warn` or `error`. No message is
|
||||||
at `debug` yet. It holds back no request log line. `warn` and `error` hold
|
at `debug`. It holds back no request log line. `warn` and `error` hold back
|
||||||
back the line of settings at start.
|
the line of settings at start.
|
||||||
- `SWWAF_LOG_REMOTE_URL` (default unset): a syslog server every line on stdout
|
- `SWWAF_LOG_REMOTE_URL` (default unset): a syslog server every line on stdout
|
||||||
is also sent to, such as `syslog+tls://logs.example:6514`. The forms are
|
is also sent to, such as `syslog+tls://logs.example:6514`. The forms are
|
||||||
`syslog+udp://`, `syslog+tcp://` and `syslog+tls://`, each with a host and a
|
`syslog+udp://`, `syslog+tcp://` and `syslog+tls://`, each with a host and a
|
||||||
@@ -713,8 +740,8 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
- `SWWAF_LOG_REMOTE_FACILITY` (default `local0`): the syslog facility: `kern`,
|
- `SWWAF_LOG_REMOTE_FACILITY` (default `local0`): the syslog facility: `kern`,
|
||||||
`user`, `mail`, `daemon`, `auth`, `syslog`, `lpr`, `news`, `uucp`, `cron`,
|
`user`, `mail`, `daemon`, `auth`, `syslog`, `lpr`, `news`, `uucp`, `cron`,
|
||||||
`authpriv`, `ftp`, or `local0` to `local7`.
|
`authpriv`, `ftp`, or `local0` to `local7`.
|
||||||
- `SWWAF_LOG_REMOTE_APP_NAME` (default: `SWWAF_INSTANCE_NAME`): the app name the
|
- `SWWAF_LOG_REMOTE_APP_NAME` (default: `SWWAF_INSTANCE_NAME`): the app name in
|
||||||
records carry, 1 to 48 printable ASCII characters without a space.
|
each syslog record, 1 to 48 printable ASCII characters without a space.
|
||||||
|
|
||||||
### Tokens and metrics
|
### Tokens and metrics
|
||||||
|
|
||||||
@@ -731,18 +758,18 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
|
|
||||||
### Alert destinations
|
### Alert destinations
|
||||||
|
|
||||||
- `SWWAF_ALERT_WEBHOOK_URL` (default unset): a webhook each alert is posted to
|
- `SWWAF_ALERT_WEBHOOK_URL` (default unset): the webhook that receives each
|
||||||
as a JSON object. An `http` or `https` URL without a user or a fragment. The
|
alert as a JSON object. An `http` or `https` URL without a user or a fragment.
|
||||||
settings logged at start show its path and query as `********`, since many
|
The settings logged at start show its path and query as `********`, since many
|
||||||
webhooks carry their secret there.
|
webhooks carry their secret there.
|
||||||
- `SWWAF_ALERT_WEBHOOK_HEADERS` (default empty): headers sent with each post,
|
- `SWWAF_ALERT_WEBHOOK_HEADERS` (default empty): headers sent with each post,
|
||||||
such as `Authorization:Bearer 0123456789abcdef`. A list of name, `:` and
|
such as `Authorization:Bearer 0123456789abcdef`. A list of name, `:` and
|
||||||
value. A value cannot hold a comma.
|
value. A value cannot hold a comma.
|
||||||
- `SWWAF_ALERT_SLACK_WEBHOOK_URL` (default unset): a Slack incoming webhook each
|
- `SWWAF_ALERT_SLACK_WEBHOOK_URL` (default unset): the Slack incoming webhook
|
||||||
alert is posted to as a message. It is checked and masked as the webhook URL
|
that receives each alert as a message. It is checked and masked as the webhook
|
||||||
is.
|
URL is.
|
||||||
- `SWWAF_ALERT_NTFY_URL` (default unset): an ntfy topic each alert is published
|
- `SWWAF_ALERT_NTFY_URL` (default unset): the ntfy topic that receives each
|
||||||
to, as the topic's full URL, such as `https://ntfy.sh/my-alerts`. It is
|
alert, as the topic's full URL, such as `https://ntfy.sh/my-alerts`. It is
|
||||||
checked and masked as the webhook URL is.
|
checked and masked as the webhook URL is.
|
||||||
- `SWWAF_ALERT_NTFY_TOKEN` (default unset): an ntfy access token, sent as
|
- `SWWAF_ALERT_NTFY_TOKEN` (default unset): an ntfy access token, sent as
|
||||||
`Authorization: Bearer <token>`. A control character in it is refused.
|
`Authorization: Bearer <token>`. A control character in it is refused.
|
||||||
@@ -757,20 +784,27 @@ See "Blocklists", "DNS blocklists", "AbuseIPDB" and "CrowdSec" below.
|
|||||||
### Anomaly thresholds
|
### Anomaly thresholds
|
||||||
|
|
||||||
These only alert. They refuse and ban nothing. Each scope has four thresholds:
|
These only alert. They refuse and ban nothing. Each scope has four thresholds:
|
||||||
the most requests and the most bytes counted in a minute and in an hour. Their
|
the most requests and the most bytes counted in a minute and in an hour.
|
||||||
names are the scope's prefix and `REQUESTS_PER_MINUTE`, `REQUESTS_PER_HOUR`,
|
Requests are counts, bytes are sizes. Each is `off` by default. What is unusual
|
||||||
`BYTES_PER_MINUTE` or `BYTES_PER_HOUR`. Requests are counts, bytes are sizes.
|
depends on each service's normal traffic, which the metrics show. The four
|
||||||
Each is `off` by default. What is unusual depends on each service's normal
|
thresholds per client are:
|
||||||
traffic, which the metrics show.
|
|
||||||
|
|
||||||
- `SWWAF_ANOMALY_CLIENT_...`: per client.
|
- `SWWAF_ANOMALY_CLIENT_REQUESTS_PER_MINUTE`;
|
||||||
- `SWWAF_ANOMALY_NET_...`: per netblock around a client. The netblock is
|
- `SWWAF_ANOMALY_CLIENT_REQUESTS_PER_HOUR`;
|
||||||
`SWWAF_ANOMALY_NET_V4_PREFIX` (default `24`, from 0 to 32) long for an IPv4
|
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_MINUTE`;
|
||||||
client, and `SWWAF_ANOMALY_NET_V6_PREFIX` (default `48`, from 0 to 128) long
|
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_HOUR`.
|
||||||
for an IPv6 one.
|
|
||||||
|
The other scopes have the same four names with `CLIENT` replaced:
|
||||||
|
|
||||||
|
- `SWWAF_ANOMALY_NET_...`: per netblock around a client. Two settings give the
|
||||||
|
netblock's length:
|
||||||
|
- `SWWAF_ANOMALY_NET_V4_PREFIX` (default `24`, from 0 to 32) for an IPv4
|
||||||
|
client;
|
||||||
|
- `SWWAF_ANOMALY_NET_V6_PREFIX` (default `48`, from 0 to 128) for an IPv6
|
||||||
|
client.
|
||||||
- `SWWAF_ANOMALY_ASN_...`: per AS number. Needs lookups on.
|
- `SWWAF_ANOMALY_ASN_...`: per AS number. Needs lookups on.
|
||||||
- `SWWAF_ANOMALY_TOTAL_...`: the whole service.
|
- `SWWAF_ANOMALY_TOTAL_...`: the whole service.
|
||||||
- `SWWAF_WATCH_...`: each netblock `SWWAF_WATCH_NETS` names, as a whole.
|
- `SWWAF_WATCH_...`: each netblock in `SWWAF_WATCH_NETS`, as a whole.
|
||||||
`SWWAF_WATCH_NETS` (default empty) is a list of name, `=` and netblock, such
|
`SWWAF_WATCH_NETS` (default empty) is a list of name, `=` and netblock, such
|
||||||
as `office=203.0.113.0/24,scraper-x=198.51.100.0/22`. An item without `=` or a
|
as `office=203.0.113.0/24,scraper-x=198.51.100.0/22`. An item without `=` or a
|
||||||
name, or with a bad netblock, is refused. So is a name used twice.
|
name, or with a bad netblock, is refused. So is a name used twice.
|
||||||
@@ -781,16 +815,17 @@ clients in `SWWAF_ALLOW_NETS` included. At most 20,000 counters are kept.
|
|||||||
|
|
||||||
### Settings given as files
|
### Settings given as files
|
||||||
|
|
||||||
Any setting may be given as a file holding its value. The variable named like
|
Any setting may be given as a file holding its value. Add `_FILE` to the
|
||||||
the setting with `_FILE` added, such as `SWWAF_METRICS_TOKEN_FILE`, names the
|
setting's name: `SWWAF_METRICS_TOKEN_FILE` names the file that holds
|
||||||
file. The file's contents are the value, less one newline at the end. They are
|
`SWWAF_METRICS_TOKEN`. The file's contents are the value, less one newline at
|
||||||
checked as the value itself would be. Setting both forms stops the start, and so
|
the end. They are checked as the value itself would be. Setting both forms stops
|
||||||
does naming a file that cannot be read. `SWWAF_LOG_REMOTE_TLS_CA_FILE` names a
|
the start, and so does naming a file that cannot be read.
|
||||||
file already, and has no `_FILE` form.
|
`SWWAF_LOG_REMOTE_TLS_CA_FILE` already holds a file name, so it has no `_FILE`
|
||||||
|
form.
|
||||||
|
|
||||||
A token given as a file is out of the app's reach only while the `smallwebwaf`
|
A token given as a file is out of the app's reach only while the `smallwebwaf`
|
||||||
user alone can read the file. Make it on the host, owned by uid 65532 with mode
|
user alone can read the file. Make it on the host, owned by uid 65532 with mode
|
||||||
`0400`. Mount its directory into the container read-only:
|
`0400`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir -p /srv/app/tokens
|
mkdir -p /srv/app/tokens
|
||||||
@@ -799,8 +834,14 @@ chown 65532:65532 /srv/app/tokens/metrics
|
|||||||
chmod 0400 /srv/app/tokens/metrics
|
chmod 0400 /srv/app/tokens/metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
and for the container, `-v /srv/app/tokens:/etc/smallwebwaf/tokens:ro` and
|
Mount its directory into the container read-only, and name the file:
|
||||||
`-e SWWAF_METRICS_TOKEN_FILE=/etc/smallwebwaf/tokens/metrics`.
|
|
||||||
|
```sh
|
||||||
|
docker run \
|
||||||
|
-v /srv/app/tokens:/etc/smallwebwaf/tokens:ro \
|
||||||
|
-e SWWAF_METRICS_TOKEN_FILE=/etc/smallwebwaf/tokens/metrics \
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
A few limits are fixed rather than settings:
|
A few limits are fixed rather than settings:
|
||||||
|
|
||||||
@@ -813,23 +854,23 @@ A few limits are fixed rather than settings:
|
|||||||
|
|
||||||
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 the client's IPv4 address, or the netblock
|
`SWWAF_BAN_RESPONSE`. The netblock is the client's IPv4 address, or the netblock
|
||||||
around it that `SWWAF_BAN_SCOPE_V4_PREFIX` sets, or the client's IPv6 group. A
|
around it, `SWWAF_BAN_SCOPE_V4_PREFIX` long, or the client's IPv6 group. A
|
||||||
banned client is not looked up and not counted by rate limiting.
|
banned client is not looked up and not counted by rate limiting.
|
||||||
|
|
||||||
`smallwebwaf` makes bans for three causes:
|
`smallwebwaf` makes bans for three causes:
|
||||||
|
|
||||||
- `limit`: the client broke a rate limit, a byte limit or the error burst. The
|
- `limit`: the client broke a rate limit, a byte limit or the error burst. The
|
||||||
first such ban lasts `SWWAF_LIMIT_BAN_DURATION`, an hour. A limit broken again
|
first such ban lasts `SWWAF_LIMIT_BAN_DURATION`, an hour. A client that breaks
|
||||||
within `SWWAF_LIMIT_BAN_REPEAT_WINDOW`, a day, of a ban ending bans for three
|
a limit again within `SWWAF_LIMIT_BAN_REPEAT_WINDOW`, a day, after its ban
|
||||||
times as long as that ban: 1, 3, 9, 27 and 81 hours. A ban that would last
|
ended is banned three times as long: 1, 3, 9, 27 and 81 hours. A ban that
|
||||||
longer than `SWWAF_MAX_BAN_DURATION`, seven days, is permanent. Such a ban
|
would last longer than `SWWAF_MAX_BAN_DURATION`, seven days, is permanent.
|
||||||
sets the client's counters back to zero.
|
Such a ban sets the client's counters back to zero.
|
||||||
- `attack`: the client matched a `ban` rule or asked for a trap path. The first
|
- `attack`: the client matched a `ban` rule or asked for a trap path. The first
|
||||||
such ban lasts `SWWAF_ATTACK_BAN_DURATION`, seven days. Any request from the
|
such ban lasts `SWWAF_ATTACK_BAN_DURATION`, seven days. Any request from the
|
||||||
netblock while it lasts makes it permanent. Once it has run out, the netblock
|
netblock while it lasts makes it permanent. Once it has run out, the netblock
|
||||||
is served like any other. Its next clear sign of attack bans it permanently at
|
is served like any other. Its next clear sign of attack bans it permanently at
|
||||||
once.
|
once.
|
||||||
- `crowdsec`: a decision of your CrowdSec engine lists the client. The ban lasts
|
- `crowdsec`: a decision of your CrowdSec engine names the client. The ban lasts
|
||||||
until the decision ends.
|
until the decision ends.
|
||||||
|
|
||||||
A `block` rule and the Core Rule Set ban no one by themselves, since the Core
|
A `block` rule and the Core Rule Set ban no one by themselves, since the Core
|
||||||
@@ -845,14 +886,15 @@ Each ban carries notes for deciding whether to lift it:
|
|||||||
setting lowered it;
|
setting lowered it;
|
||||||
- the request that caused the ban;
|
- the request that caused the ban;
|
||||||
- the client's AS number, AS name and country;
|
- the client's AS number, AS name and country;
|
||||||
- the reputation sources that listed the client;
|
- the reputation sources the client is on;
|
||||||
- the netblock's requests since it was first seen, and how many this ban has
|
- the netblock's requests since it was first seen, and how many this ban has
|
||||||
refused;
|
refused;
|
||||||
- how many earlier bans the netblock had, by cause.
|
- how many earlier bans the netblock had, by cause.
|
||||||
|
|
||||||
At most `SWWAF_MAX_BANS` bans `smallwebwaf` made are kept. Past that, the
|
At most `SWWAF_MAX_BANS` bans `smallwebwaf` made are kept. Past that, one ban is
|
||||||
earliest ban of the netblock that has gone longest without a request is dropped.
|
dropped: the earliest ban of the netblock that has gone longest without a
|
||||||
Bans whose cause is `admin`, those you make or keep, are never dropped.
|
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.
|
||||||
|
|
||||||
### Managing bans by editing `bans.json`
|
### Managing bans by editing `bans.json`
|
||||||
|
|
||||||
@@ -983,10 +1025,10 @@ COPY 50-gitea.rules /etc/smallwebwaf/rules.d/50-gitea.rules
|
|||||||
```
|
```
|
||||||
|
|
||||||
A directory mounted over `/etc/smallwebwaf/rules.d` replaces the default file.
|
A directory mounted over `/etc/smallwebwaf/rules.d` replaces the default file.
|
||||||
Single files mounted into it add to it. Docker does not show a single mounted
|
Single files mounted into it add to it. Many editors save a file by replacing
|
||||||
file being replaced, which is how many editors save. So rules to be edited while
|
it, and a container keeps seeing the old one when the file alone is mounted. So
|
||||||
`smallwebwaf` runs belong in a mounted directory. To run without rules, mount an
|
rules to be edited while `smallwebwaf` runs belong in a mounted directory. To
|
||||||
empty directory or set `SWWAF_RULES_ENABLED=false`.
|
run without rules, mount an empty directory or set `SWWAF_RULES_ENABLED=false`.
|
||||||
|
|
||||||
## The Core Rule Set
|
## The Core Rule Set
|
||||||
|
|
||||||
@@ -998,9 +1040,9 @@ adds to the request's score. A score at or over `SWWAF_WAF_ANOMALY_THRESHOLD` is
|
|||||||
a match. A match is refused with `403` in `block` mode, and let through in
|
a match. A match is refused with `403` in `block` mode, and let through in
|
||||||
`detect` mode. Either way the log line gives the rule ids and the score, and a
|
`detect` mode. Either way the log line gives the rule ids and the score, and a
|
||||||
`waf_block` alert is sent. A request with more than 1000 query parameters or
|
`waf_block` alert is sent. A request with more than 1000 query parameters or
|
||||||
body fields adds 5 to its score, since Coraza reads only the first 1000. Three
|
body fields adds 5 to its score, since Coraza reads only the first 1000. The
|
||||||
kinds of request are not inspected: one a rule file refused, one for a path
|
Core Rule Set inspects only a request that every earlier check let through. It
|
||||||
under `SWWAF_WAF_EXEMPT_PATHS`, and one from a client in `SWWAF_ALLOW_NETS`.
|
skips a path under `SWWAF_WAF_EXEMPT_PATHS` and a client in `SWWAF_ALLOW_NETS`.
|
||||||
|
|
||||||
`smallwebwaf` changes the Core Rule Set in six ways, so that a code forge's
|
`smallwebwaf` changes the Core Rule Set in six ways, so that a code forge's
|
||||||
ordinary requests get through. The changes hold in front of every app. No
|
ordinary requests get through. The changes hold in front of every app. No
|
||||||
@@ -1018,11 +1060,11 @@ setting undoes them.
|
|||||||
(930120), shell paths (932160) and command names (932260). They are `path`,
|
(930120), shell paths (932160) and command names (932260). They are `path`,
|
||||||
`files`, `skip-to`, `sub_path`, `ref`, `sha`, `branch`, `workflow`,
|
`files`, `skip-to`, `sub_path`, `ref`, `sha`, `branch`, `workflow`,
|
||||||
`artifactName` and `redirect_to`. A file such as `.gitignore` or a branch such
|
`artifactName` and `redirect_to`. A file such as `.gitignore` or a branch such
|
||||||
as `docker-build` then gets through there. So does what only those three rules
|
as `docker-build` then gets through there. So does an attack only those three
|
||||||
refuse, such as `|cat /etc/passwd`. Path traversal and SQL injection are still
|
rules catch, such as `|cat /etc/passwd`. Path traversal and SQL injection are
|
||||||
refused there. Names are matched without regard to case. An app that uses one
|
still refused there. Names are matched without regard to case. An app that
|
||||||
of these parameters as a file on the server, or passes it to a shell, has to
|
uses one of these parameters as a file on the server, or passes it to a shell,
|
||||||
check it itself.
|
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).
|
||||||
@@ -1032,8 +1074,8 @@ Body inspection suits apps whose forms carry no code. In front of a code forge
|
|||||||
it refuses issue and comment text, wiki pages and files saved in the web editor
|
it refuses issue and comment text, wiki pages and files saved in the web editor
|
||||||
that hold shell commands or code. The log line names the rule ids, which
|
that hold shell commands or code. The log line names the rule ids, which
|
||||||
`SWWAF_WAF_DISABLED_RULES` can switch off. A body the Core Rule Set cannot parse
|
`SWWAF_WAF_DISABLED_RULES` can switch off. A body the Core Rule Set cannot parse
|
||||||
adds 5 to the score. So does a multipart body the limit cuts in the middle of a
|
adds 5 to the score. So does a multipart body cut by the limit in the middle of
|
||||||
part's header. The client has until `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out to
|
a part's header. The client has until `SWWAF_CLIENT_REQUEST_TIMEOUT` runs out to
|
||||||
send the part that is read.
|
send the part that is read.
|
||||||
|
|
||||||
## Admin endpoints
|
## Admin endpoints
|
||||||
@@ -1055,18 +1097,19 @@ is soon banned. While a token is unset, its endpoints answer `404`.
|
|||||||
lists it. Needs `SWWAF_ADMIN_TOKEN`, as do the three below.
|
lists it. Needs `SWWAF_ADMIN_TOKEN`, as do the three below.
|
||||||
- `POST /_smallwebwaf/bans`: bans a netblock. The body is a JSON object of
|
- `POST /_smallwebwaf/bans`: bans a netblock. The body is a JSON object of
|
||||||
`netblock`, `duration` and, if you like, `reason`. `netblock` is a netblock
|
`netblock`, `duration` and, if you like, `reason`. `netblock` is a netblock
|
||||||
such as `203.0.113.0/24`, or a client's address, which bans the netblock a ban
|
such as `203.0.113.0/24`, or a client's address. A client's address bans its
|
||||||
on that client covers. `duration` is a duration such as `1h` or `7d`, or
|
IPv4 netblock (`SWWAF_BAN_SCOPE_V4_PREFIX`) or its IPv6 group. `duration` is a
|
||||||
`permanent`. The ban starts at once, with the cause `admin`. A bad body is
|
duration such as `1h` or `7d`, or `permanent`. The ban starts at once, with
|
||||||
answered `400`, saying what is wrong: one that is not such an object, has
|
the cause `admin`. A bad body is answered `400`, saying what is wrong:
|
||||||
another field, is longer than 4 KiB, or names an IPv4-mapped netblock or an
|
- one that is not such an object, or has another field;
|
||||||
address with a zone.
|
- one longer than 4 KiB;
|
||||||
- `DELETE /_smallwebwaf/bans/<client>`: lifts every active ban on a netblock the
|
- one that names an IPv4-mapped netblock, or an address with a zone.
|
||||||
address `<client>` is in. It answers `404` when none is active.
|
- `DELETE /_smallwebwaf/bans/<client>`: lifts every active ban covering the
|
||||||
|
address `<client>`. It answers `404` when none is active.
|
||||||
- `GET /_smallwebwaf/clients/<ip>`: what `smallwebwaf` knows of the client.
|
- `GET /_smallwebwaf/clients/<ip>`: what `smallwebwaf` knows of the client.
|
||||||
Under `client`, its counters and history as `clients.json` holds them, or
|
Under `client`, its counters and history as `clients.json` holds them, or
|
||||||
`null` when it has never been seen. Under `bans`, every ban on a netblock the
|
`null` when it has never been seen. Under `bans`, every ban covering the
|
||||||
address is in, with its notes.
|
address, with its notes.
|
||||||
|
|
||||||
An admin whose own address is banned lifts that ban by editing `bans.json`.
|
An admin whose own address is banned lifts that ban by editing `bans.json`.
|
||||||
|
|
||||||
@@ -1098,8 +1141,8 @@ fields from `time` to `user_agent`, `request_id`, `peer_ip`, `client_group`,
|
|||||||
- the source cannot place the client;
|
- the source cannot place the client;
|
||||||
- GeoJS had not answered by the time the request went on.
|
- GeoJS had not answered by the time the request went on.
|
||||||
- `content_type` and `content_length` are the request's own. `request_headers`
|
- `content_type` and `content_length` are the request's own. `request_headers`
|
||||||
holds the headers `SWWAF_LOG_REQUEST_HEADERS` names, by name in lower case.
|
holds the headers listed in `SWWAF_LOG_REQUEST_HEADERS`, by name in lower
|
||||||
`has_authorization` and `has_cookie` are true when the request had those
|
case. `has_authorization` and `has_cookie` are true when the request had those
|
||||||
headers; their values are never logged. `websocket` is true when the app
|
headers; their values are never logged. `websocket` is true when the app
|
||||||
switched the connection to another protocol.
|
switched the connection to another protocol.
|
||||||
- `status` is what the client was sent, `0` if nothing. `upstream_status` is
|
- `status` is what the client was sent, `0` if nothing. `upstream_status` is
|
||||||
@@ -1117,8 +1160,9 @@ fields from `time` to `user_agent`, `request_id`, `peer_ip`, `client_group`,
|
|||||||
- `too_large` and `timed_out`: a size or time limit was passed;
|
- `too_large` and `timed_out`: a size or time limit was passed;
|
||||||
- `upstream_error`: the app could not be reached, or its answer broke off;
|
- `upstream_error`: the app could not be reached, or its answer broke off;
|
||||||
- `admin`: answered at one of `smallwebwaf`'s own endpoints.
|
- `admin`: answered at one of `smallwebwaf`'s own endpoints.
|
||||||
- `would_action` is there in `observe` mode. It names the action `enforce` mode
|
- In `observe` mode, `would_action` names what `enforce` mode would have done
|
||||||
would have taken. `action` then names what was done, normally `forward`.
|
with a request it would have refused. `action` then names what was done,
|
||||||
|
normally `forward`.
|
||||||
- `limit_percent` and `limit_percent_setting` give the client's percentage of
|
- `limit_percent` and `limit_percent_setting` give the client's percentage of
|
||||||
the rate limits and the setting that lowered it, when one did. `bytes_percent`
|
the rate limits and the setting that lowered it, when one did. `bytes_percent`
|
||||||
and `bytes_percent_setting` do the same for the byte limits.
|
and `bytes_percent_setting` do the same for the byte limits.
|
||||||
@@ -1126,8 +1170,8 @@ fields from `time` to `user_agent`, `request_id`, `peer_ip`, `client_group`,
|
|||||||
limiting counts them, this request included. `minute_bytes`, `hour_bytes` and
|
limiting counts them, this request included. `minute_bytes`, `hour_bytes` and
|
||||||
`day_bytes` give its bytes. Each window is counted in two fixed buckets, the
|
`day_bytes` give its bytes. Each window is counted in two fixed buckets, the
|
||||||
earlier one weighted by how much of it the window still covers, so a count can
|
earlier one weighted by how much of it the window still covers, so a count can
|
||||||
have a fraction. `counts` is left out for a request rate limiting does not
|
have a fraction. `counts` is left out when rate limiting did not count the
|
||||||
count.
|
request.
|
||||||
- `rule_ids` lists the rule file rules that matched, in order, up to the one
|
- `rule_ids` lists the rule file rules that matched, in order, up to the one
|
||||||
that refused. `waf_score` and `waf_rule_ids` give the Core Rule Set's score
|
that refused. `waf_score` and `waf_rule_ids` give the Core Rule Set's score
|
||||||
and the rules that matched, for a request it inspected.
|
and the rules that matched, for a request it inspected.
|
||||||
@@ -1135,8 +1179,8 @@ fields from `time` to `user_agent`, `request_id`, `peer_ip`, `client_group`,
|
|||||||
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 that list the client: blocklist URLs, the
|
- `reputation` lists the sources the client is on: blocklist URLs, the CrowdSec
|
||||||
CrowdSec decision list's URL, DNS blocklist zones, then `abuseipdb`.
|
decision list's URL, DNS blocklist zones, then `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
|
||||||
@@ -1155,33 +1199,38 @@ No body is logged, and no header but those above. `smallwebwaf`'s own messages
|
|||||||
While `SWWAF_LOG_REMOTE_URL` is set, every line on stdout is also sent to that
|
While `SWWAF_LOG_REMOTE_URL` is set, every line on stdout is also sent to that
|
||||||
server, as the message of an RFC 5424 syslog record. Over UDP each record is one
|
server, as the message of an RFC 5424 syslog record. Over UDP each record is one
|
||||||
datagram. Over TCP and TLS each record follows its length in bytes and a space.
|
datagram. Over TCP and TLS each record follows its length in bytes and a space.
|
||||||
The record carries the facility `SWWAF_LOG_REMOTE_FACILITY` names, the severity
|
stdout is unchanged. The record carries:
|
||||||
informational, the time, the host's name and the app name
|
|
||||||
`SWWAF_LOG_REMOTE_APP_NAME` gives. stdout is unchanged.
|
- the facility from `SWWAF_LOG_REMOTE_FACILITY`;
|
||||||
|
- the severity informational;
|
||||||
|
- the time;
|
||||||
|
- the host's name;
|
||||||
|
- the app name from `SWWAF_LOG_REMOTE_APP_NAME`.
|
||||||
|
|
||||||
Lines wait in a buffer of `SWWAF_LOG_REMOTE_BUFFER` lines, so a server that is
|
Lines wait in a buffer of `SWWAF_LOG_REMOTE_BUFFER` lines, so a server that is
|
||||||
slow or down never holds up a request. When the buffer is full, the oldest line
|
slow or down never holds up a request. When the buffer is full, the oldest line
|
||||||
is dropped. A failed connection is tried again a second later, and twice as long
|
is dropped. A failed connection is tried again a second later, and twice as long
|
||||||
after each further failure, up to a minute. A line too long for one UDP datagram
|
after each further failure, up to a minute. A line too long for one UDP datagram
|
||||||
is dropped alone. As `smallwebwaf` stops, it sends the lines still waiting for
|
is dropped alone. As `smallwebwaf` stops, it spends at most two seconds sending
|
||||||
at most two seconds and gives up the rest. stdout has carried them.
|
the lines still waiting, and drops the rest. stdout has carried them.
|
||||||
|
|
||||||
## Alerts
|
## Alerts
|
||||||
|
|
||||||
Each alert goes to each destination you name. `SWWAF_ALERT_WEBHOOK_URL` gets it
|
Each alert goes to each destination you name. `SWWAF_ALERT_WEBHOOK_URL` gets it
|
||||||
as one JSON object. `SWWAF_ALERT_SLACK_WEBHOOK_URL` and `SWWAF_ALERT_NTFY_URL`
|
as one JSON object. `SWWAF_ALERT_SLACK_WEBHOOK_URL` and `SWWAF_ALERT_NTFY_URL`
|
||||||
get a message made from it. An alert is one of these events, sent when
|
get a message made from it. An alert is one of these events, and is sent when
|
||||||
`SWWAF_ALERT_EVENTS` names it:
|
its event is in `SWWAF_ALERT_EVENTS`:
|
||||||
|
|
||||||
- `ban`: a ban `smallwebwaf` made.
|
- `ban`: a ban `smallwebwaf` made.
|
||||||
- `permanent_ban`: a permanent ban it made, or a ban a request made permanent.
|
- `permanent_ban`: a permanent ban it made, or an existing ban made permanent by
|
||||||
- `waf_block`: a request the Core Rule Set scored at or over the threshold. In
|
a request.
|
||||||
|
- `waf_block`: the Core Rule Set scored a request at or over the threshold. In
|
||||||
`block` mode the request was refused. In `detect` mode it was not, and the
|
`block` mode the request was refused. In `detect` mode it was not, and the
|
||||||
alert's `detail` gives `mode`.
|
alert's `detail` gives `mode`.
|
||||||
- `anomaly`: a count over an anomaly threshold.
|
- `anomaly`: a count over an anomaly threshold.
|
||||||
- `reputation_hit`: a request whose client a blocklist, the CrowdSec decision
|
- `reputation_hit`: a request from a client on a blocklist, the CrowdSec
|
||||||
list or a DNS blocklist lists, or whose AbuseIPDB score is a hit. One alert
|
decision list or a DNS blocklist, or with an AbuseIPDB score that is a hit.
|
||||||
per source.
|
One alert per source.
|
||||||
- `source_failure`: GeoJS failing, a list that cannot be fetched, a DNS
|
- `source_failure`: GeoJS failing, a list that cannot be fetched, a DNS
|
||||||
blocklist or AbuseIPDB failing or refusing a query, or the day's AbuseIPDB
|
blocklist or AbuseIPDB failing or refusing a query, or the day's AbuseIPDB
|
||||||
checks used up.
|
checks used up.
|
||||||
@@ -1189,9 +1238,9 @@ get a message made from it. An alert is one of these events, sent when
|
|||||||
cannot be written, or a replacement lookup database that cannot be read.
|
cannot be written, or 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 raises the alert `enforce` mode would have, with `mode` set to
|
made a ban still raises the ban's alert, with `mode` set to `observe` in its
|
||||||
`observe` in its `detail`. This is the alert for a ban for a broken rate limit,
|
`detail`. This is the alert for a ban for a broken rate limit, shown indented;
|
||||||
shown indented; it is sent on one line:
|
it is sent on one line:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -1255,11 +1304,16 @@ shown indented; it is sent on one line:
|
|||||||
alert.
|
alert.
|
||||||
|
|
||||||
Slack and ntfy get a message with a title and a text. The title is the instance
|
Slack and ntfy get a message with a title and a text. The title is the instance
|
||||||
and the event, such as `fsn1app1/gitea: ban`. The text is the `reason`, then a
|
and the event, such as `fsn1app1/gitea: ban`. The text is the `reason`, then one
|
||||||
line for each of the client, netblock, country, file, source, error and mode the
|
line for each of these that the alert has:
|
||||||
alert has, and the suppressed repeats if any. Slack is posted the title in bold
|
|
||||||
and the text below it, with `&`, `<` and `>` escaped. ntfy is posted the text,
|
- the client, the netblock and the country;
|
||||||
with the title in `Title`, the token if set, and a priority and a tag by event:
|
- the file, the source, the error and the mode from `detail`;
|
||||||
|
- the suppressed repeats, if any.
|
||||||
|
|
||||||
|
Slack is posted the title in bold and the text below it, with `&`, `<` and `>`
|
||||||
|
escaped. ntfy is posted the text, with the title in `Title`, the token if set,
|
||||||
|
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`.
|
||||||
@@ -1379,10 +1433,13 @@ write. A hard kill loses what changed since the last write.
|
|||||||
|
|
||||||
At start the files are read back. Each client keeps its counts, so a restart
|
At start the files are read back. Each client keeps its counts, so a restart
|
||||||
gives no one a fresh allowance. Each ban keeps refusing until it ends. A missing
|
gives no one a fresh allowance. Each ban keeps refusing until it ends. A missing
|
||||||
file is empty state. A file that does not parse, or has another `version`, stops
|
file is empty state. These stop the start, with a message naming the file and
|
||||||
the start with a message naming the file and the place. So does an entry without
|
the place:
|
||||||
a field it needs, a ban whose `cause` is not `limit`, `attack`, `admin` or
|
|
||||||
`crowdsec`, and a ban whose `expires` is not after its `start`.
|
- a file that does not parse, or has another `version`;
|
||||||
|
- an entry without a field it needs;
|
||||||
|
- a ban whose `cause` is not `limit`, `attack`, `admin` or `crowdsec`;
|
||||||
|
- a ban whose `expires` is not after its `start`.
|
||||||
|
|
||||||
While it runs, `smallwebwaf` watches the directory and takes in your edit of a
|
While it runs, `smallwebwaf` watches the directory and takes in your edit of a
|
||||||
file as soon as you save it. What the file then holds replaces what it held. It
|
file as soon as you save it. What the file then holds replaces what it held. It
|
||||||
@@ -1400,52 +1457,66 @@ back. A file you remove is written again at its next write.
|
|||||||
By default `smallwebwaf` asks GeoJS, a free web service that needs no account,
|
By default `smallwebwaf` asks GeoJS, a free web service that needs no account,
|
||||||
about every new visitor's address. It asks whether or not a setting uses the
|
about every new visitor's address. It asks whether or not a setting uses the
|
||||||
answer, since the log line, the history, the ban notes and the metrics carry the
|
answer, since the log line, the history, the ban notes and the metrics carry the
|
||||||
AS number and country. Four kinds of visitor are not sent: those in
|
AS number and country. These addresses are never sent:
|
||||||
`SWWAF_ALLOW_NETS` or `SWWAF_DENY_NETS`, those a ban covers, and those on a
|
|
||||||
private, loopback or link-local address, which no source can place. An IPv6
|
|
||||||
visitor is asked about by the first address of its group. Each answer is kept
|
|
||||||
for seven days, in memory and in `lookups.json`.
|
|
||||||
|
|
||||||
A request waits for its client's first answer, up to `SWWAF_LOOKUP_TIMEOUT`,
|
- one in `SWWAF_ALLOW_NETS` or `SWWAF_DENY_NETS`;
|
||||||
only while a setting acts on the answer before the request goes on: a country
|
- one under a ban;
|
||||||
list, `SWWAF_ADD_LOOKUP_HEADERS`, or any setting under "Lower limits by AS
|
- a private, loopback or link-local address, which no source can place.
|
||||||
number and country" above. Otherwise it goes on at once, logged without the
|
|
||||||
answer. The answer reaches the client's history and ban notes when it comes.
|
An IPv6 visitor is asked about by the first address of its group. Each answer is
|
||||||
Addresses are asked about together, up to 200 in one request, one request at a
|
kept for seven days, in memory and in `lookups.json`.
|
||||||
time. While GeoJS fails, new visitors count as from an unknown country, which
|
|
||||||
|
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
|
||||||
|
settings that make it wait:
|
||||||
|
|
||||||
|
- a country list;
|
||||||
|
- `SWWAF_ADD_LOOKUP_HEADERS`;
|
||||||
|
- any setting under "Lower limits by AS number and country" above.
|
||||||
|
|
||||||
|
Otherwise the request goes on at once, logged without the answer. The answer
|
||||||
|
reaches the client's history and ban notes when it comes. Addresses are asked
|
||||||
|
about together, up to 200 in one request, one request at a time. While GeoJS
|
||||||
|
fails, new visitors count as from an unknown country, which
|
||||||
`SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` refuses. GeoJS is then left alone for a
|
`SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` refuses. GeoJS is then left alone for a
|
||||||
second, and twice as long after each further failure, up to five minutes.
|
second, and twice as long after each further failure, up to five minutes.
|
||||||
|
|
||||||
To keep your visitors' addresses on your own host, set
|
To keep your visitors' addresses on your own host, set
|
||||||
`SWWAF_LOOKUP_SOURCE=off`, or set it to `file`. `file` looks every client up in
|
`SWWAF_LOOKUP_SOURCE=off`, or set it to `file`. `file` looks every client up in
|
||||||
the free IPinfo Lite database (`ipinfo_lite.mmdb`), which `SWWAF_LOOKUP_DB_PATH`
|
the free IPinfo Lite database (`ipinfo_lite.mmdb`) at `SWWAF_LOOKUP_DB_PATH`.
|
||||||
names. You download the database with an IPinfo account and mount it into the
|
You download the database with an IPinfo account and mount it into the
|
||||||
container. It answers at once, with nothing sent off the host. A client whose
|
container. It answers at once, with nothing sent off the host. A client whose
|
||||||
address it does not hold counts as from an unknown country. `smallwebwaf` reads
|
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 rather than the file itself: docker does not show a single
|
holds the file rather than the file itself: many editors and download tools
|
||||||
mounted file being replaced. IPinfo releases the database under the Creative
|
replace a file when they save it, and a container keeps seeing the old one when
|
||||||
|
the file alone is mounted. IPinfo releases the database under the Creative
|
||||||
Commons Attribution-ShareAlike 4.0 licence. It asks for a link to IPinfo on the
|
Commons Attribution-ShareAlike 4.0 licence. It asks for a link to IPinfo on the
|
||||||
site 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
|
||||||
|
|
||||||
`SWWAF_BLOCKLIST_URLS` names text files of addresses and netblocks, one to a
|
`SWWAF_BLOCKLIST_URLS` lists text files of addresses and netblocks, one to a
|
||||||
line, as the Spamhaus DROP list is written. Anything after a `;` or `#` is left
|
line, as the Spamhaus DROP list is written. Anything after a `;` or `#` is left
|
||||||
out. An IPv4-mapped address such as `::ffff:192.0.2.0/120` is read as the IPv4
|
out. An IPv4-mapped address such as `::ffff:192.0.2.0/120` is read as the IPv4
|
||||||
netblock it stands for.
|
netblock it stands for.
|
||||||
|
|
||||||
Each list, and the file `SWWAF_ASN_LIMIT_PERCENT_URL` names, is fetched
|
Each list is fetched again `SWWAF_BLOCKLIST_REFRESH` after its last try, one
|
||||||
`SWWAF_BLOCKLIST_REFRESH` after it was last fetched or tried, one after another.
|
list at a time. The file of `SWWAF_ASN_LIMIT_PERCENT_URL` is too.
|
||||||
`reputation.json` keeps when each was last tried, so a restart does not fetch a
|
`reputation.json` keeps when each was last tried, so a restart does not fetch a
|
||||||
list again before its time. A fetch fails when the server answers other than
|
list again before its time. A fetch fails when:
|
||||||
`200`, does not finish within a minute, or sends more than 16 MiB. It also fails
|
|
||||||
on a line that is not an address or netblock, or, for the AS number file, not
|
- the server answers other than `200`;
|
||||||
`AS<number>:<percent>`. The copy fetched before stays in use. The failure is
|
- the fetch does not finish within a minute;
|
||||||
logged and raised as a `source_failure` alert. Each list is named by its URL in
|
- the list is longer than 16 MiB;
|
||||||
the log, the alerts, the ban notes and the metrics, so keep a secret out of it.
|
- a line is not an address or netblock, or, for the AS number file, not
|
||||||
|
`AS<number>:<percent>`.
|
||||||
|
|
||||||
|
The copy fetched before stays in use. The failure is logged and raised as a
|
||||||
|
`source_failure` alert. Each list is named by its URL in the log, the alerts,
|
||||||
|
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
|
||||||
@@ -1463,10 +1534,15 @@ background, the first time the client is seen. The request goes on at once, and
|
|||||||
so does every request from the client until the zone has answered. The name
|
so does every request from the client until the zone has answered. The name
|
||||||
asked about is the one RFC 5782 gives: the numbers of an IPv4 address reversed,
|
asked about is the one RFC 5782 gives: the numbers of an IPv4 address reversed,
|
||||||
or the 32 hex digits of an IPv6 address reversed. `192.0.2.99` is asked about as
|
or the 32 hex digits of an IPv6 address reversed. `192.0.2.99` is asked about as
|
||||||
`99.2.0.192.dnsbl.dronebl.org`. An answer in `127.0.0.0/8` lists the client. A
|
`99.2.0.192.dnsbl.dronebl.org`. An answer in `127.0.0.0/8` lists the client, but
|
||||||
name that does not exist does not. Any other answer is a failure: an address in
|
one in `127.255.255.0/24` is Spamhaus refusing the query. A name that does not
|
||||||
`127.255.255.0/24`, with which Spamhaus refuses a query, an address outside
|
exist does not list the client. These are failures:
|
||||||
`127.0.0.0/8`, a resolver error, or no answer within `SWWAF_REPUTATION_TIMEOUT`.
|
|
||||||
|
- an answer in `127.255.255.0/24`;
|
||||||
|
- an answer outside `127.0.0.0/8`;
|
||||||
|
- a resolver error;
|
||||||
|
- no answer within `SWWAF_REPUTATION_TIMEOUT`.
|
||||||
|
|
||||||
A failure is logged and alerted, and the zone is not asked again for a minute.
|
A failure is logged and alerted, and the zone is not asked again for a minute.
|
||||||
Each verdict is kept for `SWWAF_REPUTATION_CACHE_TTL`, in memory and in
|
Each verdict is kept for `SWWAF_REPUTATION_CACHE_TTL`, in memory and in
|
||||||
`reputation.json`.
|
`reputation.json`.
|
||||||
@@ -1479,10 +1555,14 @@ The key of a Spamhaus zone under `dq.spamhaus.net` is its first label.
|
|||||||
|
|
||||||
While `SWWAF_ABUSEIPDB_KEY` is set, `smallwebwaf` asks AbuseIPDB's check
|
While `SWWAF_ABUSEIPDB_KEY` is set, `smallwebwaf` asks AbuseIPDB's check
|
||||||
endpoint for the abuse confidence score of a client, from 0 to 100. Only a
|
endpoint for the abuse confidence score of a client, from 0 to 100. Only a
|
||||||
client that has committed an offence is checked: one that broke a limit, matched
|
client that has committed an offence is checked. An offence is one of:
|
||||||
a `ban` rule, asked for a trap path, or was refused by a `block` rule, the Core
|
|
||||||
Rule Set or a token check. The check runs in the background, at the client's
|
- a broken limit;
|
||||||
first request after the offence, so no request waits. The score applies to the
|
- a `ban` rule match or a trap path;
|
||||||
|
- a refusal by a `block` rule, the Core Rule Set or a token check.
|
||||||
|
|
||||||
|
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
|
||||||
client's later requests. An IPv6 group is checked by the address of the request
|
client's later requests. An IPv6 group is checked by the address of the request
|
||||||
that has it checked, and the score is used for the whole group. A score at or
|
that has it checked, and the score is used for the whole group. A score at or
|
||||||
over `SWWAF_ABUSEIPDB_MIN_SCORE` is a hit, and `SWWAF_REPUTATION_ACTION` says
|
over `SWWAF_ABUSEIPDB_MIN_SCORE` is a hit, and `SWWAF_REPUTATION_ACTION` says
|
||||||
@@ -1498,21 +1578,21 @@ sent in the `Key` header and nowhere else, and is never logged.
|
|||||||
|
|
||||||
## CrowdSec
|
## CrowdSec
|
||||||
|
|
||||||
While `SWWAF_CROWDSEC_LAPI_URL` names the local API of a CrowdSec engine you
|
While `SWWAF_CROWDSEC_LAPI_URL` is set to the local API of a CrowdSec engine you
|
||||||
run, `smallwebwaf` fetches its decision list every minute. It sends
|
run, `smallwebwaf` fetches its decision list every minute. It sends
|
||||||
`SWWAF_CROWDSEC_LAPI_KEY` in the `X-Api-Key` header. The list is kept as a
|
`SWWAF_CROWDSEC_LAPI_KEY` in the `X-Api-Key` header. The list is kept as a
|
||||||
blocklist is kept. The decisions of the type `ban` on an address or a netblock
|
blocklist is kept. The decisions of the type `ban` on an address or a netblock
|
||||||
are used. Any other, such as a captcha or a country, is left out. A decision
|
are used. Any other, such as a captcha or a country, is left out. A decision
|
||||||
lasts for the `duration` it had left when the engine answered.
|
lasts for the `duration` it had left when the engine answered.
|
||||||
|
|
||||||
A request from a client a decision in force lists is refused. It bans the
|
A request is refused when a decision still in force names its client. The
|
||||||
client's netblock until the decision ends, with the cause `crowdsec`. Only a
|
client's netblock is banned until the decision ends, with the cause `crowdsec`.
|
||||||
client that sends a request is banned, so a long list does not fill `bans.json`.
|
Only a client that sends a request is banned, so a long list does not fill
|
||||||
Such a ban is never made permanent, and does not lengthen the netblock's next
|
`bans.json`. Such a ban is never made permanent, and does not lengthen the
|
||||||
ban for a broken limit. A ban you lift while the decision is in force is
|
netblock's next ban for a broken limit. A ban you lift while the decision is in
|
||||||
followed by another at the client's next request. 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 the engine
|
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
|
answers other than `200`, a redirect included. It also fails when the fetch does
|
||||||
not finish within a minute, the answer is longer than 16 MiB, or the answer is
|
not finish within a minute, the answer is longer than 16 MiB, or the answer is
|
||||||
not a list of decisions.
|
not a list of decisions.
|
||||||
|
|||||||
Reference in New Issue
Block a user