SPEC: the one place for behaviour and reasons; docs/ as lookup lists only
check / check (push) Waiting to run
check / check (push) Waiting to run
docs/core-rule-set.md, docs/lookup.md, docs/reputation.md and docs/bans.md restated the spec and are folded back into it and removed. The remaining docs files hold only lists: settings with their defaults and value forms, the request log's fields, the metrics, the endpoints, the state files and their contents, the rule file format and default file, the alert events and messages, the image's contents and pins, and the gitea notes. Every behaviour each of them held moves to the spec, said once. The settings groups have the same names in both files. Stale comments in internal/state and script/example-app point at the right places. Model: fable-5-1
This commit is contained in:
+221
-378
@@ -1,27 +1,19 @@
|
||||
# Settings
|
||||
|
||||
Every setting is an environment variable whose name starts with `SWWAF_`. Every
|
||||
setting has a default, so none has to be set. Settings are read once, at start.
|
||||
To change one, restart the container.
|
||||
Every setting, its default and the values it takes, in the groups of "Settings"
|
||||
in [`SPEC.md`](../SPEC.md). That section gives the conventions, the settings
|
||||
that take `off`, the `_FILE` form, and what each group does and why. A setting
|
||||
set to a value its entry does not allow stops the start.
|
||||
|
||||
A setting that is set to a value it cannot take stops the start. The process
|
||||
writes one line, `"type":"process"` with `"msg":"invalid setting"`, naming the
|
||||
variable and what is wrong with the value. It exits with status 1, and runit
|
||||
starts it again every second until the value is fixed. The entries below say
|
||||
which values each setting takes. Any other value stops the start. At start, the
|
||||
settings in effect are logged on one line. Tokens, keys and the paths of
|
||||
webhooks are shown as `********`.
|
||||
|
||||
The forms of values:
|
||||
## Forms of values
|
||||
|
||||
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`. Zero
|
||||
and negative durations are refused.
|
||||
- A size is a number of bytes with an optional `K`, `M` or `G`, which are powers
|
||||
of 1024: `1K` is 1024 bytes, `512K`, `100M`, `5G`.
|
||||
of 1024, so `1K` is 1024 bytes. Examples: `512K`, `100M`, `5G`.
|
||||
- A count is a whole number of requests, such as `1000`.
|
||||
- `off` switches a setting off, where its entry below says it takes `off`.
|
||||
- A list is comma separated, with spaces around items ignored. An empty item is
|
||||
refused. A list set but empty is an empty list, and replaces the default.
|
||||
refused.
|
||||
- A netblock is an address and a prefix length, such as `10.0.0.0/8`. A bare
|
||||
address stands for itself alone.
|
||||
- A country is a two-letter code of ISO 3166-1, in either case: `de` and `DE`
|
||||
@@ -31,337 +23,58 @@ The forms of values:
|
||||
- A percentage is a whole number from 0 to 100.
|
||||
- A path prefix starts with `/`, such as `/assets/`.
|
||||
|
||||
## Where it listens and what it protects
|
||||
## Core
|
||||
|
||||
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens, an
|
||||
optional host and a port. Keep the host empty: `:9000`, never
|
||||
`127.0.0.1:9000`. The app must not listen on that port. The health check
|
||||
follows the setting, and traefik's label must name the port.
|
||||
- `SWWAF_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An `http` or
|
||||
`https` URL with a host and an optional port, and nothing more: no path, user
|
||||
or query.
|
||||
- `SWWAF_INSTANCE_NAME` (default: the container's host name, the first 12
|
||||
characters of the container's id): the `instance` name in every log line,
|
||||
alert and metric. Set it to a name that survives deploys and tells instances
|
||||
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_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.
|
||||
- `SWWAF_MODE` (default `enforce`): `enforce`, or `observe` to ban no one and
|
||||
log what `enforce` would have done. Observe mode still refuses a request over
|
||||
a size or time limit, and one without the right token.
|
||||
- `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
|
||||
believed, normally where traefik comes from. Set but empty, it trusts nothing:
|
||||
every TCP peer is then the client.
|
||||
- `SWWAF_IPV6_GROUP_PREFIX` (default `64`): the length of the netblock that is
|
||||
one IPv6 client, from 32 to 128. After a change, each IPv6 client starts
|
||||
afresh. Existing bans keep refusing their netblocks until they end.
|
||||
- `SWWAF_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept in memory
|
||||
and in `clients.json`, a whole number above zero. Past it, the least recently
|
||||
seen client is dropped first.
|
||||
|
||||
## Time and size limits
|
||||
|
||||
Each request has limits on both sides: between the client and `smallwebwaf`, and
|
||||
between `smallwebwaf` and the app. Bodies stream through in both directions,
|
||||
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
|
||||
send its request line and headers, and then, from the end of the headers, its
|
||||
body. A duration or `off`.
|
||||
- `SWWAF_CLIENT_REQUEST_HEADER_MAX_BYTES` (default `32K`): the largest request
|
||||
line and headers. A size over `4K`. It cannot be `off`: Go's HTTP server
|
||||
always has such a limit.
|
||||
- `SWWAF_CLIENT_IDLE_TIMEOUT` (default `120s`): how long a kept-open connection
|
||||
may wait for its next request before it is closed. A duration or `off`. The
|
||||
default is longer than the 90 seconds after which traefik closes an unused
|
||||
connection, so traefik closes first.
|
||||
- `SWWAF_CLIENT_RESPONSE_TIMEOUT` (default `30m`): how long the answer may take
|
||||
to reach the client, from the end of the request to the last byte. A duration
|
||||
or `off`.
|
||||
- `SWWAF_UPSTREAM_REQUEST_TIMEOUT` (default `60s`): how long connecting to the
|
||||
app and sending it the whole request may take. A duration or `off`.
|
||||
- `SWWAF_UPSTREAM_RESPONSE_TIMEOUT` (default `30m`): how long the app may take
|
||||
to send its whole answer. A duration or `off`.
|
||||
- `SWWAF_REQUEST_MAX_BYTES` (default `100M`): the largest request body. A size
|
||||
or `off`. A request that announces a larger body is refused before anything
|
||||
reaches the app.
|
||||
- `SWWAF_RESPONSE_MAX_BYTES` (default `5G`): the largest response body. A size
|
||||
or `off`.
|
||||
|
||||
In front of a code forge, a clone is a response and a push is a request. At the
|
||||
defaults, an upload over 100 MiB or slower than 60 seconds is cut off. A forge
|
||||
that takes large uploads needs `SWWAF_REQUEST_MAX_BYTES`,
|
||||
`SWWAF_CLIENT_REQUEST_TIMEOUT` and `SWWAF_UPSTREAM_REQUEST_TIMEOUT` raised to
|
||||
fit.
|
||||
|
||||
What the client gets when a limit is passed is under "Size and time limits" in
|
||||
[`SPEC.md`](../SPEC.md).
|
||||
|
||||
## Netblock lists
|
||||
|
||||
- `SWWAF_ALLOW_NETS` (default empty): netblocks whose clients skip every check,
|
||||
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
|
||||
endpoints.
|
||||
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
||||
exempt from rate limiting and the byte limits, such as a machine that talks to
|
||||
the app all day. Everything else still applies to them.
|
||||
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
||||
A client in both lists is let through.
|
||||
|
||||
## Rate limiting and byte limits
|
||||
|
||||
- `SWWAF_RATE_LIMIT_PER_MINUTE` (default `1000`), `SWWAF_RATE_LIMIT_PER_HOUR`
|
||||
(default `10000`) and `SWWAF_RATE_LIMIT_PER_DAY` (default `50000`): the most
|
||||
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
|
||||
makes a few hundred requests, and several people often share one address.
|
||||
- `SWWAF_RATE_LIMIT_EXEMPT_PATHS` (default empty): path prefixes exempt from
|
||||
rate limiting, such as `/assets/`. Their bytes are not counted either. The
|
||||
path is matched exactly as sent, without the query, and is not
|
||||
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
|
||||
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.
|
||||
- `SWWAF_BYTES_LIMIT_PER_MINUTE` (default `10G`), `SWWAF_BYTES_LIMIT_PER_HOUR`
|
||||
(default `20G`) and `SWWAF_BYTES_LIMIT_PER_DAY` (default `50G`): the most
|
||||
bytes a client may have counted in each window. A size or `off`. Bytes are
|
||||
counted once the answer has ended, so a byte limit never cuts an answer short.
|
||||
The client's next request is refused instead. Each default is above the
|
||||
largest request and answer together, so at the defaults no single download
|
||||
breaks a limit.
|
||||
- `SWWAF_BYTES_COUNT` (default `both`): which body bytes the byte limits count:
|
||||
`response`, `request` or `both`. For a WebSocket, what it carried each way
|
||||
counts once it closes.
|
||||
|
||||
## Looking up the AS number and country
|
||||
|
||||
- `SWWAF_LOOKUP_SOURCE` (default `geojs`): `geojs`, `file` or `off`. `geojs`
|
||||
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 at
|
||||
`SWWAF_LOOKUP_DB_PATH`, and sends nothing off the host. `off` looks up no
|
||||
client. With `off`, a setting that needs lookups stops the start:
|
||||
- a country list;
|
||||
- `SWWAF_ADD_LOOKUP_HEADERS`;
|
||||
- an entry in `SWWAF_ASN_LIMIT_PERCENT`, `SWWAF_COUNTRY_LIMIT_PERCENT`,
|
||||
`SWWAF_ASN_BYTES_PERCENT` or `SWWAF_COUNTRY_BYTES_PERCENT`;
|
||||
- `SWWAF_UNKNOWN_LIMIT_PERCENT` below 100;
|
||||
- `SWWAF_ASN_LIMIT_PERCENT_URL`;
|
||||
- an anomaly threshold per AS number.
|
||||
- `SWWAF_LOOKUP_DB_PATH` (default empty): the IPinfo Lite database file, in its
|
||||
`.mmdb` form, for `SWWAF_LOOKUP_SOURCE=file`. `file` without it stops the
|
||||
start, and so does setting it with any other source. A file that is missing or
|
||||
cannot be read stops the start too.
|
||||
- `SWWAF_LOOKUP_TIMEOUT` (default `1s`): how long a request waits for its
|
||||
client's first answer from GeoJS when a setting needs it, and how long a
|
||||
request to GeoJS may take. A duration; it cannot be `off`.
|
||||
- `SWWAF_ADD_LOOKUP_HEADERS` (default `false`): `true` passes the app the
|
||||
client's AS number in `X-Client-ASN` and its country in `X-Client-Country`. A
|
||||
header whose value is unknown is left out. Whatever this setting says, any
|
||||
such header the client sent is removed.
|
||||
|
||||
See [`lookup.md`](lookup.md) for what each source sends where, and how long
|
||||
answers are kept.
|
||||
|
||||
## Country lists
|
||||
|
||||
- `SWWAF_DENIED_COUNTRIES` (default empty): countries whose clients are refused,
|
||||
such as `cn,ru,kp`.
|
||||
- `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
|
||||
countries whose clients get through, such as `us,de`. A client whose country
|
||||
cannot be found is refused too. Otherwise every new client would be let in
|
||||
whenever GeoJS stops answering. A client on a private address has no country,
|
||||
so it is refused unless it is in `SWWAF_ALLOW_NETS`.
|
||||
|
||||
A country on both lists stops the start. What a refused request gets, and why
|
||||
the lists are empty by default, is under "Country lists" in
|
||||
[`SPEC.md`](../SPEC.md).
|
||||
|
||||
## Lower limits by AS number and country
|
||||
|
||||
Clients of the AS numbers and countries listed here get a percentage of every
|
||||
rate limit and byte limit. A lowered limit is rounded down. "Biased thresholds"
|
||||
in [`SPEC.md`](../SPEC.md) says why the lists are empty by default and why no
|
||||
budget is shared by a whole AS number or country.
|
||||
|
||||
- `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
|
||||
listed twice is refused.
|
||||
- `SWWAF_COUNTRY_LIMIT_PERCENT` (default empty): the same by country, such as
|
||||
`cn:25,ru:50`.
|
||||
- `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
|
||||
list, they take the place of the two settings above.
|
||||
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage of every limit
|
||||
for a client without a country:
|
||||
- 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
|
||||
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
|
||||
it and `SWWAF_ASN_LIMIT_PERCENT` both give a percentage, the lower applies. A
|
||||
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
|
||||
each counted request, and the notes of each ban, say which setting lowered the
|
||||
limit.
|
||||
|
||||
## Reputation sources
|
||||
|
||||
None is on by default. A reputation source judges a client by what others saw it
|
||||
do. The defaults judge it by what it does to your service.
|
||||
|
||||
- `SWWAF_BLOCKLIST_URLS` (default empty): blocklists to fetch, such as
|
||||
`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.
|
||||
- `SWWAF_BLOCKLIST_REFRESH` (default `24h`): how long a list waits after its
|
||||
last fetch, or failed try, before it is fetched again. A duration of `1h` or
|
||||
more; it cannot be `off`. The Spamhaus lists may be fetched no more than once
|
||||
an hour.
|
||||
- `SWWAF_BLOCKLIST_ACTION` (default `deny`): what is done with a client on a
|
||||
blocklist. `deny` refuses its requests. `limit:<percent>`, such as `limit:25`,
|
||||
gives it that percentage of every limit. `log` only notes the list in the log
|
||||
line and raises an alert.
|
||||
- `SWWAF_DNSBL_ZONES` (default empty): the DNS blocklists to ask about each
|
||||
client, such as `dnsbl.dronebl.org`. A DNS blocklist answers a DNS query for a
|
||||
name made from an address, and so says whether the address is on it. A zone is
|
||||
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
|
||||
twice is refused, whatever its case or key. Do not name a zone meant for mail:
|
||||
such a zone holds the residential addresses ordinary visitors come from.
|
||||
- `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
|
||||
port, 53 when none is given. Unset, the host's resolver is used. Several zones
|
||||
refuse queries that come through a public resolver.
|
||||
- `SWWAF_ABUSEIPDB_KEY` (default unset): the key of your AbuseIPDB account.
|
||||
While it is set, a client that has committed an offence is checked. A control
|
||||
character in it is refused.
|
||||
- `SWWAF_ABUSEIPDB_MIN_SCORE` (default `75`): the least abuse confidence score,
|
||||
from 0 to 100, that is a hit.
|
||||
- `SWWAF_ABUSEIPDB_DAILY_BUDGET` (default `900`): the most checks made in a day,
|
||||
in UTC, a whole number above zero. AbuseIPDB's free accounts may make 1,000.
|
||||
- `SWWAF_CROWDSEC_LAPI_URL` (default unset): the local API of a CrowdSec engine
|
||||
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
|
||||
is fetched every minute. Set without the key, it stops the start. So does a
|
||||
decision list URL that is also in `SWWAF_BLOCKLIST_URLS`, or that is
|
||||
`SWWAF_ASN_LIMIT_PERCENT_URL`.
|
||||
- `SWWAF_CROWDSEC_LAPI_KEY` (default unset): the key the engine gave for
|
||||
`smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it. Set without the
|
||||
URL, it stops the start.
|
||||
- `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`,
|
||||
`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
|
||||
quarter of every limit.
|
||||
- `SWWAF_REPUTATION_CACHE_TTL` (default `24h`): how long a zone's verdict or
|
||||
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
|
||||
check with AbuseIPDB may take. A duration; it cannot be `off`.
|
||||
|
||||
See [`reputation.md`](reputation.md) for how each source is asked and what it
|
||||
does with the answer.
|
||||
|
||||
## Attack detection
|
||||
|
||||
- `SWWAF_RULES_DIR` (default `/etc/smallwebwaf/rules.d`): the directory of the
|
||||
rule files (see [`rules.md`](rules.md)). A directory that cannot be read stops
|
||||
the start. An empty one is no error.
|
||||
- `SWWAF_RULES_ENABLED` (default `true`): `false` reads no rule file.
|
||||
- `SWWAF_TRAP_PATHS` (default empty): paths the app never serves and only
|
||||
scanners ask for, such as `/wp-login.php,/xmlrpc.php` in front of a code
|
||||
forge. A request for one bans the client, as a `ban` rule does. Each starts
|
||||
with `/` and holds no `?`. A trap path is matched whole, without the query,
|
||||
against the path as sent and once percent-decoded. `/wp-login.php` matches
|
||||
`/wp-login.php?x=1` and `/%77p-login.php`. It does not match `/wp-login.php/`
|
||||
or `/blog/wp-login.php`.
|
||||
- `SWWAF_WAF_MODE` (default `block`): what the Core Rule Set does with a request
|
||||
it takes for an attack. `block` refuses it with `403`. `detect` lets it
|
||||
through, logged and alerted. `off` inspects no request.
|
||||
- `SWWAF_WAF_PARANOIA_LEVEL` (default `1`): the Core Rule Set's paranoia level,
|
||||
from 1 to 4. Each level up runs more rules. Those find more attacks, and take
|
||||
more ordinary requests for attacks.
|
||||
- `SWWAF_WAF_ANOMALY_THRESHOLD` (default `5`): the score at which a request is a
|
||||
match. Each rule that matches adds to the request's score, and a rule rated
|
||||
critical adds 5. So by default one such rule is enough. A whole number above
|
||||
zero, or `off`. With `off` no request is a match, and scores are still logged.
|
||||
- `SWWAF_WAF_DISABLED_RULES` (default
|
||||
`920340,920420,920440,920640,930130,930140`): the ids of Core Rule Set rules
|
||||
to switch off. The log line names the rules a request matched, so a rule that
|
||||
refuses your app's ordinary requests can be found there. "Attack detection" in
|
||||
[`SPEC.md`](../SPEC.md) says what the six default ids refuse and why they are
|
||||
off. A list you give replaces the default, so name these six ids in it to keep
|
||||
them off. An id must be a whole number above zero. An id from 900000 to 900999
|
||||
is refused: those are the rules that set the Core Rule Set up, and
|
||||
`smallwebwaf`'s own.
|
||||
- `SWWAF_WAF_EXEMPT_PATHS` (default empty): path prefixes the Core Rule Set does
|
||||
not inspect, matched as `SWWAF_RATE_LIMIT_EXEMPT_PATHS` is.
|
||||
- `SWWAF_WAF_BODY_LIMIT` (default `off`): `off` reads no request body. A size,
|
||||
at most `1G`, switches body inspection on. The Core Rule Set then reads a form
|
||||
data or multipart body up to that size, and the rest streams on to the app. It
|
||||
reads a JSON or XML body no larger than that size, since those cannot be read
|
||||
in part. Any other body, such as a git push, reaches the app uninspected. See
|
||||
[`core-rule-set.md`](core-rule-set.md).
|
||||
- `SWWAF_ERROR_BURST_THRESHOLD` (default `30`): the most refusals of one client
|
||||
in a minute for a rule match, a trap path, a Core Rule Set match or a failed
|
||||
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.
|
||||
|
||||
## How long bans last and what they cover
|
||||
|
||||
- `SWWAF_BAN_RESPONSE` (default `403`): how a refused client is answered: `403`,
|
||||
`429`, or `close` to close the connection without an answer. A `block` rule
|
||||
and the Core Rule Set always answer `403`.
|
||||
- `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`.
|
||||
- `SWWAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`): a client that breaks a limit
|
||||
again within this time after its ban ended is banned three times as long. A
|
||||
duration; it cannot be `off`.
|
||||
- `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`.
|
||||
- `SWWAF_ATTACK_BAN_DURATION` (default `7d`): the ban for a first clear sign of
|
||||
attack. A duration; it cannot be `off`.
|
||||
`https` URL with a host and an optional port, and nothing more.
|
||||
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens, an
|
||||
optional host and a port. The host stays empty.
|
||||
- `SWWAF_INSTANCE_NAME` (default: the container's host name): the `instance`
|
||||
name in every log line, alert and metric, such as `fsn1app1/gitea`. Valid
|
||||
UTF-8. No control character while `SWWAF_ALERT_NTFY_URL` is set. 1 to 48
|
||||
printable ASCII characters without a space while `SWWAF_LOG_REMOTE_URL` is set
|
||||
and `SWWAF_LOG_REMOTE_APP_NAME` is not.
|
||||
- `SWWAF_MODE` (default `enforce`): `enforce` or `observe`.
|
||||
- `SWWAF_TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`):
|
||||
the netblocks whose `X-Forwarded-For` is believed. Set but empty, it trusts
|
||||
nothing.
|
||||
- `SWWAF_IPV6_GROUP_PREFIX` (default `64`): the prefix length of an IPv6
|
||||
client's group, from 32 to 128.
|
||||
- `SWWAF_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a whole
|
||||
number above zero.
|
||||
- `SWWAF_MAX_BANS` (default `5000`): the most bans `smallwebwaf` made that are
|
||||
kept, whether past, active or permanent. A whole number above zero. Bans whose
|
||||
cause is `admin` are kept besides and never dropped (see
|
||||
[`bans.md`](bans.md)).
|
||||
- `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 IPv6 ban covers the client's group.
|
||||
kept, a whole number above zero.
|
||||
|
||||
## Where and how often the state files are written
|
||||
## Tokens
|
||||
|
||||
- `SWWAF_ADMIN_TOKEN` (default unset): the token for the ban endpoints and
|
||||
`/_smallwebwaf/clients/<ip>`. At least 32 characters.
|
||||
- `SWWAF_METRICS_TOKEN` (default unset): the token for `/_smallwebwaf/metrics`.
|
||||
At least 32 characters.
|
||||
|
||||
## State files
|
||||
|
||||
- `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
|
||||
stops the start.
|
||||
files, an absolute path that exists and can be written.
|
||||
- `SWWAF_STATE_WRITE_DELAY` (default `10s`): how long `bans.json` waits to be
|
||||
written after a ban is made, lifted or made permanent. Every change in between
|
||||
goes into the same write. A duration; it cannot be `off`.
|
||||
written after a ban is made, lifted or made permanent. A duration.
|
||||
- `SWWAF_STATE_COUNTER_INTERVAL` (default `15m`): how often every state file is
|
||||
written. A duration; it cannot be `off`.
|
||||
written. A duration.
|
||||
|
||||
## Logging
|
||||
|
||||
- `SWWAF_LOG_LEVEL` (default `info`): the least severe of `smallwebwaf`'s own
|
||||
messages that are written: `debug`, `info`, `warn` or `error`.
|
||||
- `SWWAF_LOG_REQUEST_HEADERS` (default
|
||||
`accept,accept-language,accept-encoding,content-type,origin,range`): the
|
||||
request headers to record in the log line, in either case. `Authorization`,
|
||||
`Cookie` and `Set-Cookie` are never logged, even if listed. `Host` and
|
||||
`Transfer-Encoding` are refused: Go's HTTP server takes them out of the
|
||||
request. The host is the field `host`.
|
||||
- `SWWAF_LOG_LEVEL` (default `info`): the least severe of `smallwebwaf`'s own
|
||||
messages that are written: `debug`, `info`, `warn` or `error`. No message is
|
||||
at `debug`. It holds back no request log line. `warn` and `error` hold back
|
||||
the line of settings at start.
|
||||
- `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
|
||||
`syslog+udp://`, `syslog+tcp://` and `syslog+tls://`, each with a host and a
|
||||
port and nothing more.
|
||||
request headers recorded in the log line, in either case. `Host` and
|
||||
`Transfer-Encoding` are refused; the host is the field `host`.
|
||||
- `SWWAF_LOG_REMOTE_URL` (default unset): a syslog server that also gets every
|
||||
line written to stdout: `syslog+udp://`, `syslog+tcp://` or `syslog+tls://`,
|
||||
each with a host and a port and nothing more.
|
||||
- `SWWAF_LOG_REMOTE_TLS_CA_FILE` (default unset): a file of PEM certificates
|
||||
that a `syslog+tls` server's certificate must chain to, instead of the host's
|
||||
own. A file that cannot be read, or holds no certificate, stops the start.
|
||||
own. A file that cannot be read, or holds no certificate, is refused.
|
||||
- `SWWAF_LOG_REMOTE_BUFFER` (default `10000`): the most lines held while they
|
||||
wait to be sent, a whole number above zero.
|
||||
- `SWWAF_LOG_REMOTE_FACILITY` (default `local0`): the syslog facility: `kern`,
|
||||
@@ -370,53 +83,193 @@ does with the answer.
|
||||
- `SWWAF_LOG_REMOTE_APP_NAME` (default: `SWWAF_INSTANCE_NAME`): the app name in
|
||||
each syslog record, 1 to 48 printable ASCII characters without a space.
|
||||
|
||||
## Tokens and metrics
|
||||
## Metrics
|
||||
|
||||
- `SWWAF_ADMIN_TOKEN` (default unset): the token for the ban endpoints and
|
||||
`/_smallwebwaf/clients/<ip>`. A long random value of at least 32 characters.
|
||||
Unset, those endpoints are off. Give it as a file to keep it from the app (see
|
||||
"Settings given as files" below).
|
||||
- `SWWAF_METRICS_TOKEN` (default unset): the token a scraper sends for
|
||||
`/_smallwebwaf/metrics`, at least 32 characters. Unset, the metrics are off.
|
||||
It is separate from the admin token, so a scraper cannot manage bans.
|
||||
- `SWWAF_METRICS_TOP_N` (default `50`): how many AS numbers and how many
|
||||
countries get series of their own in the metrics. The rest count as `other`. A
|
||||
whole number above zero.
|
||||
countries get series of their own. A whole number above zero.
|
||||
|
||||
## Alert destinations
|
||||
## Static lists
|
||||
|
||||
- `SWWAF_ALLOW_NETS` (default empty): netblocks whose clients are refused only
|
||||
for a size or time limit or a missing or wrong token, such as your monitoring.
|
||||
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
||||
exempt from rate limiting and the byte limits.
|
||||
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
||||
|
||||
## Request rate limits, per client
|
||||
|
||||
- `SWWAF_RATE_LIMIT_PER_MINUTE` (default `1000`), `SWWAF_RATE_LIMIT_PER_HOUR`
|
||||
(default `10000`) and `SWWAF_RATE_LIMIT_PER_DAY` (default `50000`): the most
|
||||
requests a client may make in each window. A count or `off`.
|
||||
- `SWWAF_RATE_LIMIT_EXEMPT_PATHS` (default empty): path prefixes exempt from
|
||||
rate limiting and the byte limits, such as `/assets/`.
|
||||
|
||||
## Byte limits, per client
|
||||
|
||||
- `SWWAF_BYTES_LIMIT_PER_MINUTE` (default `10G`), `SWWAF_BYTES_LIMIT_PER_HOUR`
|
||||
(default `20G`) and `SWWAF_BYTES_LIMIT_PER_DAY` (default `50G`): the most
|
||||
bytes counted for a client in each window. A size or `off`.
|
||||
- `SWWAF_BYTES_COUNT` (default `both`): which body bytes the byte limits count:
|
||||
`response`, `request` or `both`.
|
||||
|
||||
## Size and time limits
|
||||
|
||||
- `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
|
||||
body. A duration or `off`.
|
||||
- `SWWAF_CLIENT_REQUEST_HEADER_MAX_BYTES` (default `32K`): the largest request
|
||||
line and headers. A size over `4K`; never `off`.
|
||||
- `SWWAF_CLIENT_IDLE_TIMEOUT` (default `120s`): how long a kept-open connection
|
||||
may wait for its next request before it is closed. A duration or `off`.
|
||||
- `SWWAF_CLIENT_RESPONSE_TIMEOUT` (default `30m`): how long the answer may take
|
||||
to reach the client, from the end of the request to the last byte. A duration
|
||||
or `off`.
|
||||
- `SWWAF_UPSTREAM_REQUEST_TIMEOUT` (default `60s`): how long connecting to the
|
||||
app and sending it the whole request may take. A duration or `off`.
|
||||
- `SWWAF_UPSTREAM_RESPONSE_TIMEOUT` (default `30m`): how long the app may take
|
||||
to send its whole answer. A duration or `off`.
|
||||
- `SWWAF_REQUEST_MAX_BYTES` (default `100M`): the largest request body. A size
|
||||
or `off`.
|
||||
- `SWWAF_RESPONSE_MAX_BYTES` (default `5G`): the largest response body. A size
|
||||
or `off`.
|
||||
|
||||
## Lookup of AS number and country
|
||||
|
||||
- `SWWAF_LOOKUP_SOURCE` (default `geojs`): `geojs`, `file` or `off`.
|
||||
- `SWWAF_LOOKUP_DB_PATH` (default empty): the IPinfo Lite database file, in its
|
||||
`.mmdb` form, for `SWWAF_LOOKUP_SOURCE=file`.
|
||||
- `SWWAF_LOOKUP_TIMEOUT` (default `1s`): how long a request waits for its
|
||||
client's first answer from GeoJS, and how long a request to GeoJS may take. A
|
||||
duration; never `off`.
|
||||
- `SWWAF_ADD_LOOKUP_HEADERS` (default `false`): `true` passes the app the
|
||||
client's AS number in `X-Client-ASN` and its country in `X-Client-Country`.
|
||||
|
||||
## Country lists
|
||||
|
||||
- `SWWAF_DENIED_COUNTRIES` (default empty): countries whose clients are refused,
|
||||
such as `cn,ru,kp`.
|
||||
- `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
|
||||
countries whose clients get through, such as `us,de`. A country on both lists
|
||||
is refused.
|
||||
|
||||
## Biased thresholds
|
||||
|
||||
- `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
|
||||
listed twice is refused.
|
||||
- `SWWAF_COUNTRY_LIMIT_PERCENT` (default empty): the same by country, such as
|
||||
`cn:25,ru:50`.
|
||||
- `SWWAF_ASN_BYTES_PERCENT` and `SWWAF_COUNTRY_BYTES_PERCENT` (default empty):
|
||||
the same, for the byte limits alone.
|
||||
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage for a client the
|
||||
lookup cannot place.
|
||||
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
||||
text file of `AS<number>:<percent>` lines, one to a line. A URL that is also
|
||||
in `SWWAF_BLOCKLIST_URLS` is refused.
|
||||
|
||||
## Attack detection
|
||||
|
||||
- `SWWAF_RULES_DIR` (default `/etc/smallwebwaf/rules.d`): the directory of the
|
||||
rule files.
|
||||
- `SWWAF_RULES_ENABLED` (default `true`): `false` reads no rule file.
|
||||
- `SWWAF_TRAP_PATHS` (default empty): paths only scanners ask for, such as
|
||||
`/wp-login.php,/xmlrpc.php`. Each starts with `/` and holds no `?`.
|
||||
- `SWWAF_WAF_MODE` (default `block`): `off`, `detect` or `block`.
|
||||
- `SWWAF_WAF_PARANOIA_LEVEL` (default `1`): the Core Rule Set's paranoia level,
|
||||
from 1 to 4.
|
||||
- `SWWAF_WAF_ANOMALY_THRESHOLD` (default `5`): the score at which a request is a
|
||||
match. A whole number above zero, or `off`.
|
||||
- `SWWAF_WAF_DISABLED_RULES` (default
|
||||
`920340,920420,920440,920640,930130,930140`): the ids of Core Rule Set rules
|
||||
to switch off. Each a whole number above zero. An id from 900000 to 900999 is
|
||||
refused: those are the rules that set the Core Rule Set up, and
|
||||
`smallwebwaf`'s own.
|
||||
- `SWWAF_WAF_EXEMPT_PATHS` (default empty): path prefixes the Core Rule Set does
|
||||
not inspect.
|
||||
- `SWWAF_WAF_BODY_LIMIT` (default `off`): `off`, or how much of a request body
|
||||
the Core Rule Set reads, a size of at most `1G`.
|
||||
- `SWWAF_ERROR_BURST_THRESHOLD` (default `30`): the most refusals of one client
|
||||
in a minute for a rule match, a trap path, a Core Rule Set match or a failed
|
||||
token check. A count or `off`.
|
||||
|
||||
## Ban lengths, response and scope
|
||||
|
||||
- `SWWAF_BAN_RESPONSE` (default `403`): `403`, `429` or `close`.
|
||||
- `SWWAF_LIMIT_BAN_DURATION` (default `1h`): the ban for a first broken limit. A
|
||||
duration.
|
||||
- `SWWAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`): the time after a ban ends
|
||||
within which breaking a limit again lengthens the next ban. A duration.
|
||||
- `SWWAF_MAX_BAN_DURATION` (default `7d`): the longest ban for a broken limit;
|
||||
one that would be longer is permanent. A duration.
|
||||
- `SWWAF_ATTACK_BAN_DURATION` (default `7d`): the ban for a first clear sign of
|
||||
attack. A duration.
|
||||
- `SWWAF_BAN_SCOPE_V4_PREFIX` (default `32`): how much a ban on an IPv4 client
|
||||
covers: the prefix length of its netblock, from 0 to 32.
|
||||
|
||||
## Reputation
|
||||
|
||||
- `SWWAF_BLOCKLIST_URLS` (default empty): blocklists to fetch, such as
|
||||
`https://www.spamhaus.org/drop/drop.txt`. Each an `http` or `https` URL
|
||||
without a user or a fragment. A URL listed twice is refused.
|
||||
- `SWWAF_BLOCKLIST_REFRESH` (default `24h`): how long a list waits after its
|
||||
last try before it is fetched again. A duration of `1h` or more.
|
||||
- `SWWAF_BLOCKLIST_ACTION` (default `deny`): `deny`, `limit:<percent>` or `log`.
|
||||
- `SWWAF_DNSBL_ZONES` (default empty): the DNS blocklists to ask, such as
|
||||
`dnsbl.dronebl.org`. Each a DNS name of at most 189 characters. A Spamhaus
|
||||
zone carries its key, as in `<key>.xbl.dq.spamhaus.net`. A zone listed twice
|
||||
is refused, whatever its case or key.
|
||||
- `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
|
||||
port, 53 when none is given. Unset, the host's resolver is used.
|
||||
- `SWWAF_ABUSEIPDB_KEY` (default unset): the key of your AbuseIPDB account. A
|
||||
control character in it is refused.
|
||||
- `SWWAF_ABUSEIPDB_MIN_SCORE` (default `75`): the least abuse confidence score,
|
||||
from 0 to 100, that is a hit.
|
||||
- `SWWAF_ABUSEIPDB_DAILY_BUDGET` (default `900`): the most checks made in a day,
|
||||
a whole number above zero. AbuseIPDB's free accounts may make 1,000.
|
||||
- `SWWAF_CROWDSEC_LAPI_URL` (default unset): the local API of a CrowdSec engine
|
||||
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 it. Set
|
||||
without the key, it is refused. So is a decision list URL that is also in
|
||||
`SWWAF_BLOCKLIST_URLS`, or that is `SWWAF_ASN_LIMIT_PERCENT_URL`.
|
||||
- `SWWAF_CROWDSEC_LAPI_KEY` (default unset): the key the engine gave for
|
||||
`smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it. Set without the
|
||||
URL, it is refused.
|
||||
- `SWWAF_REPUTATION_ACTION` (default `limit:25`): `deny`, `limit:<percent>` or
|
||||
`log`, for DNS blocklist and AbuseIPDB hits.
|
||||
- `SWWAF_REPUTATION_CACHE_TTL` (default `24h`): how long a zone's verdict or
|
||||
AbuseIPDB's score is used. A duration.
|
||||
- `SWWAF_REPUTATION_TIMEOUT` (default `2s`): how long a query to a zone or a
|
||||
check with AbuseIPDB may take. A duration.
|
||||
|
||||
## Alerting
|
||||
|
||||
- `SWWAF_ALERT_WEBHOOK_URL` (default unset): the webhook that receives each
|
||||
alert as a JSON object. An `http` or `https` URL without a user or a fragment.
|
||||
The settings logged at start show its path and query as `********`, since many
|
||||
webhooks carry their secret there.
|
||||
- `SWWAF_ALERT_WEBHOOK_HEADERS` (default empty): headers sent with each post,
|
||||
such as `Authorization:Bearer 0123456789abcdef`. A list of name, `:` and
|
||||
value. A value cannot hold a comma.
|
||||
- `SWWAF_ALERT_SLACK_WEBHOOK_URL` (default unset): the Slack incoming webhook
|
||||
that receives each alert as a message. It is checked and masked as the webhook
|
||||
URL is.
|
||||
that receives each alert as a message, checked as the webhook URL is.
|
||||
- `SWWAF_ALERT_NTFY_URL` (default unset): the ntfy topic that receives each
|
||||
alert, as the topic's full URL, such as `https://ntfy.sh/my-alerts`. It is
|
||||
checked and masked as the webhook URL is.
|
||||
- `SWWAF_ALERT_NTFY_TOKEN` (default unset): an ntfy access token, sent as
|
||||
`Authorization: Bearer <token>`. A control character in it is refused.
|
||||
alert, as the topic's full URL, such as `https://ntfy.sh/my-alerts`, checked
|
||||
as the webhook URL is.
|
||||
- `SWWAF_ALERT_NTFY_TOKEN` (default unset): an ntfy access token. A control
|
||||
character in it is refused.
|
||||
- `SWWAF_ALERT_EVENTS` (default
|
||||
`ban,permanent_ban,waf_block,anomaly,reputation_hit,source_failure,file_error`):
|
||||
the events alerts are sent for. Any other name is refused.
|
||||
the event types alerts are sent for, from those in [`alerts.md`](alerts.md)
|
||||
but `summary`. Any other name is refused.
|
||||
- `SWWAF_ALERT_COOLDOWN` (default `15m`): how long a repeat of an alert is held
|
||||
back. A duration or `off`.
|
||||
- `SWWAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour.
|
||||
The rest are rolled into one summary. A whole number above zero, or `off`.
|
||||
|
||||
See [`alerts.md`](alerts.md) for the events and what each destination gets.
|
||||
- `SWWAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour. A
|
||||
whole number above zero, or `off`.
|
||||
|
||||
## Anomaly 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.
|
||||
Requests are counts, bytes are sizes. Each is `off` by default. What is unusual
|
||||
depends on each service's normal traffic, which the metrics show. The four
|
||||
thresholds per client are:
|
||||
Each scope has four thresholds: the most requests and the most bytes counted in
|
||||
a minute and in an hour. Requests are counts, bytes are sizes. Each is `off` by
|
||||
default. The four thresholds per client are:
|
||||
|
||||
- `SWWAF_ANOMALY_CLIENT_REQUESTS_PER_MINUTE`;
|
||||
- `SWWAF_ANOMALY_CLIENT_REQUESTS_PER_HOUR`;
|
||||
@@ -432,30 +285,19 @@ as `REQUESTS_PER_MINUTE`:
|
||||
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 a lookup source.
|
||||
- `SWWAF_ANOMALY_TOTAL_...`: the whole service.
|
||||
- `SWWAF_WATCH_...`: each netblock in `SWWAF_WATCH_NETS`, as a whole.
|
||||
`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
|
||||
name, or with a bad netblock, is refused. So is a name used twice.
|
||||
|
||||
Every request but the health check is counted, whatever was done with it,
|
||||
clients in `SWWAF_ALLOW_NETS` included. At most 20,000 counters are kept.
|
||||
`alerts.json` keeps them across a restart.
|
||||
|
||||
## Settings given as files
|
||||
|
||||
Any setting may be given as a file holding its value. Add `_FILE` to the
|
||||
setting's name: `SWWAF_METRICS_TOKEN_FILE` names the file that holds
|
||||
`SWWAF_METRICS_TOKEN`. The file's contents are the value, less one newline at
|
||||
the end. They are checked as the value itself would be. Setting both forms stops
|
||||
the start, and so does naming a file that cannot be read.
|
||||
`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`
|
||||
user alone can read the file. Make it on the host, owned by uid 65532 with mode
|
||||
`0400`:
|
||||
Add `_FILE` to a setting's name to read its value from a file:
|
||||
`SWWAF_METRICS_TOKEN_FILE` names the file that holds `SWWAF_METRICS_TOKEN`. To
|
||||
keep a token from the app, make the file on the host, owned by uid 65532 with
|
||||
mode `0400`:
|
||||
|
||||
```sh
|
||||
mkdir -p /srv/app/tokens
|
||||
@@ -477,7 +319,8 @@ docker run \
|
||||
|
||||
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;
|
||||
- at most 100,000 DNS blocklist verdicts and 100,000 AbuseIPDB scores are kept;
|
||||
- at most 1,000 DNS blocklist queries are under way at once;
|
||||
- the CrowdSec decision list is fetched again a minute after the last fetch.
|
||||
- at most 20,000 anomaly counters are kept;
|
||||
- each alert destination's queue holds at most 1,000 alerts.
|
||||
|
||||
Reference in New Issue
Block a user