rproxy, the proxy in front of each app, asks servewaf and fails open (closes #171)
check / check (push) Successful in 4m26s

`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 limits. Any
other answer goes to the visitor as servewaf gave it. SWRP_DEFAULT_DISPOSITION
decides only when servewaf cannot be connected to, does not answer at all, or
does not answer within SWRP_TIMEOUT_MS. Only the methods SPEC.md lists pass on;
any other gets 405.

Duplication: internal/rproxy copies passing-on code from internal/proxy until
#172 removes the originals.
Rule suppressed: gosec G704 and G705; paralleltest on one test.
Judgement call: an answer over 1 MiB, or with a status below 100, gets 502.
Judgement call: every target not starting with one / gets 400.

Model: opus-5-5
This commit is contained in:
2026-11-05 03:07:45 +00:00
parent 2eeea136e8
commit 33b8d81be1
23 changed files with 5869 additions and 59 deletions
+44 -2
View File
@@ -1,6 +1,7 @@
// Package requestlog writes the lines smallwebwaf prints on stdout: one
// JSON object per request, or two from servewaf, marked "type":"request", and
// the process's own messages as JSON lines marked "type":"process".
// JSON object per request, two from servewaf and a short one from rproxy, marked
// "type":"request", and the process's own messages as JSON lines marked
// "type":"process".
package requestlog
import (
@@ -193,10 +194,51 @@ type Line struct {
DurationUpstreamTotal *float64 `json:"duration_upstream_total,omitempty"`
}
// RProxyLine is rproxy's short line in the request log about one request. The
// field names, and their order, are those of docs/request-log.md. A field that
// does not apply to a request is left out of its line.
//
//nolint:tagliatelle // docs/request-log.md names the fields in snake_case
type RProxyLine struct {
Type string `json:"type"`
Time string `json:"time"`
RequestID string `json:"request_id"`
ClientIP string `json:"client_ip"`
Host string `json:"host"`
Method string `json:"method"`
Path string `json:"path"`
Query string `json:"query"`
Status int `json:"status"`
RequestBytes int64 `json:"request_bytes"`
ResponseBytes int64 `json:"response_bytes"`
// DurationRequest runs in milliseconds from the request's headers being read
// until the app has been sent the whole request, or the answer has started
// if that is sooner. DurationResponse runs from then until the answer ends.
DurationRequest float64 `json:"duration_request"`
DurationResponse float64 `json:"duration_response"`
// Disposition is the valid disposition servewaf answered or, while
// DefaultDisposition is true, the value of SWRP_DEFAULT_DISPOSITION. It is ""
// when neither gave one.
Disposition string `json:"disposition,omitempty"`
DefaultDisposition bool `json:"default_disposition,omitempty"`
}
// Write writes line to w as one JSON line marked "type":"request".
func Write(w io.Writer, line *Line) error {
line.Type = "request"
return writeJSONLine(w, line)
}
// WriteRProxyLine writes line to w as one JSON line marked "type":"request".
func WriteRProxyLine(w io.Writer, line *RProxyLine) error {
line.Type = "request"
return writeJSONLine(w, line)
}
// writeJSONLine writes line to w as one JSON line.
func writeJSONLine(w io.Writer, line any) error {
encoded, err := json.Marshal(line)
if err != nil {
return fmt.Errorf("encode the request log line: %w", err)