rproxy and servewaf replace the one-process smallwebwaf (closes #172, closes #165)
check / check (push) Failing after 23m49s

`smallwebwaf` with no command now prints the commands and exits with status 2.
The image runs rproxy, or servewaf with SMALLWEBWAF_COMMAND=servewaf. The
passing-on code, its settings and its log fields and metric go. internal/proxy
keeps the checks, answers, reports and own endpoints; its tests run through the
real rproxy. The documents describe the pair.

Judgement call: clients.json keeps token_refused, since state files refuse unknown fields.
Deviation: script/gitea-requests sets SWWAF_LOG_REQUEST_HEADERS, to label requests.

Model: opus-5-5
This commit is contained in:
2026-11-05 10:33:24 +00:00
committed by sneak
parent 17c5dd2438
commit ac1a69e863
71 changed files with 2653 additions and 5493 deletions
+19 -52
View File
@@ -84,26 +84,12 @@ func startServeWAFWith(
}
maps.Copy(settings, env)
server, out, queue := newServerWith(t, proxy.NewServeWAF,
"http://127.0.0.1:8081", geojsURL, clk.Now, settings)
server, out, queue := newServeWAF(t, geojsURL, clk.Now, settings)
change(server)
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
if err != nil {
t.Fatalf("listen: %v", err)
}
go func() {
_ = server.Serve(listener)
}()
t.Cleanup(func() {
_ = server.Close()
})
serve(t, server)
return &serveWAF{
t: t, addr: listener.Addr().String(), out: out, server: server,
clock: clk, queue: queue,
t: t, addr: server.Addr, out: out, server: server, clock: clk, queue: queue,
}
}
@@ -626,13 +612,10 @@ func TestQuestionWhoseBodyStopsArrivingGetsNoAnswer(t *testing.T) {
wantProcessMessages(t, s, "a question's body stopped arriving", 1)
wantNoRequestLines(t, s)
// The id was not taken, and the metrics count nothing for it.
// The id was not taken.
again := s.question(siteA, client, "/")
again.Header.Set(requestIDHeader, "an-id")
wantDisposition(t, do(t, again), http.StatusOK, proxy.DispositionAllow)
wantNoSeries(t, s.metrics(),
`smallwebwaf_size_and_time_limit_hits_total{instance="`+alertInstance+
`",limit="SWWAF_CLIENT_REQUEST_TIMEOUT"}`)
})
}
}
@@ -1466,16 +1449,10 @@ func TestOwnEndpointsLinesDoNotSayWhichOfTheTwoTheyAre(t *testing.T) {
}
}
func TestSettingsOfTheLimitsAndTheProxiesAreIgnored(t *testing.T) {
func TestALargeBodyAnnouncedAndLongHeadersAreNoReasonToRefuse(t *testing.T) {
t.Parallel()
s := startServeWAF(t, "", map[string]string{
requestMaxBytes: "1",
clientHeaderMaxBytes: "5K",
clientRequestTimeout: off,
clientResponseTimeout: off,
trustedProxies: localhost,
})
s := startServeWAF(t, "", nil)
req := s.questionWith(http.MethodPost, siteA, client, "/",
strings.NewReader("a body"))
@@ -1493,33 +1470,23 @@ func TestSettingsOfTheLimitsAndTheProxiesAreIgnored(t *testing.T) {
}
}
func TestShortClientResponseTimeoutIsIgnored(t *testing.T) {
func TestLogLevelHoldsBackNoRequestLine(t *testing.T) {
t.Parallel()
// The setting would leave no time to send any answer.
s := startServeWAF(t, "", map[string]string{
clientResponseTimeout: time.Nanosecond.String(), denyNets: client,
})
s := startServeWAF(t, "", map[string]string{"SWWAF_LOG_LEVEL": "error"})
wantDisposition(t, s.ask(client, "/"), http.StatusForbidden, proxy.DispositionDeny)
wantStatus(t, get(t, s.addr, proxy.HealthPath), http.StatusOK)
}
// A refused question would write a process warning, held back at error.
req := s.question(siteA, client, "/")
req.Header.Del(requestIDHeader)
wantStatus(t, do(t, req), http.StatusBadRequest)
func TestShortClientRequestTimeoutIsIgnored(t *testing.T) {
t.Parallel()
s.allowed(client)
wantLine(t, s.out.requestLine(t), http.StatusOK, requestlog.ActionForward)
// The setting would have run out while the Core Rule Set read the body. Go's
// server would then take the connection for gone, and the line say aborted.
s := startServeWAF(t, "", map[string]string{
clientRequestTimeout: time.Nanosecond.String(), wafMode: block, wafBodyLimit: "1M",
})
req := s.questionWith(http.MethodPost, siteA, client, "/", strings.NewReader("a=1"))
req.Header.Set("Content-Type", formData)
wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow)
if line := s.out.requestLine(t); line.Aborted {
t.Errorf("log line says aborted: %+v", line.Line)
for _, line := range s.out.lines(t) {
if line["type"] == "process" {
t.Errorf("process line %v, want none at error", line)
}
}
}
@@ -1573,7 +1540,7 @@ func TestCoreRuleSetReadsTheBodyAQuestionCarries(t *testing.T) {
t.Parallel()
s := startServeWAF(t, "", map[string]string{
wafMode: block, wafBodyLimit: "1M",
wafMode: block, wafBodyLimit: "64K",
})
ask := func(contentType, body string) answer {