rproxy, the proxy in front of each app, asks servewaf and fails open (closes #171)
check / check (push) Failing after 1m14s

`smallwebwaf rproxy` reads the first 64 KiB of each body, asks servewaf over
HTTP/1.1, and passes what servewaf allows to the app within its own size and
time limits. It reports each request servewaf answered. SWRP_DEFAULT_DISPOSITION
decides when servewaf gives no answer. A request it cannot put to servewaf as
sent, it refuses itself.

Duplication: internal/rproxy copies passing-on code from internal/proxy until
#172 removes the originals.
Rule suppressed: gosec G704 and G705, on servewaf's URL and the visitor's answer.
Rule suppressed: paralleltest, on the test of a switch rproxy refuses.
Judgement call: the SWRP_ size and time limits take off; SWRP_TIMEOUT_MS does not.
Judgement call: every target not starting with one / gets 400.

Model: opus-5-5
This commit is contained in:
2026-11-05 02:11:02 +00:00
parent 2eeea136e8
commit 553bbcbe95
23 changed files with 5516 additions and 57 deletions
+6
View File
@@ -51,3 +51,9 @@ curl -H "Authorization: Bearer $TOKEN" \
`smallwebwaf servewaf` answers these endpoints at its own address. No check runs
on those requests (see "`servewaf`" in [`SPEC.md`](../SPEC.md)).
## `rproxy`
`smallwebwaf rproxy` answers only the health check, at its own address. It asks
`servewaf` about any other request under `/_smallwebwaf/` (see "`rproxy`" in
[`SPEC.md`](../SPEC.md)).
+35 -1
View File
@@ -3,6 +3,7 @@
The fields of the JSON object `smallwebwaf` writes for each request. "Request
log" in [`SPEC.md`](../SPEC.md) says 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.
`smallwebwaf rproxy` writes a shorter line, which "`rproxy`'s line" below gives.
## Example
@@ -12,7 +13,7 @@ sent to a syslog server. A field that does not apply to a request is left out.
## Every line
Every line has:
Every line but `rproxy`'s has:
- `type`: `request` (the process's own messages have `process`);
- the standard web log fields;
@@ -180,3 +181,36 @@ For an allowed request, the report's counting also changes these fields:
- `counts`: the byte totals now include this request's;
- `limit_hit`, `offence` and `ban_expires`: set when the report's bytes broke a
byte limit.
## `rproxy`'s line
`smallwebwaf rproxy` writes one short line for each request. `request_id` joins
it to `servewaf`'s two. It has these fields and no others:
- `type`: `request`.
- `time`: when the request arrived, RFC 3339 with milliseconds, in UTC.
- `request_id`: the id `rproxy` made for the request when it arrived.
- `client_ip`: the visitor's address.
- `host` and `method`.
- `path` and `query`: as the visitor sent them.
- `status`: what the visitor got, `0` if nothing.
- `request_bytes`: the bytes of the visitor's body that `rproxy` read, with what
a WebSocket carried from the visitor.
- `response_bytes`: the bytes of the answer's body, with what a WebSocket
carried from the app.
- `duration_request`: 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 `servewaf`.
- `duration_response`: milliseconds from then until the end of the answer.
- `disposition`: what was done with the request: `allow`, `deny` or `close`. It
is `servewaf`'s answer, or the value of `SWRP_DEFAULT_DISPOSITION`. It is left
out when `servewaf` was not asked, as for the health check, and when the
visitor went away while it was being asked.
- `default_disposition`: `true` when `SWRP_DEFAULT_DISPOSITION` gave
`disposition`, and otherwise left out.
For example:
```
{"type":"request","time":"2026-10-03T12:00:00.123Z","request_id":"7Q2NHZ4KJ3VXW5YB6R3MEFTD2A","client_ip":"203.0.113.9","host":"app.example","method":"GET","path":"/","query":"","status":200,"request_bytes":0,"response_bytes":5120,"duration_request":1.204,"duration_response":3.104,"disposition":"allow"}
```
+40 -2
View File
@@ -340,11 +340,14 @@ 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;
- `servewaf` holds at most 10,000 answered requests awaiting their reports.
- `servewaf` holds at most 10,000 answered requests awaiting their reports;
- `rproxy` takes a request line and headers of at most 32 KiB, closes a
kept-open connection idle for 120 seconds, and reads at most 1 MiB of the body
of `servewaf`'s answer.
## `servewaf`
`smallwebwaf servewaf` uses every setting here but these, which it ignores:
`smallwebwaf servewaf` uses every `SWWAF_` setting but these, which it ignores:
- `SWWAF_UPSTREAM_URL` and `SWWAF_TRUSTED_PROXIES`;
- the five timeouts under "Size and time limits";
@@ -358,3 +361,38 @@ at start still shows them.
request to an app. `SWWAF_WAF_BODY_LIMIT` has an effect up to 64 KiB less one
byte, whatever its value. A question carries no more of a body than 64 KiB (see
"`servewaf`" in [`SPEC.md`](../SPEC.md)).
## `rproxy`
`smallwebwaf rproxy` reads the settings below and no others, and nothing else
reads them. They are read once at start, and an invalid value stops the start.
None can be given as a file, since `rproxy` never touches disk. "`rproxy`" in
[`SPEC.md`](../SPEC.md) says what they do.
A time here is a whole number of milliseconds above zero, such as `2000`.
- `SWRP_WAF_BACKEND` (required, no default): `servewaf`'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 `servewaf`'s
answer, to a question and to a report. A time; never `off`.
- `SWRP_DEFAULT_DISPOSITION` (default `allow`): what is done with a request
`servewaf` gives no answer about. `allow` passes it to the app, and `deny`
answers `503`.
- `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
`off`.
- `SWRP_MAX_REQUEST_TIME_MS` (default `60000`): how long the visitor may take to
send its request line and headers. It then also bounds, from the end of the
headers, the time until the app has been sent the whole request. A time or
`off`.
- `SWRP_MAX_RESPONSE_TIME_MS` (default `1800000`): how long the answer may take,
from the end of the request to its last byte reaching the visitor. A time or
`off`.
- `SWRP_LISTEN_ADDR` (default `:8080`): where `rproxy` listens, such as `:9000`.
- `SWRP_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.
- `SWRP_TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`):
the netblocks whose `X-Forwarded-For` is believed. Set but empty, it trusts
nothing.