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
+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"}
```