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
+25 -22
View File
@@ -9,6 +9,7 @@ import (
"net/netip"
"reflect"
"slices"
"strings"
"sync"
"testing"
"time"
@@ -374,7 +375,7 @@ func (c *clock) advance(d time.Duration) {
c.now = c.now.Add(d)
}
// startWithClock starts smallwebwaf in front of an app that answers 200,
// startWithClock starts servewaf, and rproxy in front of an app that answers 200,
// with the settings in env on top of trusting localhost's
// X-Forwarded-For, clients' AS numbers and countries looked up at
// geojsURL, and a clock set to midnight, the start of a bucket in every
@@ -391,18 +392,19 @@ func startWithClock(
addr, out, server := startProxyWithClock(t, app.URL, geojsURL, clk.Now, settings)
return &sender{t: t, addr: addr, out: out}, clk, server
return &sender{t: t, addr: addr, serveWAFAddr: server.Addr, out: out}, clk, server
}
// sender sends requests to smallwebwaf one after another, each on a
// connection of its own, and checks each one's answer and log line. They
// must be the only requests smallwebwaf is sent, since the log lines are
// matched to them in order.
// sender sends requests to rproxy one after another, each on a connection of its
// own, and checks each one's answer and log line. serveWAFAddr is where servewaf's
// own endpoints are, which adminRequest asks. Every request servewaf gets must
// come through the sender, since the log lines are matched to them in order.
type sender struct {
t *testing.T
addr string
out *output
sent int
t *testing.T
addr string
serveWAFAddr string
out *output
sent int
}
// get sends a GET request for / from the client at from.
@@ -419,22 +421,19 @@ func (s *sender) get(from string, status int, action string) logLine {
func (s *sender) request(from, path string, status int, action string) logLine {
s.t.Helper()
line, _ := s.requestWithHeader(from, path, "", status, action)
return line
return s.requestWithHeader(from, path, "", status, action)
}
// requestWithHeader is request with header, such as "Authorization:
// Bearer x", added to the request unless it is "". It returns the body of
// the answer too.
// Bearer x", added to the request unless it is "".
func (s *sender) requestWithHeader(
from, path, header string, status int, action string,
) (logLine, string) {
) logLine {
s.t.Helper()
line, got := s.requestWithBody(http.MethodGet, from, path, header, "", status, action)
line, _ := s.requestWithBody(http.MethodGet, from, path, header, "", status, action)
return line, string(got.body)
return line
}
// requestWithBody is requestWithHeader for a request with method, whose
@@ -450,10 +449,14 @@ func (s *sender) requestWithBody(
header += "\r\n"
}
// rproxy refuses a request with more than one.
if !strings.Contains(header, "User-Agent:") {
header += "User-Agent: " + userAgent + "\r\n"
}
conn := dial(s.t, s.addr)
send(s.t, conn, method+" "+path+" HTTP/1.1\r\nHost: "+appHost+
"\r\nUser-Agent: "+userAgent+"\r\n"+forwardedFor+": "+from+"\r\n"+
header+"\r\n"+body)
send(s.t, conn, method+" "+path+" HTTP/1.1\r\nHost: "+appHost+"\r\n"+
forwardedFor+": "+from+"\r\n"+header+"\r\n"+body)
err := conn.SetReadDeadline(time.Now().Add(waitLimit))
if err != nil {
@@ -478,7 +481,7 @@ func (s *sender) requestWithBody(
got.status, status)
}
line := s.out.requestLines(s.t, s.sent+1)[s.sent]
line := s.out.endedLines(s.t, s.sent+1)[s.sent]
s.sent++
wantLine(s.t, line, status, action)