servewaf: answer rproxy's question about each request, and take its report #170

Closed
opened 2026-11-04 20:09:56 +01:00 by clawbot · 1 comment
Collaborator

Part of #165. The first of three units for the split: this one builds servewaf; rproxy (#171) and the replacement of today's one-process smallwebwaf (#172) follow.

What to build

smallwebwaf servewaf: a command of the same binary that runs today's checks on requests smallwebwaf rproxy asks about, and answers rproxy instead of passing them to an app.

  • It starts as today's process does: the settings, the rule files, the lookup database, the state files, listening at SWWAF_LISTEN_ADDR, and an orderly stop that writes the state files. It passes nothing to an app. The settings it has no use for (SWWAF_UPSTREAM_URL, the size and time limits, SWWAF_TRUSTED_PROXIES) are ignored; the replacement unit removes them.
  • smallwebwaf with no command stays exactly as it is in this unit.

The question

A request carrying X-Smallwebwaf-Client is rproxy asking about one visitor's request. It comes as the visitor sent it: the method, the path and query as sent, Host, the headers, and the first 64 KiB of the body (all of it when shorter). rproxy adds these, after removing any the visitor sent:

  • X-Smallwebwaf-Client: the visitor's address, which rproxy has worked out. servewaf takes it as the client, grouping IPv6 as today.
  • X-Smallwebwaf-Peer: the address of rproxy's own TCP peer, for the log line's peer_ip.
  • X-Smallwebwaf-Content-Length: the body length the visitor announced, absent when it announced none.
  • X-Request-ID: the request's id, which rproxy makes fresh for every request when it arrives (sneak, #165 (comment)). servewaf takes it as given and never makes one; a question without a valid id, or with an id servewaf already holds, gets 400.
  • X-Forwarded-Proto: the scheme.

The answer

Today's checks, from the static lists to the Core Rule Set, in today's order:

  • X-Smallwebwaf-Disposition: allow, status 200, no body. With SWWAF_ADD_LOOKUP_HEADERS it also carries X-Client-ASN and X-Client-Country, which rproxy adds to the request it passes on.
  • X-Smallwebwaf-Disposition: deny, with the status, headers and body the visitor is to get: SWWAF_BAN_RESPONSE, or 403 for a block rule or the Core Rule Set.
  • A question about a path under /_smallwebwaf/ is denied with 404, so that the app never sees that prefix.
  • Observe mode answers allow and logs would_action, as today.

The report

After the visitor's answer has ended, rproxy sends POST /_smallwebwaf/report for each request servewaf answered: a JSON object with the request id, the status the visitor got, the bytes of each body and the times, named as the request log's fields of the same meaning. For a request it allowed, servewaf then counts the bytes toward the client's byte limits at the percentages it gave that request, bans a client that passes one as today, and adds the request with its status to the client's history and the anomaly counters. A denied request's counting is finished at its decision.

servewaf keeps each answered request awaiting its report in a bounded table: only what the report's steps need (the decision's log line and the counting data), no credentials, never the request or response objects, so that a report can never reach a request still being handled. It ignores a report for a request it does not hold.

The log

Three request log lines per request, joined by the request id (sneak, #165 (comment)): rproxy's short line (#171); servewaf's detailed line at the decision, as today's line; and, when the report comes, a third line from servewaf with everything known, its detailed line's fields and rproxy's report together. The repetition is intended. A field says plainly which of servewaf's two lines a line is. Query strings are logged verbatim, nowhere redacted (#177 (comment)).

servewaf's own endpoints

A request without X-Smallwebwaf-Client is for servewaf itself, at its own address on the private network: the health check, the metrics, the bans and clients endpoints (with today's tokens), and the report. Anything else gets 404.

Sites

One servewaf keeps one table of clients, counters and bans for every site that asks it (#168): a client banned through one site is refused on all of them. Its log lines, alerts and ban notes name the site by its host.

Tests

Allow; deny with SWWAF_BAN_RESPONSE's status and body; 403 for a block rule; 404 for /_smallwebwaf/ on a site; observe mode; the lookup headers on an allow; a report counted toward a byte limit that then bans; a report for an unknown request ignored; the third line written with the report's and the decision's fields; a question with a held id refused; a ban made through one host refusing a client on another; servewaf's own endpoints answering only without X-Smallwebwaf-Client.

Documents

SPEC.md gets a section on servewaf, saying the question, the answer and the report as built, which rproxy then follows. docs/settings.md says which settings servewaf uses, docs/request-log.md its log line, README.md the command in a line. The full rewrite around the new arrangement comes with the replacement unit.

Packages

The checks are internal/proxy's, so the question, the answer and the report go there, with its package doc saying it also answers rproxy. The command goes in internal/smallwebwaf, which runs the process.

Model: opus-5-5

Part of https://git.eeqj.de/sneak/smallwebwaf/issues/165. The first of three units for the split: this one builds `servewaf`; `rproxy` (https://git.eeqj.de/sneak/smallwebwaf/issues/171) and the replacement of today's one-process `smallwebwaf` (https://git.eeqj.de/sneak/smallwebwaf/issues/172) follow. ## What to build `smallwebwaf servewaf`: a command of the same binary that runs today's checks on requests `smallwebwaf rproxy` asks about, and answers rproxy instead of passing them to an app. - It starts as today's process does: the settings, the rule files, the lookup database, the state files, listening at `SWWAF_LISTEN_ADDR`, and an orderly stop that writes the state files. It passes nothing to an app. The settings it has no use for (`SWWAF_UPSTREAM_URL`, the size and time limits, `SWWAF_TRUSTED_PROXIES`) are ignored; the replacement unit removes them. - `smallwebwaf` with no command stays exactly as it is in this unit. ## The question A request carrying `X-Smallwebwaf-Client` is rproxy asking about one visitor's request. It comes as the visitor sent it: the method, the path and query as sent, `Host`, the headers, and the first 64 KiB of the body (all of it when shorter). rproxy adds these, after removing any the visitor sent: - `X-Smallwebwaf-Client`: the visitor's address, which rproxy has worked out. servewaf takes it as the client, grouping IPv6 as today. - `X-Smallwebwaf-Peer`: the address of rproxy's own TCP peer, for the log line's `peer_ip`. - `X-Smallwebwaf-Content-Length`: the body length the visitor announced, absent when it announced none. - `X-Request-ID`: the request's id, which rproxy makes fresh for every request when it arrives (sneak, https://git.eeqj.de/sneak/smallwebwaf/issues/165#issuecomment-167908). servewaf takes it as given and never makes one; a question without a valid id, or with an id servewaf already holds, gets `400`. - `X-Forwarded-Proto`: the scheme. ## The answer Today's checks, from the static lists to the Core Rule Set, in today's order: - `X-Smallwebwaf-Disposition: allow`, status `200`, no body. With `SWWAF_ADD_LOOKUP_HEADERS` it also carries `X-Client-ASN` and `X-Client-Country`, which rproxy adds to the request it passes on. - `X-Smallwebwaf-Disposition: deny`, with the status, headers and body the visitor is to get: `SWWAF_BAN_RESPONSE`, or `403` for a block rule or the Core Rule Set. - A question about a path under `/_smallwebwaf/` is denied with `404`, so that the app never sees that prefix. - Observe mode answers `allow` and logs `would_action`, as today. ## The report After the visitor's answer has ended, rproxy sends `POST /_smallwebwaf/report` for each request servewaf answered: a JSON object with the request id, the status the visitor got, the bytes of each body and the times, named as the request log's fields of the same meaning. For a request it allowed, servewaf then counts the bytes toward the client's byte limits at the percentages it gave that request, bans a client that passes one as today, and adds the request with its status to the client's history and the anomaly counters. A denied request's counting is finished at its decision. servewaf keeps each answered request awaiting its report in a bounded table: only what the report's steps need (the decision's log line and the counting data), no credentials, never the request or response objects, so that a report can never reach a request still being handled. It ignores a report for a request it does not hold. ## The log Three request log lines per request, joined by the request id (sneak, https://git.eeqj.de/sneak/smallwebwaf/issues/165#issuecomment-167910): rproxy's short line (https://git.eeqj.de/sneak/smallwebwaf/issues/171); servewaf's detailed line at the decision, as today's line; and, when the report comes, a third line from servewaf with everything known, its detailed line's fields and rproxy's report together. The repetition is intended. A field says plainly which of servewaf's two lines a line is. Query strings are logged verbatim, nowhere redacted (https://git.eeqj.de/sneak/smallwebwaf/issues/177#issuecomment-167904). ## servewaf's own endpoints A request without `X-Smallwebwaf-Client` is for servewaf itself, at its own address on the private network: the health check, the metrics, the bans and clients endpoints (with today's tokens), and the report. Anything else gets `404`. ## Sites One servewaf keeps one table of clients, counters and bans for every site that asks it (https://git.eeqj.de/sneak/smallwebwaf/issues/168): a client banned through one site is refused on all of them. Its log lines, alerts and ban notes name the site by its host. ## Tests Allow; deny with `SWWAF_BAN_RESPONSE`'s status and body; `403` for a block rule; `404` for `/_smallwebwaf/` on a site; observe mode; the lookup headers on an allow; a report counted toward a byte limit that then bans; a report for an unknown request ignored; the third line written with the report's and the decision's fields; a question with a held id refused; a ban made through one host refusing a client on another; servewaf's own endpoints answering only without `X-Smallwebwaf-Client`. ## Documents `SPEC.md` gets a section on `servewaf`, saying the question, the answer and the report as built, which rproxy then follows. `docs/settings.md` says which settings servewaf uses, `docs/request-log.md` its log line, `README.md` the command in a line. The full rewrite around the new arrangement comes with the replacement unit. ## Packages The checks are `internal/proxy`'s, so the question, the answer and the report go there, with its package doc saying it also answers rproxy. The command goes in `internal/smallwebwaf`, which runs the process. Model: opus-5-5
clawbot self-assigned this 2026-11-04 20:09:56 +01:00
Author
Collaborator

Built in #180, awaiting review.

Model: opus-5-5

Built in https://git.eeqj.de/sneak/smallwebwaf/pulls/180, awaiting review. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/smallwebwaf#170