Rename discriminator to waf everywhere (closes #312)
check / check (push) Failing after 2m11s
check / check (push) Failing after 2m11s
The command is now `sww waf`, and the package is `internal/waf`. Types, functions, files, tests, comments, docs, the Dockerfile, the runit script, `script/`, compose examples, the log field `waf_line` and the `X-From-Waf` test header follow. No behaviour changes, and no setting changes name: rproxy already finds the waf through `SWRP_WAF_BACKEND`. Done with one `sed -i` (Discriminator and DISCRIMINATOR to WAF, discriminator to waf) over the files `git grep -i discriminator` listed, as sneak allowed for this rename, then `git mv` for the package and files. By hand afterwards: the header's spelling, which the linter requires, and the padding after `waf` in `sww`'s usage text. Model: opus-5-5
This commit is contained in:
1 parent
ad36dd1775
commit
d31a052aff
89 files changed
+1454
-1489
No files matched your search
+17
-17
@@ -1,8 +1,8 @@
|
||||
# Admin endpoints
|
||||
|
||||
The endpoints under `/_sww/`, what each takes and what each answers.
|
||||
`sww discriminator` answers them at its own address, on the private network.
|
||||
"Admin endpoints" in [`SPEC.md`](../SPEC.md) says how their tokens work.
|
||||
The endpoints under `/_sww/`, what each takes and what each answers. `sww waf`
|
||||
answers them at its own address, on the private network. "Admin endpoints" in
|
||||
[`SPEC.md`](../SPEC.md) says how their tokens work.
|
||||
|
||||
- `GET /_sww/healthz`: `200` and `ok`, to anyone.
|
||||
- `GET /_sww/metrics`: the metrics, for `SWW_METRICS_TOKEN` (see
|
||||
@@ -20,10 +20,10 @@ The endpoints under `/_sww/`, what each takes and what each answers.
|
||||
- one that names an IPv4-mapped netblock, or an address with a zone.
|
||||
- `DELETE /_sww/bans/<client>`: lifts every active ban covering the address
|
||||
`<client>`. It answers `404` when none is active.
|
||||
- `GET /_sww/clients/<ip>`: what `discriminator` knows of the client. Under
|
||||
`client`, its counters and history as `clients.json` holds them, or `null`
|
||||
when `clients.json` does not hold it. Under `bans`, every ban covering the
|
||||
address, with its notes.
|
||||
- `GET /_sww/clients/<ip>`: what `waf` knows of the client. Under `client`, its
|
||||
counters and history as `clients.json` holds them, or `null` when
|
||||
`clients.json` does not hold it. Under `bans`, every ban covering the address,
|
||||
with its notes.
|
||||
- `POST /_sww/report`: takes `rproxy`'s report, without a token. It answers
|
||||
`204`, or `400` to a body that is no report (see "The report" in
|
||||
[`SPEC.md`](../SPEC.md)).
|
||||
@@ -32,29 +32,29 @@ No check runs on these requests. A missing or wrong token is answered `401`. An
|
||||
endpoint whose token is unset answers `404`, as does any other path.
|
||||
|
||||
`sww rproxy` answers only the health check of these, at its own address. It asks
|
||||
`discriminator` about any other request under `/_sww/`. One that passes the
|
||||
checks is answered `404`, so that the app never sees that prefix (see "`rproxy`"
|
||||
in [`SPEC.md`](../SPEC.md)).
|
||||
`waf` about any other request under `/_sww/`. One that passes the checks is
|
||||
answered `404`, so that the app never sees that prefix (see "`rproxy`" in
|
||||
[`SPEC.md`](../SPEC.md)).
|
||||
|
||||
## Examples
|
||||
|
||||
With `SWW_ADMIN_TOKEN` set, the token in `$TOKEN`, and `discriminator` reached
|
||||
as `discriminator:8080` on its network:
|
||||
With `SWW_ADMIN_TOKEN` set, the token in `$TOKEN`, and `waf` reached as
|
||||
`waf:8080` on its network:
|
||||
|
||||
```sh
|
||||
# Every ban.
|
||||
curl -H "Authorization: Bearer $TOKEN" http://discriminator:8080/_sww/bans
|
||||
curl -H "Authorization: Bearer $TOKEN" http://waf:8080/_sww/bans
|
||||
|
||||
# Ban 203.0.113.0/24 for seven days.
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
--json '{"netblock": "203.0.113.0/24", "duration": "7d", "reason": "probes for logins"}' \
|
||||
http://discriminator:8080/_sww/bans
|
||||
http://waf:8080/_sww/bans
|
||||
|
||||
# Lift the bans on 203.0.113.9.
|
||||
curl -H "Authorization: Bearer $TOKEN" -X DELETE \
|
||||
http://discriminator:8080/_sww/bans/203.0.113.9
|
||||
http://waf:8080/_sww/bans/203.0.113.9
|
||||
|
||||
# What discriminator knows of 203.0.113.9.
|
||||
# What waf knows of 203.0.113.9.
|
||||
curl -H "Authorization: Bearer $TOKEN" \
|
||||
http://discriminator:8080/_sww/clients/203.0.113.9
|
||||
http://waf:8080/_sww/clients/203.0.113.9
|
||||
```
|
||||
+6
-7
@@ -6,7 +6,7 @@ back or dropped.
|
||||
|
||||
## Events
|
||||
|
||||
- `ban`: a ban `discriminator` made.
|
||||
- `ban`: a ban `waf` made.
|
||||
- `permanent_ban`: a permanent ban it made, or an existing ban made permanent by
|
||||
a request.
|
||||
- `core_rule_set_match`: the Core Rule Set scored a request at or over the
|
||||
@@ -34,7 +34,7 @@ on one line:
|
||||
|
||||
```json
|
||||
{
|
||||
"instance": "fsn1/discriminator",
|
||||
"instance": "fsn1/waf",
|
||||
"time": "2026-10-06T12:00:50.123461Z",
|
||||
"event": "ban",
|
||||
"host": "app.example",
|
||||
@@ -79,9 +79,8 @@ on one line:
|
||||
|
||||
- `instance`: `SWW_INSTANCE_NAME`. `time`: when the alert was raised. `event`:
|
||||
one of the events above.
|
||||
- `host`: the site of the request that raised the alert, since one
|
||||
`discriminator` serves many sites. It is left out of `source_failure`,
|
||||
`file_error` and `summary`.
|
||||
- `host`: the site of the request that raised the alert, since one `waf` serves
|
||||
many sites. It is left out of `source_failure`, `file_error` and `summary`.
|
||||
- `client`: the address whose request raised the alert. Empty for
|
||||
`source_failure`, `file_error` and `summary`.
|
||||
- `netblock`: empty for `source_failure`, `file_error` and `summary`; otherwise
|
||||
@@ -111,8 +110,8 @@ on one line:
|
||||
## Slack and ntfy
|
||||
|
||||
Slack and ntfy get a message with a title and a text. The title is the instance
|
||||
and the event, such as `fsn1/discriminator: ban`. The text is the `reason`, then
|
||||
one line for each of these that the alert has:
|
||||
and the event, such as `fsn1/waf: ban`. The text is the `reason`, then one line
|
||||
for each of these that the alert has:
|
||||
|
||||
- the host, the client, the netblock and the country;
|
||||
- the file, the source, the error and the mode from `detail`;
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
# What the image holds
|
||||
|
||||
The contents of the `smallwebwaf` image, and how each part is pinned. The image
|
||||
runs `rproxy` by default, or `discriminator` with `SWW_COMMAND=discriminator`.
|
||||
"Deployment" in [`SPEC.md`](../SPEC.md) says why each part is there and why it
|
||||
is pinned that way.
|
||||
runs `rproxy` by default, or `waf` with `SWW_COMMAND=waf`. "Deployment" in
|
||||
[`SPEC.md`](../SPEC.md) says why each part is there and why it is pinned that
|
||||
way.
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -18,8 +18,8 @@ is pinned that way.
|
||||
- The `sww` binary, and the user `sww` (uid and gid 65532).
|
||||
- The service directory `/etc/service/sww`, with the `run` script
|
||||
`share/sww.run`.
|
||||
- `/var/lib/sww` for `discriminator`'s state files, and `/etc/sww/rules.d` with
|
||||
the default rule file `share/rules.d/00-default.rules`.
|
||||
- `/var/lib/sww` for `waf`'s state files, and `/etc/sww/rules.d` with the
|
||||
default rule file `share/rules.d/00-default.rules`.
|
||||
- `EXPOSE 8080`, and the `HEALTHCHECK` that runs `sww healthcheck`.
|
||||
|
||||
## Pins
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
# The metrics
|
||||
|
||||
What `GET /_sww/metrics` answers with, at `discriminator`'s own address.
|
||||
"Metrics endpoint" in [`SPEC.md`](../SPEC.md) describes the endpoint. Prometheus
|
||||
keeps the `instance` label as `exported_instance` unless the scrape sets
|
||||
What `GET /_sww/metrics` answers with, at `waf`'s own address. "Metrics
|
||||
endpoint" in [`SPEC.md`](../SPEC.md) describes the endpoint. Prometheus keeps
|
||||
the `instance` label as `exported_instance` unless the scrape sets
|
||||
`honor_labels: true`. `rproxy` has no metrics.
|
||||
|
||||
Every `sww_*request_bytes_total` and `sww_*response_bytes_total` series counts
|
||||
@@ -15,7 +15,7 @@ body bytes, never headers, whatever `SWW_BYTES_COUNT` says.
|
||||
- `sww_request_duration_seconds` and `sww_upstream_duration_seconds`, as
|
||||
histograms;
|
||||
- `sww_requests_in_flight`: the questions and requests to its own endpoints that
|
||||
`discriminator` is handling.
|
||||
`waf` is handling.
|
||||
|
||||
## Limits and bans
|
||||
|
||||
@@ -78,8 +78,8 @@ By `file`:
|
||||
|
||||
## When a request is counted
|
||||
|
||||
`discriminator` counts a denied request at its decision, and a request to its
|
||||
own endpoints when it is answered. It counts an allowed request when its report
|
||||
`waf` counts a denied request at its decision, and a request to its own
|
||||
endpoints when it is answered. It counts an allowed request when its report
|
||||
comes, with the status, bytes and times the report gives. These are not counted:
|
||||
|
||||
- a report itself;
|
||||
|
||||
+34
-37
@@ -1,32 +1,32 @@
|
||||
# The request log's fields
|
||||
|
||||
The fields of the JSON objects `rproxy` and `discriminator` write about each
|
||||
request. "Request log" and "The log" in [`SPEC.md`](../SPEC.md) say when a line
|
||||
is written and how the log is sent to a syslog server. A field that does not
|
||||
apply to a request is left out.
|
||||
The fields of the JSON objects `rproxy` and `waf` write about each request.
|
||||
"Request log" and "The log" in [`SPEC.md`](../SPEC.md) say when a line is
|
||||
written and how the log is sent to a syslog server. A field that does not apply
|
||||
to a request is left out.
|
||||
|
||||
## `discriminator`'s lines
|
||||
## `waf`'s lines
|
||||
|
||||
`sww discriminator` writes two lines for each question it answers, joined by
|
||||
`request_id`. The field `discriminator_line` says which of the two a line is:
|
||||
`sww waf` writes two lines for each question it answers, joined by `request_id`.
|
||||
The field `waf_line` says which of the two a line is:
|
||||
|
||||
- `decision`: the line it writes once it has answered the question;
|
||||
- `report`: the line it writes when `rproxy`'s report comes, with everything
|
||||
known. The repetition is intended.
|
||||
|
||||
A request to its own endpoints writes one line, without `discriminator_line`. A
|
||||
report writes no line but the `report` line of the request it names.
|
||||
A request to its own endpoints writes one line, without `waf_line`. A report
|
||||
writes no line but the `report` line of the request it names.
|
||||
|
||||
These questions write no line:
|
||||
|
||||
- a question refused with `400` (see "The question" under "`discriminator`" in
|
||||
- a question refused with `400` (see "The question" under "`waf`" in
|
||||
[`SPEC.md`](../SPEC.md));
|
||||
- a question whose body stopped arriving, which gets no answer.
|
||||
|
||||
A `report` line:
|
||||
|
||||
```
|
||||
{"type":"request","discriminator_line":"report","time":"2026-10-03T12:00:00.123Z","instance":"fsn1/discriminator","client_ip":"203.0.113.9","method":"GET","scheme":"https","host":"app.example","path":"/","query":"","protocol":"HTTP/1.1","status":200,"request_bytes":0,"response_bytes":5120,"referer":"","user_agent":"curl/8.9.1","request_id":"7Q2NHZ4KJ3VXW5YB6R3MEFTD2A","peer_ip":"172.18.0.2","forwarded_for":"203.0.113.9","client_group":"203.0.113.9/32","asn":"AS64496","as_name":"Example Net","country":"DE","request_headers":{"accept":"*/*"},"action":"forward","counts":{"minute":1,"hour":12,"day":40,"minute_bytes":5120,"hour_bytes":61440,"day_bytes":204800},"core_rule_set_score":0,"duration_total_ms":3.217,"duration_checks_ms":0.041,"duration_core_rule_set_ms":0.029,"duration_upstream_total_ms":3.104}
|
||||
{"type":"request","waf_line":"report","time":"2026-10-03T12:00:00.123Z","instance":"fsn1/waf","client_ip":"203.0.113.9","method":"GET","scheme":"https","host":"app.example","path":"/","query":"","protocol":"HTTP/1.1","status":200,"request_bytes":0,"response_bytes":5120,"referer":"","user_agent":"curl/8.9.1","request_id":"7Q2NHZ4KJ3VXW5YB6R3MEFTD2A","peer_ip":"172.18.0.2","forwarded_for":"203.0.113.9","client_group":"203.0.113.9/32","asn":"AS64496","as_name":"Example Net","country":"DE","request_headers":{"accept":"*/*"},"action":"forward","counts":{"minute":1,"hour":12,"day":40,"minute_bytes":5120,"hour_bytes":61440,"day_bytes":204800},"core_rule_set_score":0,"duration_total_ms":3.217,"duration_checks_ms":0.041,"duration_core_rule_set_ms":0.029,"duration_upstream_total_ms":3.104}
|
||||
```
|
||||
|
||||
### Every line
|
||||
@@ -41,9 +41,8 @@ Every one of these lines has:
|
||||
|
||||
### Standard web log fields
|
||||
|
||||
- `time`: when `discriminator` had read the question's body, RFC 3339 with
|
||||
milliseconds, in UTC. The `report` line gives the same time as the `decision`
|
||||
line.
|
||||
- `time`: when `waf` had read the question's body, RFC 3339 with milliseconds,
|
||||
in UTC. The `report` line gives the same time as the `decision` line.
|
||||
- `instance`: `SWW_INSTANCE_NAME`.
|
||||
- `client_ip`: the visitor's address, as `rproxy` worked it out.
|
||||
- `method`, `path` and `query`: the visitor's request.
|
||||
@@ -52,9 +51,8 @@ Every one of these lines has:
|
||||
- `host`: the site the visitor asked for.
|
||||
- `protocol`: the question's own, `HTTP/1.1`.
|
||||
- `status`:
|
||||
- on the `decision` line, what `discriminator` answered: `200` for `allow`,
|
||||
the status of `deny`, or `0` for `close`, as the visitor is then sent
|
||||
nothing;
|
||||
- on the `decision` line, what `waf` answered: `200` for `allow`, the status
|
||||
of `deny`, or `0` for `close`, as the visitor is then sent nothing;
|
||||
- on the `report` line, what the visitor got, `0` if nothing.
|
||||
- `request_bytes`:
|
||||
- on the `decision` line, how much of the question's body the Core Rule Set
|
||||
@@ -62,7 +60,7 @@ Every one of these lines has:
|
||||
- on the `report` line, the bytes of the visitor's body that `rproxy` read,
|
||||
with what a WebSocket carried from the visitor.
|
||||
- `response_bytes`:
|
||||
- on the `decision` line, the body of `discriminator`'s answer;
|
||||
- on the `decision` line, the body of `waf`'s answer;
|
||||
- on the `report` line, the bytes of the answer's body, with what a
|
||||
WebSocket carried from the app.
|
||||
- `referer` and `user_agent`.
|
||||
@@ -79,7 +77,7 @@ Every one of these lines has:
|
||||
- lookups are off;
|
||||
- the client is not looked up;
|
||||
- the source cannot place the client;
|
||||
- GeoJS had not answered by the time `discriminator` answered.
|
||||
- GeoJS had not answered by the time `waf` answered.
|
||||
- `content_type`: the visitor's `Content-Type`.
|
||||
- `content_length`: the body length the visitor announced.
|
||||
- `request_headers`: the headers listed in `SWW_LOG_REQUEST_HEADERS`, by name in
|
||||
@@ -89,7 +87,7 @@ Every one of these lines has:
|
||||
|
||||
### Response detail
|
||||
|
||||
- `response_content_type`: the type of `discriminator`'s own answer.
|
||||
- `response_content_type`: the type of `waf`'s own answer.
|
||||
- `aborted`: true when `rproxy` went away before the answer.
|
||||
|
||||
### Decision
|
||||
@@ -105,8 +103,8 @@ Every one of these lines has:
|
||||
- `country_denied`, `client_is_rate_limited`, `blocked_by_block_rule` and
|
||||
`blocked_by_core_rule_set`: refused by that check;
|
||||
- `request_header_too_large`: over a header limit;
|
||||
- `own_endpoint`: answered at one of `discriminator`'s own endpoints, or
|
||||
`404` for a path under `/_sww/`.
|
||||
- `own_endpoint`: answered at one of `waf`'s own endpoints, or `404` for a
|
||||
path under `/_sww/`.
|
||||
- `would_action`: in `observe` mode, what `enforce` mode would have done with a
|
||||
request it would have refused. `action` then names what was done, normally
|
||||
`forward`.
|
||||
@@ -131,9 +129,8 @@ Every one of these lines has:
|
||||
- `core_rule_set`: `score`, and `threshold`, `SWW_WAF_ANOMALY_THRESHOLD`;
|
||||
- `refusal_limit`: `window`, `count` and `limit`, of the error burst;
|
||||
- `byte_limits`: `window`, `count` and `limit`, the count in bytes.
|
||||
- `after_answer_heuristic`: the heuristic that banned the client after
|
||||
`discriminator` answered the request, in `observe` mode too: `refusal_limit`
|
||||
or `byte_limits`.
|
||||
- `after_answer_heuristic`: the heuristic that banned the client after `waf`
|
||||
answered the request, in `observe` mode too: `refusal_limit` or `byte_limits`.
|
||||
- `after_answer_heuristic_figures`: an object of the figures that heuristic
|
||||
acted on, as listed above.
|
||||
|
||||
@@ -191,8 +188,8 @@ Every one of these lines has:
|
||||
All in milliseconds:
|
||||
|
||||
- `duration_total_ms`:
|
||||
- on the `decision` line, from `discriminator` having read the question's
|
||||
body to the line;
|
||||
- on the `decision` line, from `waf` having read the question's body to the
|
||||
line;
|
||||
- on the `report` line, from `rproxy` having read the request's headers to
|
||||
the end of the answer.
|
||||
- `duration_checks_ms`: to the end of the checks;
|
||||
@@ -202,8 +199,8 @@ All in milliseconds:
|
||||
|
||||
### Requests to its own endpoints
|
||||
|
||||
The line of a request to `discriminator`'s own endpoints has these fields, with
|
||||
these meanings where they differ:
|
||||
The line of a request to `waf`'s own endpoints has these fields, with these
|
||||
meanings where they differ:
|
||||
|
||||
- `client_ip` and `peer_ip`: the request's TCP peer.
|
||||
- `request_id`: a new random one of 26 letters and digits.
|
||||
@@ -216,7 +213,7 @@ these meanings where they differ:
|
||||
## `rproxy`'s line
|
||||
|
||||
`sww rproxy` writes one short line for each request. `request_id` joins it to
|
||||
`discriminator`'s two. It has these fields and no others:
|
||||
`waf`'s two. It has these fields and no others:
|
||||
|
||||
- `type`: `request`.
|
||||
- `time`: when the request arrived, RFC 3339 with milliseconds, in UTC.
|
||||
@@ -231,15 +228,15 @@ these meanings where they differ:
|
||||
carried from the app.
|
||||
- `duration_request_ms`: milliseconds from the request's headers being read
|
||||
until the app had been sent the whole request, or the answer started if that
|
||||
was sooner. It includes the wait for `discriminator`.
|
||||
was sooner. It includes the wait for `waf`.
|
||||
- `duration_response_ms`: milliseconds from then until the end of the answer.
|
||||
- `disposition`: what was done with the request: `allow`, `deny` or `close`. It
|
||||
is `discriminator`'s answer, or the value of `SWRP_DEFAULT_DISPOSITION`. It is
|
||||
left out when:
|
||||
- `discriminator` was not asked, as for the health check;
|
||||
- the visitor went away while `discriminator` was being asked;
|
||||
- `discriminator`'s answer had no valid `X-Sww-Disposition`;
|
||||
- `rproxy` could not pass on `discriminator`'s answer, and answered `502`.
|
||||
is `waf`'s answer, or the value of `SWRP_DEFAULT_DISPOSITION`. It is left out
|
||||
when:
|
||||
- `waf` was not asked, as for the health check;
|
||||
- the visitor went away while `waf` was being asked;
|
||||
- `waf`'s answer had no valid `X-Sww-Disposition`;
|
||||
- `rproxy` could not pass on `waf`'s answer, and answered `502`.
|
||||
- `default_disposition`: `true` when `SWRP_DEFAULT_DISPOSITION` gave
|
||||
`disposition`, and otherwise left out.
|
||||
|
||||
|
||||
+4
-6
@@ -54,20 +54,18 @@ empty-agent user_agent log ^$
|
||||
|
||||
## A file of your own
|
||||
|
||||
`discriminator` reads the rule files in its own container. Rules of your own go
|
||||
in a file named to sort after the default, such as `50-site.rules`:
|
||||
`waf` reads the rule files in its own container. Rules of your own go in a file
|
||||
named to sort after the default, such as `50-site.rules`:
|
||||
|
||||
```
|
||||
# 50-site.rules: WordPress probes, which this site never serves
|
||||
wp-probe path ban (?i)^/(wp-login\.php|xmlrpc\.php|wp-admin/)
|
||||
```
|
||||
|
||||
A `Dockerfile` built on the `smallwebwaf` image, for `discriminator` to run
|
||||
from, adds it:
|
||||
A `Dockerfile` built on the `smallwebwaf` image, for `waf` to run from, adds it:
|
||||
|
||||
```dockerfile
|
||||
COPY 50-site.rules /etc/sww/rules.d/50-site.rules
|
||||
```
|
||||
|
||||
Or mount it there. A `path` rule applies on every site behind that
|
||||
`discriminator`.
|
||||
Or mount it there. A `path` rule applies on every site behind that `waf`.
|
||||
+37
-40
@@ -4,9 +4,9 @@ Every setting, its default and the values it takes, in the groups of "Settings"
|
||||
in [`SPEC.md`](../SPEC.md). That section gives the conventions, the settings
|
||||
that take `off`, the `_FILE` form, and what each group does and why.
|
||||
|
||||
The settings that start with `SWW_` are `discriminator`'s. Those that start with
|
||||
`SWRP_` are `rproxy`'s, and are under "`rproxy`" below. `SWW_COMMAND` chooses
|
||||
between the two (see "The image's command" below).
|
||||
The settings that start with `SWW_` are `waf`'s. Those that start with `SWRP_`
|
||||
are `rproxy`'s, and are under "`rproxy`" below. `SWW_COMMAND` chooses between
|
||||
the two (see "The image's command" below).
|
||||
|
||||
## Forms of values
|
||||
|
||||
@@ -28,21 +28,20 @@ between the two (see "The image's command" below).
|
||||
|
||||
## Core
|
||||
|
||||
- `SWW_LISTEN_ADDR` (default `:8080`): where `discriminator` listens: `:` and a
|
||||
port, such as `:9000`. Leave the host out (see "Ports" in
|
||||
[`SPEC.md`](../SPEC.md)).
|
||||
- `SWW_LISTEN_ADDR` (default `:8080`): where `waf` listens: `:` and a port, such
|
||||
as `:9000`. Leave the host out (see "Ports" in [`SPEC.md`](../SPEC.md)).
|
||||
- `SWW_INSTANCE_NAME` (default: the container's host name): the `instance` name
|
||||
in each line `discriminator` logs, and in each alert and metric, such as
|
||||
`fsn1/discriminator`. Valid UTF-8. No control character while
|
||||
`SWW_ALERT_NTFY_URL` is set. 1 to 48 printable ASCII characters without a
|
||||
space while `SWW_LOG_REMOTE_URL` is set and `SWW_LOG_REMOTE_APP_NAME` is not.
|
||||
in each line `waf` logs, and in each alert and metric, such as `fsn1/waf`.
|
||||
Valid UTF-8. No control character while `SWW_ALERT_NTFY_URL` is set. 1 to 48
|
||||
printable ASCII characters without a space while `SWW_LOG_REMOTE_URL` is set
|
||||
and `SWW_LOG_REMOTE_APP_NAME` is not.
|
||||
- `SWW_MODE` (default `enforce`): `enforce` or `observe`.
|
||||
- `SWW_IPV6_GROUP_PREFIX` (default `64`): the prefix length of an IPv6 client's
|
||||
group, from 32 to 128.
|
||||
- `SWW_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a whole
|
||||
number above zero.
|
||||
- `SWW_MAX_BANS` (default `5000`): how many of its own bans `discriminator`
|
||||
keeps, a whole number above zero.
|
||||
- `SWW_MAX_BANS` (default `5000`): how many of its own bans `waf` keeps, a whole
|
||||
number above zero.
|
||||
|
||||
## Tokens
|
||||
|
||||
@@ -62,8 +61,8 @@ between the two (see "The image's command" below).
|
||||
|
||||
## Logging
|
||||
|
||||
- `SWW_LOG_LEVEL` (default `info`): the least severe of `discriminator`'s own
|
||||
messages that are written: `debug`, `info`, `warn` or `error`.
|
||||
- `SWW_LOG_LEVEL` (default `info`): the least severe of `waf`'s own messages
|
||||
that are written: `debug`, `info`, `warn` or `error`.
|
||||
- `SWW_LOG_REQUEST_HEADERS` (default
|
||||
`accept,accept-language,accept-encoding,content-type,origin,range`): the
|
||||
request headers recorded in the log line, in either case. `Host` and
|
||||
@@ -167,8 +166,8 @@ cut off; the client is banned.
|
||||
|
||||
## Request tags
|
||||
|
||||
Every request gets tags from what `discriminator` knows of its client. See
|
||||
"Request tags" in [`SPEC.md`](../SPEC.md).
|
||||
Every request gets tags from what `waf` knows of its client. See "Request tags"
|
||||
in [`SPEC.md`](../SPEC.md).
|
||||
|
||||
- `SWW_TAG_LISTS` (default empty): address lists that each add a tag, as
|
||||
`name=URL` pairs, such as `torexit=https://example.com/tor.txt`. A name is the
|
||||
@@ -188,10 +187,10 @@ Every request gets tags from what `discriminator` knows of its client. See
|
||||
|
||||
- `SWW_ROUTEWATCH_URL` (default unset): the `http` or `https` URL of a
|
||||
routewatch instance. [routewatch](https://git.eeqj.de/sneak/routewatch) is a
|
||||
service that gives an address's announced netblock and origin AS.
|
||||
`discriminator` asks it about each client, in the background, and adds the
|
||||
tags `asn=<number>` and `prefix=<netblock>` from its answer. While it is
|
||||
unset, nothing asks routewatch and no request gets a `prefix=` tag.
|
||||
service that gives an address's announced netblock and origin AS. `waf` asks
|
||||
it about each client, in the background, and adds the tags `asn=<number>` and
|
||||
`prefix=<netblock>` from its answer. While it is unset, nothing asks
|
||||
routewatch and no request gets a `prefix=` tag.
|
||||
|
||||
`SWW_TAG_DENY` and `SWW_TAG_LIMIT_PERCENT` accept tags in either case. A tag
|
||||
that no request can get stops the start. A request can get only these:
|
||||
@@ -377,7 +376,7 @@ Then mount its directory into the container read-only, and name the file:
|
||||
```sh
|
||||
docker run \
|
||||
-v /srv/app/tokens:/etc/sww/tokens:ro \
|
||||
-e SWW_COMMAND=discriminator \
|
||||
-e SWW_COMMAND=waf \
|
||||
-e SWW_METRICS_TOKEN_FILE=/etc/sww/tokens/metrics \
|
||||
...
|
||||
```
|
||||
@@ -391,21 +390,20 @@ A few limits are fixed rather than settings:
|
||||
- at most 1,000 DNS blocklist queries are under way at once;
|
||||
- at most 20,000 anomaly counters are kept;
|
||||
- each alert destination's queue holds at most 1,000 alerts;
|
||||
- `discriminator` holds at most 10,000 answered requests awaiting their reports;
|
||||
- `discriminator` refuses a request with more than 50 cookies or more than 100
|
||||
header fields (see "Answering in time" under "`discriminator`" in
|
||||
[`SPEC.md`](../SPEC.md));
|
||||
- `waf` holds at most 10,000 answered requests awaiting their reports;
|
||||
- `waf` refuses a request with more than 50 cookies or more than 100 header
|
||||
fields (see "Answering in time" under "`waf`" in [`SPEC.md`](../SPEC.md));
|
||||
- `rproxy` takes a request line and headers of at most 32 KiB, and closes a
|
||||
kept-open connection idle for 120 seconds;
|
||||
- `rproxy` reads at most 1 MiB of the body of `discriminator`'s answer, and
|
||||
answers `502` to a longer one.
|
||||
- `rproxy` reads at most 1 MiB of the body of `waf`'s answer, and answers `502`
|
||||
to a longer one.
|
||||
|
||||
## `discriminator`'s answer and `rproxy`'s wait
|
||||
## `waf`'s answer and `rproxy`'s wait
|
||||
|
||||
`SWW_LOOKUP_TIMEOUT` is the longest a question waits on an outside source. Keep
|
||||
it well under `rproxy`'s `SWRP_TIMEOUT_MS`, as the default is. No question waits
|
||||
for `SWW_REPUTATION_TIMEOUT`, since the DNS blocklists and AbuseIPDB are asked
|
||||
in the background (see "Answering in time" under "`discriminator`" in
|
||||
in the background (see "Answering in time" under "`waf`" in
|
||||
[`SPEC.md`](../SPEC.md)).
|
||||
|
||||
## `rproxy`
|
||||
@@ -417,16 +415,15 @@ they do.
|
||||
|
||||
A time here is a whole number of milliseconds above zero, such as `2000`.
|
||||
|
||||
- `SWRP_WAF_BACKEND` (required, no default): `discriminator`'s URL. An `http` or
|
||||
`https` URL with a host and an optional port, and nothing more. `rproxy`
|
||||
speaks HTTP/1.1 to it.
|
||||
- `SWRP_TIMEOUT_MS` (default `2000`): how long `rproxy` waits for
|
||||
`discriminator`'s answer, to a question and to a report. A time; never `off`.
|
||||
- `SWRP_WAF_BACKEND` (required, no default): `waf`'s URL. An `http` or `https`
|
||||
URL with a host and an optional port, and nothing more. `rproxy` speaks
|
||||
HTTP/1.1 to it.
|
||||
- `SWRP_TIMEOUT_MS` (default `2000`): how long `rproxy` waits for `waf`'s
|
||||
answer, to a question and to a report. A time; never `off`.
|
||||
- `SWRP_DEFAULT_DISPOSITION` (default `allow`): what is done with a request when
|
||||
`discriminator` cannot be connected to, does not answer at all, or does not
|
||||
answer within `SWRP_TIMEOUT_MS`. `allow` passes it to the app, and `deny`
|
||||
answers `503`. It does not apply to an answer `discriminator` gives, such as
|
||||
an error.
|
||||
`waf` cannot be connected to, does not answer at all, or does not answer
|
||||
within `SWRP_TIMEOUT_MS`. `allow` passes it to the app, and `deny` answers
|
||||
`503`. It does not apply to an answer `waf` gives, such as an error.
|
||||
- `SWRP_MAX_REQUEST_SIZE` (default `100M`): the largest request body. A size or
|
||||
`off`.
|
||||
- `SWRP_MAX_RESPONSE_SIZE` (default `1G`): the largest response body. A size or
|
||||
@@ -452,8 +449,8 @@ A time here is a whole number of milliseconds above zero, such as `2000`.
|
||||
## The image's command
|
||||
|
||||
`SWW_COMMAND` (default `rproxy`) chooses what the image runs and what its health
|
||||
check checks: `rproxy` or `discriminator` (see "Deployment" in
|
||||
[`SPEC.md`](../SPEC.md)). It cannot be given as a file.
|
||||
check checks: `rproxy` or `waf` (see "Deployment" in [`SPEC.md`](../SPEC.md)).
|
||||
It cannot be given as a file.
|
||||
|
||||
## Where earlier settings went
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ a top-level `version` of 1.
|
||||
## `bans.json`
|
||||
|
||||
Active and past bans: every ban an admin made, and up to `SWW_MAX_BANS` of those
|
||||
`discriminator` made. Per entry:
|
||||
`waf` made. Per entry:
|
||||
|
||||
- `netblock`, `start` and `expires` (`null` for a permanent ban);
|
||||
- `cause`: `attack`, `limit`, `admin` or `crowdsec`;
|
||||
|
||||
Reference in new issue
Block a user