Seventh review: false statements fixed, README shortened to link the spec
check / check (push) Canceled after 0s

- Engine error handling dropped as not built; unknown country and AS
  number each on its own; observe mode makes no ban of its own.
- README no longer restates the request steps, the refusal answers or
  the design; its step list uses the spec's numbers.
- Repeats, misreadable sentences, late verbs and long sentences reworded
  as the review asks.

Model: fable-5-1
This commit is contained in:
2026-10-26 00:37:38 +00:00
parent a62909dbf4
commit 8282404453
3 changed files with 114 additions and 184 deletions
+55 -128
View File
@@ -8,8 +8,7 @@ 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, app's answers back.
apart from a few headers (see "Design" 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. [`SPEC.md`](SPEC.md) says how `smallwebwaf` behaves and why. The open internet. [`SPEC.md`](SPEC.md) says how `smallwebwaf` behaves and why. The
@@ -30,8 +29,8 @@ reference documents under [`docs/`](docs/) are listed under "Documents" below.
of your own, or an entry in `SWWAF_TRAP_PATHS`, bans those the same way. of your own, or an entry in `SWWAF_TRAP_PATHS`, bans those the same way.
- 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, runs those rules (step 11 under "What common web attacks. Coraza, a Go engine, runs those rules (see "Attack
happens to a request" below). A request the rules take for an attack is detection" in [`SPEC.md`](SPEC.md)). A request the rules take for an attack is
refused. 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;
@@ -142,89 +141,32 @@ while working on it, see "Entrypoints" below.
## What happens to a request ## What happens to a request
Two terms recur below. A clear sign of attack is a request that matches a rule `smallwebwaf` handles each request in fifteen steps, in this order. Once a step
whose action is `ban`, or asks for a trap path (steps 9 and 10). An offence is a refuses a request, the steps after it are skipped, apart from step 15, and
request that counts against its client in its history. It is one of: nothing of the request reaches the app. "What happens to a request" in
[`SPEC.md`](SPEC.md) gives each step in full, under the same numbers.
- a clear sign of attack; 1. The health check, `GET /_smallwebwaf/healthz`, is answered at once.
- a broken limit; 2. The client is identified.
- a refusal by a rule, by the Core Rule Set or for a missing or wrong token. 3. The static lists, `SWWAF_ALLOW_NETS` and `SWWAF_DENY_NETS`.
4. The bans.
5. The lookup of the client's AS number and country.
6. The country lists.
7. The reputation sources.
8. The client's limit percentage.
9. The rate limits.
10. The trap paths.
11. The rule files.
12. The Core Rule Set.
13. `smallwebwaf`'s own endpoints, under `/_smallwebwaf/`.
14. The request is passed to the app.
15. After the answer: the byte limits, the error burst and the client's history.
`smallwebwaf` handles each request in this order. Once a step refuses a request, A refused visitor is answered with `SWWAF_BAN_RESPONSE`, `403` by default (see
the steps after it are skipped, apart from step 13. Nothing of a refused request "Ban lengths, response and scope" in [`SPEC.md`](SPEC.md)). A `block` rule or a
reaches the app. "What happens to a request" in [`SPEC.md`](SPEC.md) gives each Core Rule Set match answers `403`. A missing or wrong token gets `401`. A
step in full. request over a size or time limit gets the status for that limit, such as `413`
for a body too large (see "Size and time limits" in [`SPEC.md`](SPEC.md)).
1. `GET /_smallwebwaf/healthz` is answered `200` with `ok` at once, before any
check and without asking the app.
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` lists
the netblocks traefik comes from, by default the private address ranges. For
a TCP peer inside them, `X-Forwarded-For` is read from the right, and the
first address outside those netblocks is the client. A peer outside them is
the client itself. An IPv6 client is its group: the whole netblock around its
address, a /64 by default; `SWWAF_IPV6_GROUP_PREFIX` sets its length.
3. A client in `SWWAF_ALLOW_NETS` skips steps 4 to 11, the byte limits and the
error burst, but still needs a token at step 12. A client in
`SWWAF_DENY_NETS` is refused.
4. A client under a ban is refused. If the ban was made for a clear sign of
attack, this request makes it permanent.
5. The client's AS number and country are looked up, unless
`SWWAF_LOOKUP_SOURCE` is `off`. An AS number names the network an address
belongs to, such as `AS64496`.
6. The country lists are checked. A client from a country in
`SWWAF_DENIED_COUNTRIES` is refused. When
`SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` is set, a client from any other country
is refused, and so is a client without a country. Such a refusal is not an
offence.
7. The reputation sources are checked: the blocklists, the CrowdSec decision
list, the DNS blocklists and AbuseIPDB. A client on the CrowdSec decision
list is banned until the decision ends. Any other source refuses the client,
lowers its limits or only notes it, as `SWWAF_BLOCKLIST_ACTION` and
`SWWAF_REPUTATION_ACTION` say. No request waits for a query.
8. Rate limiting counts the request, unless the client or the path is exempt. A
request that takes the client over a limit is refused and bans the client.
9. A request for a path in `SWWAF_TRAP_PATHS` is refused and bans the client, as
a clear sign of attack.
10. The rule files are checked. A `log` rule notes the match. A `block` rule
refuses the request. A `ban` rule refuses it and bans the client, as a clear
sign of attack.
11. The Core Rule Set inspects the request's method, URL and headers, and, once
`SWWAF_WAF_BODY_LIMIT` is set, form, JSON and XML bodies. A request it takes
for an attack is refused while `SWWAF_WAF_MODE` is `block`, the default, and
let through while it is `detect`. It bans no one by itself.
12. A request under `/_smallwebwaf/` is answered here by `smallwebwaf`, after
its token is checked. Any other request is passed to the app. The app's
answer is streamed back within the size and time limits.
13. Once the answer has ended, a request passed to the app has its bytes counted
for the byte limits. If a rule, a trap path, the Core Rule Set or a token
check refused the request, the refusal is counted for the error burst. The
client's history and the anomaly counters are updated. More than
`SWWAF_ERROR_BURST_THRESHOLD` refusals of one client in a minute ban it.
In `observe` mode (`SWWAF_MODE=observe`), `smallwebwaf` makes no ban of its own.
It refuses a request only for a size or time limit, or for a missing or wrong
token. A request it would otherwise have refused is passed to the app, and its
log line says what `enforce` mode would have done. Observe mode is for trying a
configuration before enforcing it.
What a refused visitor sees:
- `SWWAF_BAN_RESPONSE`, by default `403` with the plain text `Forbidden`. It can
be `429` instead, or `close`, which closes the connection, so that traefik
answers `502`. It answers:
- a ban or 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`.
- `403` for a `block` rule and the Core Rule Set.
- For a size or time limit:
- `431` for headers over their limit;
- `413` for a body over its limit;
- `408` for a client too slow to send its body;
- `502` or `504` when the app passes a limit or cannot be reached.
- `401` from an endpoint of `smallwebwaf`'s own, for a request without its
token.
## Settings you are likely to touch ## Settings you are likely to touch
@@ -233,8 +175,11 @@ Each setting is an environment variable of the app's container, such as
[`docs/settings.md`](docs/settings.md) lists every setting, its default and the [`docs/settings.md`](docs/settings.md) lists every setting, its default and the
values it takes. The ones most deployments touch: values it takes. The ones most deployments touch:
- `SWWAF_MODE=observe`: log what would have been refused, refuse almost nothing, - `SWWAF_MODE=observe`: `smallwebwaf` makes no ban of its own, and refuses a
ban no one. For trying a configuration. request only for a size or time limit or a missing or wrong token. A request
it would have refused is passed to the app, and its log line says what
`enforce` mode would have done. For trying a configuration before enforcing
it.
- `SWWAF_ALLOW_NETS`: netblocks you trust, such as your monitoring. Their - `SWWAF_ALLOW_NETS`: netblocks you trust, such as your monitoring. Their
clients are refused only for a size or time limit or a missing or wrong token. clients are refused only for a size or time limit or a missing or wrong token.
- `SWWAF_RATE_LIMIT_EXEMPT_NETS`: netblocks exempt from rate limiting and the - `SWWAF_RATE_LIMIT_EXEMPT_NETS`: netblocks exempt from rate limiting and the
@@ -376,50 +321,32 @@ nixpkgs fixed at one release. An app's `Dockerfile` installs a package with
[`docs/image.md`](docs/image.md) lists what the image holds and how each part is [`docs/image.md`](docs/image.md) lists what the image holds and how each part is
pinned. pinned.
- The container's first process is `runsvinit`, which starts runit, a service - runit, a service supervisor, runs `smallwebwaf` and the app side by side, each
supervisor. runit runs the `run` script of each directory under as its own user, and starts either again when it exits. The app cannot touch
`/etc/service`, and runs it again a second after its program exits. the state files or the process of `smallwebwaf`.
`smallwebwaf` runs as the `smallwebwaf` user (uid 65532) and the app as a user - `smallwebwaf` listens on port 8080 and passes requests to the app on
of its own. So the app cannot touch the state files or the process of `127.0.0.1:8081`. The app listens on `127.0.0.1` only, so nothing reaches it
`smallwebwaf`. except through `smallwebwaf`. `smallwebwaf`'s own endpoints are under
- `smallwebwaf` listens on port 8080, on every address. traefik sends the app's `/_smallwebwaf/`, on the same port.
requests there. `smallwebwaf` passes them to the app on `127.0.0.1:8081`. The - The app sees the headers traefik sent, with traefik's address added to
app listens on `127.0.0.1` only, so nothing reaches it except through `X-Forwarded-For`, from a TCP peer on `127.0.0.1`. So the app must trust
`smallwebwaf`. The health check, the metrics and the ban management are on the `127.0.0.1` and `::1` for forwarded headers, besides the private address
same port, under `/_smallwebwaf/`. A request for that prefix never reaches the ranges.
app. - Both processes start with the container's environment variables, so every
- The app sees what it would see from traefik directly: the same `Host`, the setting of `smallwebwaf` starts with `SWWAF_`, and the app can read a token
same `X-Forwarded-Proto`, and `X-Forwarded-For` with traefik's address added given as one. A token given as a file that only the `smallwebwaf` user can
at the end. The app's TCP peer is `smallwebwaf` on `127.0.0.1`. So the app read is out of its reach (see "Settings given as files" in
must trust `127.0.0.1` and `::1` for forwarded headers, besides the private
address ranges. An app that keeps its usual trusted proxies sees every visitor
as `127.0.0.1`. Each request also carries its id in `X-Request-ID`. 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
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
the `smallwebwaf` user can read is not (see "Settings given as files" in
[`docs/settings.md`](docs/settings.md)). [`docs/settings.md`](docs/settings.md)).
- The state files live in `/var/lib/smallwebwaf`. Without a volume there, they - The state files live in `/var/lib/smallwebwaf`. A volume mounted there keeps
survive a restart of the container and are lost when a deploy replaces it. The them across a deploy.
`run` script gives that directory and every file in it to the `smallwebwaf` - `docker stop` stops both processes at once. `smallwebwaf` gives the requests
user at each start, so a host directory mounted there needs no change of in progress five seconds to finish, so the app must keep answering for five
owner.
- The image's health check passes while `smallwebwaf` answers
`GET /_smallwebwaf/healthz` and the app accepts connections. traefik sends a
container no requests until it is healthy.
- When `smallwebwaf` or the app exits, runit starts it again a second later. The
other process keeps running. While either is down, the client gets `502`. A
`smallwebwaf` that stops at start, as on an invalid setting, is started again
every second until the cause is fixed, and logs its message each time.
- `docker stop` has runit send `SIGTERM` to both processes at once.
`smallwebwaf` gives the requests in progress five seconds to finish and writes
its state files. To let them finish, the app must keep answering for five
seconds after `SIGTERM`. seconds after `SIGTERM`.
"Deployment" in [`SPEC.md`](SPEC.md) has the rest: moving `smallwebwaf` to "Deployment" in [`SPEC.md`](SPEC.md) has the rest: the health check, what
another port, the health check's timing, and the forwarded headers. happens when a process stops, moving `smallwebwaf` to another port, and the
"Architecture" in [`SPEC.md`](SPEC.md) lists the packages and what each does. forwarded headers. "Architecture" in [`SPEC.md`](SPEC.md) lists the packages and
what each does.
## TODO ## TODO
+58 -54
View File
@@ -164,8 +164,8 @@ its rules.
thresholds are evaluated. thresholds are evaluated.
In `SWWAF_MODE=observe`, every refusal above is logged as what would have In `SWWAF_MODE=observe`, every refusal above is logged as what would have
happened, nothing is refused, and no ban is made. The size and time limits and happened, nothing is refused, and `smallwebwaf` makes no ban of its own. The
the token check still apply. size and time limits and the token check still apply.
## Counting method ## Counting method
@@ -183,8 +183,8 @@ the token check still apply.
GeoJS answers, the reputation verdicts and scores, the DNS queries under way GeoJS answers, the reputation verdicts and scores, the DNS queries under way
and the anomaly counters have fixed bounds, listed under "Fixed limits" in and the anomaly counters have fixed bounds, listed under "Fixed limits" in
`docs/settings.md`. Past its bound, the GeoJS answer or anomaly counter used `docs/settings.md`. Past its bound, the GeoJS answer or anomaly counter used
longest ago goes first, and the verdict or score fetched longest ago. Past longest ago goes first, and the verdict or score fetched longest ago. Past the
that bound, a zone is asked at the client's next request. bound on DNS queries under way, a zone is asked at the client's next request.
## Bans ## Bans
@@ -215,9 +215,12 @@ to a ban, each by its own rule:
permanent this way. permanent this way.
- Once that ban has run out, the netblock is served like any other. Its next - Once that ban has run out, the netblock is served like any other. Its next
clear sign of attack bans it permanently at once. clear sign of attack bans it permanently at once.
- A broken limit: a request over a rate limit, a response that takes the - A broken limit, one of:
client's byte total past a byte limit, or more than - a request over a rate limit;
`SWWAF_ERROR_BURST_THRESHOLD` refusals within a minute. - a response that takes the client's byte total past a byte limit;
- more than `SWWAF_ERROR_BURST_THRESHOLD` refusals within a minute.
The bans for a broken limit:
- The first such ban lasts `SWWAF_LIMIT_BAN_DURATION` (default `1h`), with - The first such ban lasts `SWWAF_LIMIT_BAN_DURATION` (default `1h`), with
the cause `limit`. So does one that comes more than the cause `limit`. So does one that comes more than
`SWWAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`) after the last such ban `SWWAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`) after the last such ban
@@ -233,7 +236,7 @@ to a ban, each by its own rule:
A Core Rule Set match refuses only the request it matched, with `403` and A Core Rule Set match refuses only the request it matched, with `403` and
without a ban, because the Core Rule Set has false positives. A `block` rule without a ban, because the Core Rule Set has false positives. A `block` rule
does the same. Those refusals count toward the error burst, so a client that does the same. Those refusals count toward the error burst, so a client that
keeps setting them off is banned under the second rule. keeps setting them off is banned for a broken limit.
A client on the CrowdSec decision list is banned until the decision ends, with A client on the CrowdSec decision list is banned until the decision ends, with
the cause `crowdsec`. Only a client that sends a request is banned, so a long the cause `crowdsec`. Only a client that sends a request is banned, so a long
@@ -328,8 +331,8 @@ Conventions:
- `SWWAF_ADMIN_TOKEN` switches on the ban endpoints and - `SWWAF_ADMIN_TOKEN` switches on the ban endpoints and
`/_smallwebwaf/clients/<ip>`. The metrics are off until `SWWAF_METRICS_TOKEN` `/_smallwebwaf/clients/<ip>`. The metrics are off until `SWWAF_METRICS_TOKEN`
is set, since without a token they would be open to anyone on the internet. is set, since without a token they would be open to anyone on the internet.
The two are separate, so a scraper that holds the metrics token cannot manage The two are separate, so the metrics collector, which holds the metrics token,
bans. cannot manage bans.
- A token is a long random value, since the endpoints can be reached from the - A token is a long random value, since the endpoints can be reached from the
internet. One shorter than 32 characters stops the start. The app starts with internet. One shorter than 32 characters stops the start. The app starts with
the same environment variables as `smallwebwaf`, so it can read a token set as the same environment variables as `smallwebwaf`, so it can read a token set as
@@ -453,8 +456,8 @@ service that keeps its visitors' addresses on its own host.
Addresses are asked about together, up to 200 in one request Addresses are asked about together, up to 200 in one request
(`https://get.geojs.io/v1/ip/geo.json?ip=a,b,c`), one request at a time, which (`https://get.geojs.io/v1/ip/geo.json?ip=a,b,c`), one request at a time, which
keeps the number of requests to GeoJS low. Each answer's `country_code`, `asn` keeps the number of requests to GeoJS low. Each answer's `country_code`, `asn`
and `organization_name` are read. An answer without a `country_code`, or with and `organization_name` are read. An answer without a `country_code` leaves
the `asn` `64512`, counts as unknown. the country unknown, and the `asn` `64512` leaves the AS number unknown.
- Each answer is kept for seven days, in memory and in `lookups.json`. Answers - Each answer is kept for seven days, in memory and in `lookups.json`. Answers
are kept apart from the table of clients, so that an answer outlasts a client are kept apart from the table of clients, so that an answer outlasts a client
dropped from that table. The answer reaches the client's history and ban notes dropped from that table. The answer reaches the client's history and ban notes
@@ -528,12 +531,12 @@ service's abuse comes from.
fetched and kept like a blocklist (see "Reputation"). Where it and fetched and kept like a blocklist (see "Reputation"). Where it and
`SWWAF_ASN_LIMIT_PERCENT` both give a percentage, the lower applies. `SWWAF_ASN_LIMIT_PERCENT` both give a percentage, the lower applies.
- A client's limit percentage is the lowest of those that apply to it: its AS - A client's limit percentage is the lowest of those that apply to it: its AS
number's, its country's, or a reputation hit's `limit:<percent>`. A client the number's, its country's, or a reputation hit's `limit:<percent>`. A client
lookup cannot place gets `SWWAF_UNKNOWN_LIMIT_PERCENT` in place of an AS without a country gets `SWWAF_UNKNOWN_LIMIT_PERCENT` in place of a country's.
number's or a country's. The percentage is 100 if none applies. Clients given The percentage is 100 if none applies. Clients given a lower percentage are
a lower percentage are banned after fewer requests, first for a while and banned after fewer requests, first for a while and then, for repeated abuse,
then, for repeated abuse, permanently. The log line of each counted request, permanently. The log line of each counted request, and the notes of each ban,
and the notes of each ban, say which setting lowered the limit. say which setting lowered the limit.
- No budget is shared by a whole country or AS number. One abuser could use it - No budget is shared by a whole country or AS number. One abuser could use it
up and lock out everyone else there: a denial of service nobody chose, and the up and lock out everyone else there: a denial of service nobody chose, and the
one this tool exists to prevent. Refusing a whole country is left to the one this tool exists to prevent. Refusing a whole country is left to the
@@ -544,15 +547,14 @@ service's abuse comes from.
By default the Core Rule Set reads the method, the URL with its query string, By default the Core Rule Set reads the method, the URL with its query string,
and the headers of every request. That is where automated attacks show: and the headers of every request. That is where automated attacks show:
injection in query strings, path traversal, attacks carried in headers, injection in query strings, path traversal, attacks carried in headers,
scanners' user agents. It reads no request body. On a code forge, the example scanners' user agents. It reads no request body. In front of a code forge such
deployment, bodies carry what people write and publish, such as issue text and as gitea, bodies carry what people write and publish, such as issue text and
files saved in the web editor. The Core Rule Set takes the shell commands and files saved in the web editor. The Core Rule Set takes the shell commands and
code in them for attacks. The default gives up refusing an attack carried in a code in them for attacks. The default gives up refusing an attack carried in a
body. The rule files, the limits and the bans still apply to the client that body. The rule files, the limits and the bans still apply to the client that
sends it. sends it.
- The Core Rule Set skips a path under `SWWAF_WAF_EXEMPT_PATHS`. An engine error - The Core Rule Set skips a path under `SWWAF_WAF_EXEMPT_PATHS`.
on a request forwards the request, and is logged and counted.
- `SWWAF_WAF_PARANOIA_LEVEL` and `SWWAF_WAF_ANOMALY_THRESHOLD` are the Core Rule - `SWWAF_WAF_PARANOIA_LEVEL` and `SWWAF_WAF_ANOMALY_THRESHOLD` are the Core Rule
Set's own two tuning values, at its own defaults. Each level up runs more Set's own two tuning values, at its own defaults. Each level up runs more
rules, which find more attacks and take more ordinary requests for attacks. rules, which find more attacks and take more ordinary requests for attacks.
@@ -633,12 +635,12 @@ every app, and no setting undoes them:
- `ref`, `sha`, `branch` and `workflow`; - `ref`, `sha`, `branch` and `workflow`;
- `artifactName` and `redirect_to`. - `artifactName` and `redirect_to`.
gitea puts file paths, branch and workflow names, and the page to return to gitea puts into them file paths, branch and workflow names, and the page to
after signing in into them. It reads these values as names in a repository return to after signing in. It reads these values as names in a repository
or in its own records, or as a page of its own site, never as a command. Any or in its own records, or as a page of its own site, never as a command. Any
name on those lists can be an ordinary file or branch, such as `.gitignore` name on those lists can be an ordinary file or branch, such as `.gitignore`
or `docker-build`. In these parameters, anything only these three rules or `docker-build`. In these parameters, what only these three rules would
catch reaches the app, commands such as `|cat /etc/passwd` included. Path catch reaches the app, such as the command `|cat /etc/passwd`. Path
traversal, SQL and script injection, and PHP, Java and Node.js code are traversal, SQL and script injection, and PHP, Java and Node.js code are
still refused there. Every other parameter keeps all three rules. An app still refused there. Every other parameter keeps all three rules. An app
that uses one of these parameters as a file on the server, or passes it to a that uses one of these parameters as a file on the server, or passes it to a
@@ -647,8 +649,8 @@ every app, and no setting undoes them:
5. Two cookies are left out, and `Referer` keeps fewer rules: 5. Two cookies are left out, and `Referer` keeps fewer rules:
- The Core Rule Set reads the request without the `gitea_flash` and - The Core Rule Set reads the request without the `gitea_flash` and
`redirect_to` cookies. gitea puts a message naming what was just done into `redirect_to` cookies. gitea puts a message naming what was just done into
`gitea_flash`. It puts the page to return to after signing in into `gitea_flash`. It puts into `redirect_to` the page to return to after
`redirect_to`. The Core Rule Set takes file names in them, such as signing in. The Core Rule Set takes file names in them, such as
`package.json`, for attacks. A browser sends such a cookie with every `package.json`, for attacks. A browser sends such a cookie with every
request until gitea replaces it. gitea cannot replace it while those request until gitea replaces it. gitea cannot replace it while those
requests are refused, so one refusal would keep the browser out of the requests are refused, so one refusal would keep the browser out of the
@@ -772,7 +774,8 @@ too close together.
client gets a quarter of every request and byte limit rather than a refusal. client gets a quarter of every request and byte limit rather than a refusal.
- Verdicts and scores are kept for `SWWAF_REPUTATION_CACHE_TTL`, in memory and - Verdicts and scores are kept for `SWWAF_REPUTATION_CACHE_TTL`, in memory and
in `reputation.json`. A source that is down or over quota gives no verdict. in `reputation.json`. A source that is down or over quota gives no verdict.
The service continues, with one `source_failure` alert per cooldown. The service continues. Each failure raises a `source_failure` alert (see
"Alerting").
### Alerting ### Alerting
@@ -861,11 +864,11 @@ and the default file.
wrong for every app, with its path rules anchored at the site root. In front wrong for every app, with its path rules anchored at the site root. In front
of gitea, `/.env` is a probe, while of gitea, `/.env` is a probe, while
`/<owner>/<repo>/src/branch/main/.env.example` is a file in a repository that `/<owner>/<repo>/src/branch/main/.env.example` is a file in a repository that
any visitor may open. `ban` rules of your own follow the same rule. Anything any visitor may open. Anchor the paths of your own `ban` rules at the site
app-specific, such as a rule banning WordPress probes in front of gitea, root too. Anything app-specific, such as a rule banning WordPress probes in
belongs in a file the app's Dockerfile adds or the deployer mounts. A front of gitea, belongs in a file the app's Dockerfile adds or the deployer
directory mounted over `SWWAF_RULES_DIR` replaces the default file; single mounts. A directory mounted over `SWWAF_RULES_DIR` replaces the default file;
files mounted into it add to it. single files mounted into it add to it.
- Rules meant to be edited while `smallwebwaf` runs belong in a mounted - Rules meant to be edited while `smallwebwaf` runs belong in a mounted
directory, not a mounted file. Many editors save a file by replacing it, and a directory, not a mounted file. Many editors save a file by replacing it, and a
container keeps seeing the old one when the file alone is mounted. The lookup container keeps seeing the old one when the file alone is mounted. The lookup
@@ -874,11 +877,11 @@ and the default file.
## Persistent state ## Persistent state
All state lives in memory, and the files in `SWWAF_STATE_DIR` hold a copy of all All state lives in memory, and the files in `SWWAF_STATE_DIR` hold a copy of it,
of it, so an orderly stop loses nothing (the remote log is the exception; see so an orderly stop loses nothing. The files are meant for people as well: an
"Request log"). The files are meant for people as well: an admin can read or admin can read or edit them at any time, and the running `smallwebwaf` takes the
edit them at any time, and the running `smallwebwaf` takes the edit in. edit in. `docs/state-files.md` lists the files, what each holds, and their
`docs/state-files.md` lists the files, what each holds, and their sizes. sizes.
- Each file holds one kind of state, as JSON with: - Each file holds one kind of state, as JSON with:
- a top-level `version` number; - a top-level `version` number;
@@ -1019,14 +1022,14 @@ what the image holds and how each part is pinned.
digest. digest.
- nixpkgs comes from a release file on `releases.nixos.org`, named with its - nixpkgs comes from a release file on `releases.nixos.org`, named with its
hash, not from a GitHub archive, whose bytes can change. Root uses Nix hash, not from a GitHub archive, whose bytes can change. Root uses Nix
directly; no Nix daemon runs, and since `nix-bin` creates no build users, directly; no Nix daemon runs. `nix-bin` creates no build users, so
`build-users-group =` in `/etc/nix/nix.conf` lets root build without them. `/etc/nix/nix.conf` sets `build-users-group =`, which lets root build without
Root's Nix profile is last on the `PATH`, so that no package hides the image's them. Root's Nix profile is last on the `PATH`, so that no package hides the
own commands; busybox's `sv`, for one, looks for services elsewhere. An app image's own commands; busybox's `sv`, for one, looks for services elsewhere.
built on the image gets the same packages each time. Security fixes from An app built on the image gets the same packages each time. Security fixes
Ubuntu and nixpkgs come with a newer `smallwebwaf` image, which an app takes from Ubuntu and nixpkgs come with a newer `smallwebwaf` image, which an app
by changing the digest in its `FROM` line. The image moves to each new nixpkgs takes by changing the digest in its `FROM` line. The image moves to each new
release, every six months. nixpkgs release, every six months.
- runit supervises the two processes, with `runsvinit` as the entrypoint, as the - runit supervises the two processes, with `runsvinit` as the entrypoint, as the
owner's code style guide asks for service containers. No package carries owner's code style guide asks for service containers. No package carries
`runsvinit`, so the image builds it from its archived source, unchanged since `runsvinit`, so the image builds it from its archived source, unchanged since
@@ -1075,10 +1078,10 @@ It sets no `ENTRYPOINT` or `USER` of its own. The container must start
- an unwritable `SWWAF_STATE_DIR`; - an unwritable `SWWAF_STATE_DIR`;
- a `SWWAF_RULES_DIR` that does not exist or cannot be read; - a `SWWAF_RULES_DIR` that does not exist or cannot be read;
- a `SWWAF_LISTEN_ADDR` it cannot listen on. - a `SWWAF_LISTEN_ADDR` it cannot listen on.
- A `smallwebwaf` that stops at start is therefore started again every second, - A `smallwebwaf` that stops at start logs its message at each try until the
logging its message each time, until the cause is fixed; the health check cause is fixed; the health check fails meanwhile. Once running, a failing
fails meanwhile. Once running, a failing GeoJS, reputation source or alert GeoJS, reputation source or alert destination, or a broken edit, never takes
destination, or a broken edit, never takes the protected service down. the protected service down.
- `docker stop` sends `SIGTERM` to `runsvinit`, which has runit stop both - `docker stop` sends `SIGTERM` to `runsvinit`, which has runit stop both
services at once, each with `SIGTERM`, and exits once they have stopped. services at once, each with `SIGTERM`, and exits once they have stopped.
`smallwebwaf` stops taking requests, gives those in progress five seconds to `smallwebwaf` stops taking requests, gives those in progress five seconds to
@@ -1147,9 +1150,10 @@ all: `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32,::1/128`.
### upaas ### upaas
An app deployed by upaas takes this shape with no change to upaas. upaas builds An app deployed by upaas, which rebuilds and restarts an app's Docker container
the app's Dockerfile and runs the one container as it runs any app. The traefik when its repository changes, takes this shape with no change to upaas. upaas
labels route to port 8080, and any `SWWAF_` settings go with the app's builds the app's Dockerfile and runs the one container as it runs any app. The
traefik labels route to port 8080, and any `SWWAF_` settings go with the app's
environment variables. The volume for `/var/lib/smallwebwaf` and the directory environment variables. The volume for `/var/lib/smallwebwaf` and the directory
of any token file go beside the app's own volumes. gitea is built on the image of any token file go beside the app's own volumes. gitea is built on the image
like any other app; `docs/gitea.md` records what the defaults do in front of it, like any other app; `docs/gitea.md` records what the defaults do in front of it,
+1 -2
View File
@@ -196,8 +196,7 @@ that take `off`, the `_FILE` form, and what each group does and why.
- `SWWAF_WAF_BODY_LIMIT` (default `off`): `off`, or how much of a request body - `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`. the Core Rule Set reads, a size of at most `1G`.
- `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 for a rule match, a trap path, a Core Rule Set match or a failed in a minute (see "Attack detection" in `SPEC.md`). A count or `off`.
token check. A count or `off`.
## Ban lengths, response and scope ## Ban lengths, response and scope