Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dbe17e594 |
No files matched your search
@@ -30,9 +30,9 @@ 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 that runs those rules, inspects each
|
common web attacks. Coraza, a Go engine, runs those rules (step 11 under "What
|
||||||
request the earlier checks let through. A request the rules take for an attack
|
happens to a request" below). A request the rules take for an attack is
|
||||||
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;
|
||||||
- give the clients of chosen networks and countries a fraction of every
|
- give the clients of chosen networks and countries a fraction of every
|
||||||
@@ -132,8 +132,9 @@ docker run --detach --name app \
|
|||||||
|
|
||||||
With no setting, the address of every new visitor is sent to GeoJS, a web
|
With no setting, the address of every new visitor is sent to GeoJS, a web
|
||||||
service, to find its country and network. `SWWAF_LOOKUP_SOURCE=off` sends
|
service, to find its country and network. `SWWAF_LOOKUP_SOURCE=off` sends
|
||||||
nothing. `SWWAF_LOOKUP_SOURCE=file` looks addresses up in a database file on
|
nothing. `SWWAF_LOOKUP_SOURCE=file`, with `SWWAF_LOOKUP_DB_PATH` naming a
|
||||||
your own host (see "Lookup of AS number and country" in [`SPEC.md`](SPEC.md)).
|
database file on your own host, looks addresses up in that file (see "Lookup of
|
||||||
|
AS number and country" in [`SPEC.md`](SPEC.md)).
|
||||||
|
|
||||||
`deploy/example-app` is such an app. `make example-app` builds it and checks it
|
`deploy/example-app` is such an app. `make example-app` builds it and checks it
|
||||||
(see "Checking that it works" below). To run `smallwebwaf` outside the image
|
(see "Checking that it works" below). To run `smallwebwaf` outside the image
|
||||||
@@ -149,9 +150,10 @@ request that counts against its client in its history. It is one of:
|
|||||||
- a broken limit;
|
- a broken limit;
|
||||||
- a refusal by a rule, by the Core Rule Set or for a missing or wrong token.
|
- a refusal by a rule, by the Core Rule Set or for a missing or wrong token.
|
||||||
|
|
||||||
`smallwebwaf` handles each request in this order. The first step that refuses it
|
`smallwebwaf` handles each request in this order. A request one step refuses
|
||||||
ends the handling. Nothing of a refused request reaches the app. "What happens
|
skips the steps after it, apart from step 13. Nothing of a refused request
|
||||||
to a request" in [`SPEC.md`](SPEC.md) gives each step in full.
|
reaches the app. "What happens to a request" in [`SPEC.md`](SPEC.md) gives each
|
||||||
|
step in full.
|
||||||
|
|
||||||
1. `GET /_smallwebwaf/healthz` is answered `200` with `ok` at once, before any
|
1. `GET /_smallwebwaf/healthz` is answered `200` with `ok` at once, before any
|
||||||
check and without asking the app.
|
check and without asking the app.
|
||||||
@@ -187,18 +189,18 @@ to a request" in [`SPEC.md`](SPEC.md) gives each step in full.
|
|||||||
10. The rule files are checked. A `log` rule notes the match. A `block` rule
|
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
|
refuses the request. A `ban` rule refuses it and bans the client, as a clear
|
||||||
sign of attack.
|
sign of attack.
|
||||||
11. The Core Rule Set inspects the request's method, URL and headers, and the
|
11. The Core Rule Set inspects the request's method, URL and headers, and, once
|
||||||
body too once `SWWAF_WAF_BODY_LIMIT` is set. A request it takes for an
|
`SWWAF_WAF_BODY_LIMIT` is set, form, JSON and XML bodies. A request it takes
|
||||||
attack is refused while `SWWAF_WAF_MODE` is `block`, the default, and let
|
for an attack is refused while `SWWAF_WAF_MODE` is `block`, the default, and
|
||||||
through while it is `detect`. It bans no one by itself.
|
let through while it is `detect`. It bans no one by itself.
|
||||||
12. A request under `/_smallwebwaf/` is answered here by `smallwebwaf`, after
|
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
|
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.
|
answer is streamed back within the size and time limits.
|
||||||
13. Once the answer has ended, the request's bytes are counted for the byte
|
13. Once the answer has ended, a request passed to the app has its bytes counted
|
||||||
limits. If a rule, a trap path, the Core Rule Set or a token check refused
|
for the byte limits. If a rule, a trap path, the Core Rule Set or a token
|
||||||
it, the refusal is counted for the error burst. The client's history and the
|
check refused the request, the refusal is counted for the error burst. The
|
||||||
anomaly counters are updated. More than `SWWAF_ERROR_BURST_THRESHOLD`
|
client's history and the anomaly counters are updated. More than
|
||||||
refusals of one client in a minute ban it.
|
`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.
|
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
|
It refuses a request only for a size or time limit, or for a missing or wrong
|
||||||
@@ -209,27 +211,32 @@ configuration before enforcing it.
|
|||||||
What a refused visitor sees:
|
What a refused visitor sees:
|
||||||
|
|
||||||
- `SWWAF_BAN_RESPONSE`, by default `403` with the plain text `Forbidden`. It can
|
- `SWWAF_BAN_RESPONSE`, by default `403` with the plain text `Forbidden`. It can
|
||||||
be `429` instead, or `close`, which closes the connection without an answer.
|
be `429` instead, or `close`, which closes the connection, so that traefik
|
||||||
It answers:
|
answers `502`. It answers:
|
||||||
- a ban or a broken rate limit;
|
- a ban or a broken rate limit;
|
||||||
- a `ban` rule or a trap path;
|
- a `ban` rule or a trap path;
|
||||||
- `SWWAF_DENY_NETS` and the country lists;
|
- `SWWAF_DENY_NETS` and the country lists;
|
||||||
- a reputation source whose action is `deny`.
|
- a reputation source whose action is `deny`.
|
||||||
- `403` for a `block` rule and the Core Rule Set.
|
- `403` for a `block` rule and the Core Rule Set.
|
||||||
- `413` or `408` when the client passes a size or time limit, `502` or `504`
|
- For a size or time limit:
|
||||||
when the app does.
|
- `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
|
- `401` from an endpoint of `smallwebwaf`'s own, for a request without its
|
||||||
token.
|
token.
|
||||||
|
|
||||||
## Settings you are likely to touch
|
## Settings you are likely to touch
|
||||||
|
|
||||||
|
Each setting is an environment variable of the app's container, such as
|
||||||
|
`docker run -e SWWAF_MODE=observe ...`, read at start.
|
||||||
[`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`: log what would have been refused, refuse almost nothing,
|
||||||
ban no one. For trying a configuration.
|
ban no one. For trying a configuration.
|
||||||
- `SWWAF_ALLOW_NETS`: netblocks whose clients are refused only for a size or
|
- `SWWAF_ALLOW_NETS`: netblocks you trust, such as your monitoring. Their
|
||||||
time limit or a missing or wrong token, such as your monitoring.
|
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
|
||||||
byte limits, such as a machine that talks to the app all day.
|
byte limits, such as a machine that talks to the app all day.
|
||||||
- `SWWAF_TRAP_PATHS`: paths only scanners ask for, such as
|
- `SWWAF_TRAP_PATHS`: paths only scanners ask for, such as
|
||||||
@@ -237,7 +244,8 @@ values it takes. The ones most deployments touch:
|
|||||||
the client.
|
the client.
|
||||||
- `SWWAF_WAF_DISABLED_RULES`: Core Rule Set rule ids to switch off when a rule
|
- `SWWAF_WAF_DISABLED_RULES`: Core Rule Set rule ids to switch off when a rule
|
||||||
refuses your app's ordinary requests. The log line names the rules a request
|
refuses your app's ordinary requests. The log line names the rules a request
|
||||||
matched.
|
matched. Keep the six default ids in a list you give (see
|
||||||
|
[`docs/settings.md`](docs/settings.md)).
|
||||||
- `SWWAF_REQUEST_MAX_BYTES`, `SWWAF_CLIENT_REQUEST_TIMEOUT` and
|
- `SWWAF_REQUEST_MAX_BYTES`, `SWWAF_CLIENT_REQUEST_TIMEOUT` and
|
||||||
`SWWAF_UPSTREAM_REQUEST_TIMEOUT`: raise them for an app that takes uploads
|
`SWWAF_UPSTREAM_REQUEST_TIMEOUT`: raise them for an app that takes uploads
|
||||||
over 100 MiB or slower than 60 seconds.
|
over 100 MiB or slower than 60 seconds.
|
||||||
@@ -261,9 +269,8 @@ values it takes. The ones most deployments touch:
|
|||||||
`SWWAF_MODE=observe` and ask for `/.env`. The request reaches the app. Its log
|
`SWWAF_MODE=observe` and ask for `/.env`. The request reaches the app. Its log
|
||||||
line has `"would_action":"banned"` and `"rule_ids":["env-file"]`. In `enforce`
|
line has `"would_action":"banned"` and `"rule_ids":["env-file"]`. In `enforce`
|
||||||
mode the same request bans your address for seven days, and your next request
|
mode the same request bans your address for seven days, and your next request
|
||||||
makes the ban permanent. Lift the ban by editing `bans.json` or through the
|
makes the ban permanent. Lift the ban by editing `bans.json` (see "Bans" in
|
||||||
ban endpoint (see "Bans" in [`SPEC.md`](SPEC.md) and
|
[`SPEC.md`](SPEC.md)).
|
||||||
[`docs/admin-endpoints.md`](docs/admin-endpoints.md)).
|
|
||||||
- With `SWWAF_METRICS_TOKEN` set,
|
- With `SWWAF_METRICS_TOKEN` set,
|
||||||
`curl -H "Authorization: Bearer $TOKEN" https://<site>/_smallwebwaf/metrics`
|
`curl -H "Authorization: Bearer $TOKEN" https://<site>/_smallwebwaf/metrics`
|
||||||
answers with the metrics.
|
answers with the metrics.
|
||||||
@@ -285,8 +292,8 @@ the `Makefile` targets are thin shims that call them. The scripts are POSIX sh.
|
|||||||
The tests and the linter run in Docker, as phases of the `Dockerfile`, and never
|
The tests and the linter run in Docker, as phases of the `Dockerfile`, and never
|
||||||
on the host.
|
on the host.
|
||||||
|
|
||||||
- `script/bootstrap`: installs what the other scripts need on the host.
|
- `script/bootstrap`: installs on the host what the other scripts need, with
|
||||||
`make bootstrap`. They need:
|
`make bootstrap`:
|
||||||
- `make`, `git` and `curl`;
|
- `make`, `git` and `curl`;
|
||||||
- Go, for `gofmt`;
|
- Go, for `gofmt`;
|
||||||
- node, yarn and prettier.
|
- node, yarn and prettier.
|
||||||
@@ -416,9 +423,9 @@ another port, the health check's timing, and the forwarded headers.
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- Everything `SPEC.md` describes is built.
|
- Planned, as "Planned, not built" in [`SPEC.md`](SPEC.md) describes:
|
||||||
- Two points `SPEC.md` defers until there is experience to go on: sharing bans
|
- sharing bans between the instances of several apps;
|
||||||
between the instances of several apps, and sending the log over RELP.
|
- sending the log over RELP.
|
||||||
|
|
||||||
## Documents
|
## Documents
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -78,11 +78,14 @@ on one line:
|
|||||||
|
|
||||||
- `instance`: `SWWAF_INSTANCE_NAME`. `time`: when the alert was raised. `event`:
|
- `instance`: `SWWAF_INSTANCE_NAME`. `time`: when the alert was raised. `event`:
|
||||||
one of the events above.
|
one of the events above.
|
||||||
- `client`: the address whose request raised the alert. `netblock`: the ban's
|
- `client`: the address whose request raised the alert. Empty for
|
||||||
netblock, the netblock an anomaly counted, or, for the other events, the
|
`source_failure` and `file_error`.
|
||||||
client as rate limiting counts it: an IPv4 address as a /32, or an IPv6 group.
|
- `netblock`: empty for `source_failure` and `file_error`; otherwise one of:
|
||||||
Both are empty for `source_failure` and `file_error`.
|
- the ban's netblock;
|
||||||
- `asn`, `as_name` and `country`: the client's as known when the alert was
|
- the netblock an anomaly counted;
|
||||||
|
- for the other events, the client as rate limiting counts it: an IPv4
|
||||||
|
address as a /32, or an IPv6 group.
|
||||||
|
- `asn`, `as_name` and `country`: the client's, as known when the alert was
|
||||||
raised. They are empty when GeoJS had not answered yet.
|
raised. They are empty when GeoJS had not answered yet.
|
||||||
- `reason`: a short sentence, such as
|
- `reason`: a short sentence, such as
|
||||||
`requests per minute over the limit of 1000` or `listed by a blocklist`.
|
`requests per minute over the limit of 1000` or `listed by a blocklist`.
|
||||||
|
|||||||
+38
-34
@@ -21,10 +21,10 @@ checked again when gitea or the Core Rule Set changes version.
|
|||||||
- a release attachment.
|
- a release attachment.
|
||||||
|
|
||||||
At the defaults, an upload larger than 100 MiB or taking more than 60
|
At the defaults, an upload larger than 100 MiB or taking more than 60
|
||||||
seconds is cut off, and no one is banned for it. "Size and time limits" in
|
seconds is cut off ("Size and time limits" in `SPEC.md`). A gitea that takes
|
||||||
`SPEC.md` gives the answers the client gets. A gitea that takes large
|
large uploads needs `SWWAF_REQUEST_MAX_BYTES`,
|
||||||
uploads needs `SWWAF_REQUEST_MAX_BYTES`, `SWWAF_CLIENT_REQUEST_TIMEOUT` and
|
`SWWAF_CLIENT_REQUEST_TIMEOUT` and `SWWAF_UPSTREAM_REQUEST_TIMEOUT` raised
|
||||||
`SWWAF_UPSTREAM_REQUEST_TIMEOUT` raised to fit.
|
to fit.
|
||||||
|
|
||||||
- Archive download and blame or history pages are what scrapers hammer. The
|
- Archive download and blame or history pages are what scrapers hammer. The
|
||||||
request limits do most of the work there.
|
request limits do most of the work there.
|
||||||
@@ -63,15 +63,15 @@ toward). The request log names the rule in `waf_rule_ids`, which
|
|||||||
|
|
||||||
### A query string that reads as an attack
|
### A query string that reads as an attack
|
||||||
|
|
||||||
Most often this is a search. Every query parameter is checked against the Core
|
Most often this is a search. A search term is a query parameter, which the Core
|
||||||
Rule Set's lists of system files and commands, except the ten named under
|
Rule Set checks against its lists of system files and commands (see the fourth
|
||||||
"Attack detection" in `SPEC.md`. A release attachment uploaded through the API
|
change under "Attack detection" in `SPEC.md`). A release attachment uploaded
|
||||||
as `docker-compose.yml` is refused the same way. Examples:
|
through the API as `docker-compose.yml` is refused the same way. Examples:
|
||||||
|
|
||||||
- a name on those lists, such as `package.json`, `.gitignore` or
|
- a name on those lists, such as `package.json`, `.gitignore` or
|
||||||
`docker-compose.yml`;
|
`docker-compose.yml`;
|
||||||
- text that starts with a command name, or whose first word has one right after
|
- text that starts with a command name, such as `python3` or `ssh key`, or has
|
||||||
a `/`, such as `python3`, `ssh key` or `feat/docker-support`;
|
one right after a `/` in its first word, such as `feat/docker-support`;
|
||||||
- a shell command with its options or a system path (`ls -la`, `sed -i`,
|
- a shell command with its options or a system path (`ls -la`, `sed -i`,
|
||||||
`/bin/sh`), or a command in backticks;
|
`/bin/sh`), or a command in backticks;
|
||||||
- script code (`fetch(`, `${VAR}`, `process.env`), HTML (`<img src=`) or an SQL
|
- script code (`fetch(`, `${VAR}`, `process.env`), HTML (`<img src=`) or an SQL
|
||||||
@@ -81,15 +81,15 @@ as `docker-compose.yml` is refused the same way. Examples:
|
|||||||
### A branch, tag or file name that starts with a command name
|
### A branch, tag or file name that starts with a command name
|
||||||
|
|
||||||
The Core Rule Set's list of commands (932260) holds names such as `docker-`,
|
The Core Rule Set's list of commands (932260) holds names such as `docker-`,
|
||||||
`python3`, `ansible`, `base64` and `whoami`. A branch, tag or file name that
|
`python3`, `ansible`, `base64` and `whoami`. A query parameter holding a branch,
|
||||||
starts with one is refused. So is one that has such a name right after a `/`,
|
tag or file name that starts with one is refused. So is one that has such a name
|
||||||
such as `feat/docker-support`, `fix/docker-build` or
|
right after a `/`, such as `feat/docker-support`, `fix/docker-build` or
|
||||||
`renovate/docker-build-push-action-6.x`. gitea's own pages send the name in a
|
`renovate/docker-build-push-action-6.x`. gitea's own pages send the name in a
|
||||||
query parameter that 930120, 932160 and 932260 still check:
|
query parameter that 930120, 932160 and 932260 still check:
|
||||||
|
|
||||||
- `refSubUrl`, the branch or tag of a directory listing. It is sent when the
|
- `refSubUrl`, the branch or tag of a directory listing. A listing sends it when
|
||||||
listing asks for its entries' last commits in a second request, which it does
|
it asks for its entries' last commits in a second request. It does so whenever
|
||||||
whenever gitea takes more than a second to work them out;
|
gitea takes more than a second to work them out;
|
||||||
- `name`, when a branch is deleted or restored on the branches page;
|
- `name`, when a branch is deleted or restored on the branches page;
|
||||||
- `tag`, when a release is started from a tag;
|
- `tag`, when a release is started from a tag;
|
||||||
- `template`, the file of an issue template, when an issue is opened from it;
|
- `template`, the file of an issue template, when an issue is opened from it;
|
||||||
@@ -110,9 +110,9 @@ What a visitor sees, for a branch named `docker-build`:
|
|||||||
### A file name in a query parameter that trips the code rules
|
### A file name in a query parameter that trips the code rules
|
||||||
|
|
||||||
The Core Rule Set's rules for Java, script, PowerShell and SQL find what they
|
The Core Rule Set's rules for Java, script, PowerShell and SQL find what they
|
||||||
look for in some file names. This holds in the parameters `smallwebwaf` leaves
|
look for in some file names. This holds too in the ten query parameters that
|
||||||
out of 930120, 932160 and 932260 too (see the changes to the Core Rule Set under
|
930120, 932160 and 932260 do not check (see the fourth change under "Attack
|
||||||
"Attack detection" in `SPEC.md`). Examples:
|
detection" in `SPEC.md`). Examples:
|
||||||
|
|
||||||
- `runtime` in OpenJDK's `src/java.base/share/classes/java/lang/Runtime.java`
|
- `runtime` in OpenJDK's `src/java.base/share/classes/java/lang/Runtime.java`
|
||||||
(944110);
|
(944110);
|
||||||
@@ -129,9 +129,9 @@ For such a file, these requests are refused:
|
|||||||
- the API's call for the contents of files that names it in its query (`body`);
|
- the API's call for the contents of files that names it in its query (`body`);
|
||||||
- the commit graph of that file alone (`file`);
|
- the commit graph of that file alone (`file`);
|
||||||
- the hidden lines of its diff (`path`), which stay hidden;
|
- the hidden lines of its diff (`path`), which stay hidden;
|
||||||
- the sign-in page gitea sends a visitor to on the way to a page of the file
|
- the sign-in page, for a visitor who opens a page of the file that needs
|
||||||
that needs signing in, such as its edit page (`redirect_to`). The visitor can
|
signing in, such as its edit page (`redirect_to`). The visitor can still sign
|
||||||
still sign in by opening the sign-in page itself.
|
in by opening the sign-in page itself.
|
||||||
|
|
||||||
### A path
|
### A path
|
||||||
|
|
||||||
@@ -143,25 +143,30 @@ For such a file, these requests are refused:
|
|||||||
`process.env.d.ts` does.
|
`process.env.d.ts` does.
|
||||||
- A path with a directory named `...` (930100, 930110), such as `.../notes.txt`.
|
- A path with a directory named `...` (930100, 930110), such as `.../notes.txt`.
|
||||||
|
|
||||||
For such a file, the page of the file, its raw file, its history, its blame and
|
For such a file, these are refused:
|
||||||
its edit page are refused. The directory that holds it lists it, and a clone
|
|
||||||
carries it.
|
- the page of the file;
|
||||||
|
- its raw file;
|
||||||
|
- its history and its blame;
|
||||||
|
- its edit page.
|
||||||
|
|
||||||
|
The directory that holds it lists it, and a clone carries it.
|
||||||
|
|
||||||
### A container image push that reuses a layer
|
### A container image push that reuses a layer
|
||||||
|
|
||||||
A push can reuse a layer of an image docker has pushed to that gitea before.
|
A push can reuse a layer of an image docker has pushed to that gitea before.
|
||||||
docker then asks gitea to take the layer from that image, naming it in the query
|
docker then asks gitea to take the layer from that image, naming it in the query
|
||||||
parameter `from`, which 932260 checks. The request is refused when the image's
|
parameter `from`, which 932260 checks. The request is refused when the image's
|
||||||
owner starts with an entry on the list of commands. So is one whose name after
|
owner starts with an entry on the list of commands. It is refused too when the
|
||||||
the owner does. Examples are `owner/python3-app`, `owner/docker-base`, and any
|
image's name after the owner starts with one. Examples are `owner/python3-app`,
|
||||||
image of an owner named `ansible` or `docker-team`.
|
`owner/docker-base`, and any image of an owner named `ansible` or `docker-team`.
|
||||||
|
|
||||||
What happens next depends on docker's image store. A docker that keeps its
|
What happens next depends on docker's image store. A docker that keeps its
|
||||||
images in containerd's image store fails the push, and fails each new try the
|
images in containerd's image store fails the push, and fails each new try the
|
||||||
same way. A new install of Docker Engine 29 does this. A docker that keeps them
|
same way. A new install of Docker Engine 29 does this. A docker that keeps them
|
||||||
in its own older image store uploads the layer instead. The push then succeeds
|
in its own older image store uploads the layer instead. The push then succeeds
|
||||||
after one refusal for each layer it reuses, and each of those refusals counts
|
after one refusal for each layer it reuses, and each of those refusals counts
|
||||||
toward the error burst.
|
toward the error burst ("Attack detection" in `SPEC.md`).
|
||||||
|
|
||||||
### Artifact uploads from `actions/upload-artifact@v3`
|
### Artifact uploads from `actions/upload-artifact@v3`
|
||||||
|
|
||||||
@@ -169,9 +174,9 @@ This action sends the header `Content-Range`, which the Core Rule Set refuses
|
|||||||
(920450). The action sends two files at a time, does not retry a `403`, and
|
(920450). The action sends two files at a time, does not retry a `403`, and
|
||||||
sends no further file once one is refused. So an upload is refused once or
|
sends no further file once one is refused. So an upload is refused once or
|
||||||
twice, however many files it holds, and the step fails. One upload does not ban
|
twice, however many files it holds, and the step fails. One upload does not ban
|
||||||
the runner. More than 30 such refusals from its address within a minute, as when
|
the runner. The error burst ("Attack detection" in `SPEC.md`) bans it after more
|
||||||
many jobs upload at once, ban it through the error burst.
|
than 30 such refusals from its address in a minute, as when many jobs upload at
|
||||||
`actions/upload-artifact@v4` does not send the header.
|
once. `actions/upload-artifact@v4` does not send the header.
|
||||||
|
|
||||||
## What body inspection refuses
|
## What body inspection refuses
|
||||||
|
|
||||||
@@ -193,8 +198,7 @@ Of the requests above, the script sends:
|
|||||||
code rules", the API's list of its commits;
|
code rules", the API's list of its commits;
|
||||||
- for `Runtime.java` alone, the commit graph of that file, the hidden lines of
|
- for `Runtime.java` alone, the commit graph of that file, the hidden lines of
|
||||||
its diff, and the API's call for the contents of files that names it;
|
its diff, and the API's call for the contents of files that names it;
|
||||||
- the sign-in page gitea sends a visitor to on the way to the edit page of
|
- the sign-in page for a visitor who opens the edit page of `document.write.js`;
|
||||||
`document.write.js`;
|
|
||||||
- the pages of `java.lang.String.md`,
|
- the pages of `java.lang.String.md`,
|
||||||
`META-INF/services/org.apache.commons.logging.LogFactory`, `process.env.d.ts`
|
`META-INF/services/org.apache.commons.logging.LogFactory`, `process.env.d.ts`
|
||||||
and `.../notes.txt`;
|
and `.../notes.txt`;
|
||||||
|
|||||||
+3
-2
@@ -31,6 +31,7 @@ is pinned that way.
|
|||||||
- nixpkgs: the release file `nixexprs.tar.xz` of
|
- nixpkgs: the release file `nixexprs.tar.xz` of
|
||||||
`nixos-26.05.11045.774debe7a0d1` on `releases.nixos.org`, named with its
|
`nixos-26.05.11045.774debe7a0d1` on `releases.nixos.org`, named with its
|
||||||
SHA-256 hash.
|
SHA-256 hash.
|
||||||
- `runsvinit`: the archived repository, unchanged since 2015, at tag `v2.0.0`,
|
- `runsvinit`: the archived repository, unchanged since 2015, at its last commit
|
||||||
by commit hash, with a `go.mod` the build writes.
|
(`b4b2c78`, eight commits after `v2.0.0`), by commit hash, with a `go.mod` the
|
||||||
|
build writes.
|
||||||
- The Go image that builds `smallwebwaf`: by digest.
|
- The Go image that builds `smallwebwaf`: by digest.
|
||||||
+3
-5
@@ -1,10 +1,8 @@
|
|||||||
# The metrics
|
# The metrics
|
||||||
|
|
||||||
`GET /_smallwebwaf/metrics` answers in the Prometheus text format. The
|
The metrics `GET /_smallwebwaf/metrics` answers with, as "Metrics endpoint" in
|
||||||
requirements are under "Metrics endpoint" in [`SPEC.md`](../SPEC.md); this file
|
[`SPEC.md`](../SPEC.md) describes it. Prometheus keeps the `instance` label as
|
||||||
lists the metrics. Every metric carries the label `instance`, set to
|
`exported_instance` unless the scrape sets `honor_labels: true`.
|
||||||
`SWWAF_INSTANCE_NAME`; Prometheus keeps it as `exported_instance` unless the
|
|
||||||
scrape sets `honor_labels: true`. No metric carries a client's address.
|
|
||||||
|
|
||||||
## Traffic
|
## Traffic
|
||||||
|
|
||||||
|
|||||||
+23
-12
@@ -12,9 +12,13 @@ sent to a syslog server. A field that does not apply to a request is left out.
|
|||||||
|
|
||||||
## Every line
|
## Every line
|
||||||
|
|
||||||
Every line has `type` (`request`; the process's own messages have `process`),
|
Every line has:
|
||||||
the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
|
|
||||||
`as_name`, `country`, `action` and `duration_total`.
|
- `type`: `request` (the process's own messages have `process`);
|
||||||
|
- the standard web log fields;
|
||||||
|
- `request_id`, `peer_ip` and `client_group`;
|
||||||
|
- `asn`, `as_name` and `country`;
|
||||||
|
- `action` and `duration_total`.
|
||||||
|
|
||||||
## Standard web log fields
|
## Standard web log fields
|
||||||
|
|
||||||
@@ -28,7 +32,7 @@ the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
|
|||||||
## Request detail
|
## Request detail
|
||||||
|
|
||||||
- `request_id`: the `X-Request-ID` a trusted proxy sent, or a new random one of
|
- `request_id`: the `X-Request-ID` a trusted proxy sent, or a new random one of
|
||||||
26 letters and digits. It goes to the app in `X-Request-ID`.
|
26 letters and digits.
|
||||||
- `peer_ip`: the TCP peer, normally traefik.
|
- `peer_ip`: the TCP peer, normally traefik.
|
||||||
- `forwarded_for`: the `X-Forwarded-For` header as received.
|
- `forwarded_for`: the `X-Forwarded-For` header as received.
|
||||||
- `client_group`: the client as rate limiting counts it, an IPv4 address as a
|
- `client_group`: the client as rate limiting counts it, an IPv4 address as a
|
||||||
@@ -73,15 +77,22 @@ the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
|
|||||||
rate limits and the setting that lowered it, when one did. `bytes_percent` and
|
rate limits and the setting that lowered it, when one did. `bytes_percent` and
|
||||||
`bytes_percent_setting` do the same for the byte limits.
|
`bytes_percent_setting` do the same for the byte limits.
|
||||||
- `counts`: the client's totals as rate limiting counts them, this request
|
- `counts`: the client's totals as rate limiting counts them, this request
|
||||||
included. It is left out when rate limiting did not count the request. A count
|
included. It is left out when rate limiting did not count the request. It
|
||||||
can have a fraction, since each window is counted in two buckets with the
|
holds:
|
||||||
earlier one weighted:
|
|
||||||
- `minute`, `hour` and `day`: requests;
|
- `minute`, `hour` and `day`: requests;
|
||||||
- `minute_bytes`, `hour_bytes` and `day_bytes`: bytes.
|
- `minute_bytes`, `hour_bytes` and `day_bytes`: bytes.
|
||||||
- `limit_hit`: the limit broken: `minute`, `hour` or `day` for a rate limit;
|
|
||||||
`minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit; or
|
A count can have a fraction (see "Counting method" in
|
||||||
`error_burst`. A request whose bytes broke a limit was passed on whole, so its
|
[`SPEC.md`](../SPEC.md)).
|
||||||
`action` stays `forward`.
|
|
||||||
|
- `limit_hit`: the limit broken:
|
||||||
|
- `minute`, `hour` or `day` for a rate limit;
|
||||||
|
- `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit;
|
||||||
|
- `error_burst`.
|
||||||
|
|
||||||
|
A request whose bytes broke a limit was passed on whole, so its `action`
|
||||||
|
stays `forward`.
|
||||||
|
|
||||||
- `rule_ids`: the rule file rules that matched, in order, up to the one that
|
- `rule_ids`: the rule file rules that matched, in order, up to the one that
|
||||||
refused.
|
refused.
|
||||||
- `waf_score` and `waf_rule_ids`: the Core Rule Set's score and the rules that
|
- `waf_score` and `waf_rule_ids`: the Core Rule Set's score and the rules that
|
||||||
@@ -91,7 +102,7 @@ the standard web log fields, `request_id`, `peer_ip`, `client_group`, `asn`,
|
|||||||
- the CrowdSec decision list's URL;
|
- the CrowdSec decision list's URL;
|
||||||
- DNS blocklist zones;
|
- DNS blocklist zones;
|
||||||
- `abuseipdb`.
|
- `abuseipdb`.
|
||||||
- `offence`: the kind of offence recorded, when one was.
|
- `offence`: `limit`, for a request that broke a limit.
|
||||||
- `ban_expires`: when the client's ban ends, for a request that made one or was
|
- `ban_expires`: when the client's ban ends, for a request that made one or was
|
||||||
refused under one: a time, or `permanent`.
|
refused under one: a time, or `permanent`.
|
||||||
|
|
||||||
|
|||||||
+17
-18
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
Every setting, its default and the values it takes, in the groups of "Settings"
|
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
|
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
|
that take `off`, the `_FILE` form, and what each group does and why.
|
||||||
set to a value its entry does not allow stops the start.
|
|
||||||
|
|
||||||
## Forms of values
|
## Forms of values
|
||||||
|
|
||||||
@@ -27,8 +26,9 @@ set to a value its entry does not allow stops the start.
|
|||||||
|
|
||||||
- `SWWAF_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An `http` or
|
- `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.
|
`https` URL with a host and an optional port, and nothing more.
|
||||||
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens, an
|
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens: `:` and a
|
||||||
optional host and a port. The host stays empty.
|
port, such as `:9000`. Leave the host out (see "Ports" in
|
||||||
|
[`SPEC.md`](../SPEC.md)).
|
||||||
- `SWWAF_INSTANCE_NAME` (default: the container's host name): the `instance`
|
- `SWWAF_INSTANCE_NAME` (default: the container's host name): the `instance`
|
||||||
name in every log line, alert and metric, such as `fsn1app1/gitea`. Valid
|
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
|
UTF-8. No control character while `SWWAF_ALERT_NTFY_URL` is set. 1 to 48
|
||||||
@@ -42,8 +42,8 @@ set to a value its entry does not allow stops the start.
|
|||||||
client's group, from 32 to 128.
|
client's group, from 32 to 128.
|
||||||
- `SWWAF_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a whole
|
- `SWWAF_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a whole
|
||||||
number above zero.
|
number above zero.
|
||||||
- `SWWAF_MAX_BANS` (default `5000`): the most bans `smallwebwaf` made that are
|
- `SWWAF_MAX_BANS` (default `5000`): how many of its own bans `smallwebwaf`
|
||||||
kept, a whole number above zero.
|
keeps, a whole number above zero.
|
||||||
|
|
||||||
## Tokens
|
## Tokens
|
||||||
|
|
||||||
@@ -90,8 +90,9 @@ set to a value its entry does not allow stops the start.
|
|||||||
|
|
||||||
## Static lists
|
## Static lists
|
||||||
|
|
||||||
- `SWWAF_ALLOW_NETS` (default empty): netblocks whose clients are refused only
|
- `SWWAF_ALLOW_NETS` (default empty): netblocks you trust, such as your
|
||||||
for a size or time limit or a missing or wrong token, such as your monitoring.
|
monitoring. Their clients are refused only for a size or time limit or a
|
||||||
|
missing or wrong token.
|
||||||
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
- `SWWAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
||||||
exempt from rate limiting and the byte limits.
|
exempt from rate limiting and the byte limits.
|
||||||
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
- `SWWAF_DENY_NETS` (default empty): netblocks whose clients are always refused.
|
||||||
@@ -150,7 +151,7 @@ set to a value its entry does not allow stops the start.
|
|||||||
such as `cn,ru,kp`.
|
such as `cn,ru,kp`.
|
||||||
- `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
|
- `SWWAF_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
|
||||||
countries whose clients get through, such as `us,de`. A country on both lists
|
countries whose clients get through, such as `us,de`. A country on both lists
|
||||||
is refused.
|
stops the start.
|
||||||
|
|
||||||
## Biased thresholds
|
## Biased thresholds
|
||||||
|
|
||||||
@@ -164,8 +165,8 @@ set to a value its entry does not allow stops the start.
|
|||||||
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage for a client the
|
- `SWWAF_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage for a client the
|
||||||
lookup cannot place.
|
lookup cannot place.
|
||||||
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
- `SWWAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
||||||
text file of `AS<number>:<percent>` lines, one to a line. A URL that is also
|
text file with one AS number and percentage on each line, such as
|
||||||
in `SWWAF_BLOCKLIST_URLS` is refused.
|
`AS64496:50`. A URL that is also in `SWWAF_BLOCKLIST_URLS` is refused.
|
||||||
|
|
||||||
## Attack detection
|
## Attack detection
|
||||||
|
|
||||||
@@ -260,8 +261,8 @@ set to a value its entry does not allow stops the start.
|
|||||||
`ban,permanent_ban,waf_block,anomaly,reputation_hit,source_failure,file_error`):
|
`ban,permanent_ban,waf_block,anomaly,reputation_hit,source_failure,file_error`):
|
||||||
the event types alerts are sent for, from those in [`alerts.md`](alerts.md)
|
the event types alerts are sent for, from those in [`alerts.md`](alerts.md)
|
||||||
but `summary`. Any other name is refused.
|
but `summary`. Any other name is refused.
|
||||||
- `SWWAF_ALERT_COOLDOWN` (default `15m`): how long a repeat of an alert is held
|
- `SWWAF_ALERT_COOLDOWN` (default `15m`): how long after an alert is sent its
|
||||||
back. A duration or `off`.
|
repeats are held back. A duration or `off`.
|
||||||
- `SWWAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour. A
|
- `SWWAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour. A
|
||||||
whole number above zero, or `off`.
|
whole number above zero, or `off`.
|
||||||
|
|
||||||
@@ -294,10 +295,8 @@ as `REQUESTS_PER_MINUTE`:
|
|||||||
|
|
||||||
## Settings given as files
|
## Settings given as files
|
||||||
|
|
||||||
Add `_FILE` to a setting's name to read its value from a file:
|
To give a token as a file, as "Tokens" under "Deployment" in
|
||||||
`SWWAF_METRICS_TOKEN_FILE` names the file that holds `SWWAF_METRICS_TOKEN`. To
|
[`SPEC.md`](../SPEC.md) describes, make the file on the host:
|
||||||
keep a token from the app, make the file on the host, owned by uid 65532 with
|
|
||||||
mode `0400`:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir -p /srv/app/tokens
|
mkdir -p /srv/app/tokens
|
||||||
@@ -306,7 +305,7 @@ chown 65532:65532 /srv/app/tokens/metrics
|
|||||||
chmod 0400 /srv/app/tokens/metrics
|
chmod 0400 /srv/app/tokens/metrics
|
||||||
```
|
```
|
||||||
|
|
||||||
Mount its directory into the container read-only, and name the file:
|
Then mount its directory into the container read-only, and name the file:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run \
|
docker run \
|
||||||
|
|||||||
+19
-15
@@ -15,8 +15,8 @@ those `smallwebwaf` made. Per entry:
|
|||||||
- `lifted`: when an admin lifted it, if one did;
|
- `lifted`: when an admin lifted it, if one did;
|
||||||
- `notes`: the notes below.
|
- `notes`: the notes below.
|
||||||
|
|
||||||
To ban a netblock, add an entry with its `netblock`, `start` and `expires`.
|
An entry an admin adds needs `netblock`, `start` and `expires`; `reason`,
|
||||||
`reason`, `notes` and `cause` may be left out; the cause is then `admin`:
|
`notes` and `cause` may be left out, and the cause is then `admin`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -32,20 +32,23 @@ To ban a netblock, add an entry with its `netblock`, `start` and `expires`.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To lift a ban, add `"lifted"` with the time you lift it, such as
|
"Bans" in [`SPEC.md`](../SPEC.md) says how an edit lifts, keeps or forgets a
|
||||||
`"lifted": "2026-10-06T13:00:00Z"`. To keep a ban `smallwebwaf` made, set its
|
ban. The notes:
|
||||||
`cause` to `admin`. To forget a ban, delete its entry.
|
|
||||||
|
|
||||||
The notes:
|
|
||||||
|
|
||||||
- `asn`, `as_name` and `country`: the client's, added when the lookup answers
|
- `asn`, `as_name` and `country`: the client's, added when the lookup answers
|
||||||
(empty when lookups are off);
|
(empty when lookups are off);
|
||||||
- what was broken: the rule id and target that matched, or the trap path, or the
|
- for a broken limit, `kind` (`requests`, `bytes` or `refusals`), `limit`,
|
||||||
limit, its window and the count reached;
|
`window` and `count`: what the limit was on, the limit, its window and the
|
||||||
- the client's percentage of the limit, with the setting that gave it;
|
count reached;
|
||||||
- the reputation sources the client is on;
|
- `limit_percent` and `limit_percent_setting`: the client's percentage of that
|
||||||
- `request`: the request that caused the ban: time, method, host, path with its
|
limit and the setting that gave it, when a setting lowered it;
|
||||||
query string, status and user agent, each text cut to 256 bytes;
|
- for a clear sign of attack, `rule_id` and `target`, or `trap_path`;
|
||||||
|
- `reputation`: the sources the client was on, each with its `source` and, for
|
||||||
|
AbuseIPDB, its `score`;
|
||||||
|
- `request`: the request that caused the ban, each text cut to 256 bytes:
|
||||||
|
- `time`, `method` and `host`;
|
||||||
|
- `path`, with its query string;
|
||||||
|
- `status` and `user_agent`;
|
||||||
- `requests` and `refused`: the netblock's requests since it was first seen, and
|
- `requests` and `refused`: the netblock's requests since it was first seen, and
|
||||||
how many this ban has refused so far, kept up to date while the ban lasts;
|
how many this ban has refused so far, kept up to date while the ban lasts;
|
||||||
- `earlier_bans`: how many earlier bans the netblock had, by cause.
|
- `earlier_bans`: how many earlier bans the netblock had, by cause.
|
||||||
@@ -93,8 +96,9 @@ GeoJS answers, one per line:
|
|||||||
`SWWAF_MAX_TRACKED_CLIENTS`. Written every 15 minutes, that is under 2 GiB of
|
`SWWAF_MAX_TRACKED_CLIENTS`. Written every 15 minutes, that is under 2 GiB of
|
||||||
disk writes a day.
|
disk writes a day.
|
||||||
- `bans.json`: about 1.2 KiB per ban, at most about 2.5 KiB, so about 6 MiB at
|
- `bans.json`: about 1.2 KiB per ban, at most about 2.5 KiB, so about 6 MiB at
|
||||||
the default `SWWAF_MAX_BANS`. With a full file, a hundred new bans a day come
|
the default `SWWAF_MAX_BANS`. Written every 15 minutes, a full file comes to
|
||||||
to about 600 MiB of writes.
|
about 600 MiB of writes a day, and a hundred new bans a day add about 600 MiB
|
||||||
|
more.
|
||||||
- `lookups.json`: about 150 bytes per answer, under 1.5 GiB of writes a day when
|
- `lookups.json`: about 150 bytes per answer, under 1.5 GiB of writes a day when
|
||||||
full.
|
full.
|
||||||
- `reputation.json` and `alerts.json`: usually a few MiB or less.
|
- `reputation.json` and `alerts.json`: usually a few MiB or less.
|
||||||
Reference in new issue
Block a user