SPEC: requirements and reasons only, reference material in docs/
check / check (push) Waiting to run
check / check (push) Waiting to run
docs/settings.md is now the one settings reference; the spec keeps the conventions, the off list and each group's requirements and reasons, and links to it for the per-setting entries. The endpoint list, the ban notes, the state file contents, the rule file format and the alert object likewise live in docs/ and are linked from the spec. The package list from the README's Design section joins "Architecture". The nine docs files are byte-identical to those on the README branch. Package comments that named "Alert webhook schema", "Request log" and "Metrics endpoint" for their field lists now name the docs files. Model: fable-5-1
This commit is contained in:
@@ -0,0 +1,483 @@
|
||||
# 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.
|
||||
|
||||
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:
|
||||
|
||||
- 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`.
|
||||
- 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.
|
||||
- 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`
|
||||
are the same. `xk` stands for Kosovo. A code that is not a country is refused,
|
||||
such as `nk` (North Korea is `kp`) or the withdrawn `su`.
|
||||
- An AS number is `AS` and the number, in either case, such as `AS64496`.
|
||||
- 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
|
||||
|
||||
- `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`.
|
||||
- `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.
|
||||
|
||||
## Where and how often the state files are written
|
||||
|
||||
- `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.
|
||||
- `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`.
|
||||
- `SWWAF_STATE_COUNTER_INTERVAL` (default `15m`): how often every state file is
|
||||
written. A duration; it cannot be `off`.
|
||||
|
||||
## Logging
|
||||
|
||||
- `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.
|
||||
- `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.
|
||||
- `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`,
|
||||
`user`, `mail`, `daemon`, `auth`, `syslog`, `lpr`, `news`, `uucp`, `cron`,
|
||||
`authpriv`, `ftp`, or `local0` to `local7`.
|
||||
- `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
|
||||
|
||||
- `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.
|
||||
|
||||
## Alert destinations
|
||||
|
||||
- `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.
|
||||
- `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.
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
## 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:
|
||||
|
||||
- `SWWAF_ANOMALY_CLIENT_REQUESTS_PER_MINUTE`;
|
||||
- `SWWAF_ANOMALY_CLIENT_REQUESTS_PER_HOUR`;
|
||||
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_MINUTE`;
|
||||
- `SWWAF_ANOMALY_CLIENT_BYTES_PER_HOUR`.
|
||||
|
||||
The other scopes' names have another prefix before the same four endings, such
|
||||
as `REQUESTS_PER_MINUTE`:
|
||||
|
||||
- `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_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`:
|
||||
|
||||
```sh
|
||||
mkdir -p /srv/app/tokens
|
||||
openssl rand -hex 32 > /srv/app/tokens/metrics
|
||||
chown 65532:65532 /srv/app/tokens/metrics
|
||||
chmod 0400 /srv/app/tokens/metrics
|
||||
```
|
||||
|
||||
Mount its directory into the container read-only, and name the file:
|
||||
|
||||
```sh
|
||||
docker run \
|
||||
-v /srv/app/tokens:/etc/smallwebwaf/tokens:ro \
|
||||
-e SWWAF_METRICS_TOKEN_FILE=/etc/smallwebwaf/tokens/metrics \
|
||||
...
|
||||
```
|
||||
|
||||
## Fixed limits
|
||||
|
||||
A few limits are fixed rather than settings:
|
||||
|
||||
- at most 100,000 GeoJS answers are kept, for 7 days each;
|
||||
- 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.
|
||||
Reference in New Issue
Block a user