Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot 235ff0707f Read SWWAF_IPV6_GROUP_PREFIX, SWWAF_MAX_TRACKED_CLIENTS and SWWAF_LOG_LEVEL (closes #112)
check / check (push) Waiting to run
The IPv6 group that is one client, the size of the table of clients and
the level of the process's own lines become settings. clientGroup reads
the group length from them, so limits, bans, history, lookups, AbuseIPDB
scores and per-client anomaly counters all follow it; ratelimit.New
takes the table size; the process logger takes the level once the
settings are read, and request lines, written apart from it, are never
held back.

Judgement call: SWWAF_IPV6_GROUP_PREFIX accepts 32 to 128, the issue's example range.
Judgement call: the log level test picks a free port by listening and closing, since at warn no starting line gives the address.

Model: opus-5-5
2026-10-07 21:22:16 +00:00
3 changed files with 64 additions and 36 deletions
+1 -2
View File
@@ -316,8 +316,7 @@ in `bin/state` unless `SWWAF_STATE_DIR` is set, and the default rule file of
Each setting is an environment variable, or a file one names (see "Settings
given as files" below), and each has a default, so none has to be set. A setting
that is set but invalid stops the start with a message naming it, and the
effective settings are logged at start, unless `SWWAF_LOG_LEVEL` is `warn` or
`error`, which hold that line back.
effective settings are logged at start.
- `SWWAF_LISTEN_ADDR` (default `:8080`): where `smallwebwaf` listens.
- `SWWAF_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app, as `http` or
-19
View File
@@ -399,25 +399,6 @@ func TestAnswers502WhenTheAppCannotBeReached(t *testing.T) {
}
}
func TestLogLevelHoldsBackNoRequestLine(t *testing.T) {
t.Parallel()
// At error the warning that the request to the app failed is held back,
// and is written before the answer is.
addr, out := startProxy(t, "http://"+localhost+":1", map[string]string{
"SWWAF_LOG_LEVEL": "error",
})
wantStatus(t, get(t, addr, "/"), http.StatusBadGateway)
wantLine(t, out.requestLine(t), http.StatusBadGateway, requestlog.ActionUpstreamError)
for _, line := range out.lines(t) {
if line["type"] == "process" {
t.Errorf("process line %v, want none at error", line)
}
}
}
func TestLogsAnAnswerThatBrokeOff(t *testing.T) {
t.Parallel()
+60 -12
View File
@@ -249,16 +249,14 @@ func TestServesUntilToldToStop(t *testing.T) {
out.line(t, "msg", "stopped")
}
func TestLogLevelHoldsBackTheLessSevereProcessLines(t *testing.T) {
func TestLogLevelHoldsBackProcessLinesAndNoRequestLine(t *testing.T) {
t.Parallel()
// A list that cannot be fetched has a warning written once smallwebwaf
// serves, after its starting line.
lists := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}))
t.Cleanup(lists.Close)
// At warn no starting line gives the address, so the test picks one.
// The app cannot be reached, so that a request has a warning written:
// no test can listen on port 1.
addr := freeAddress(t)
url := "http://" + addr + "/"
ctx, stop := context.WithCancel(t.Context())
out := &output{}
@@ -266,15 +264,18 @@ func TestLogLevelHoldsBackTheLessSevereProcessLines(t *testing.T) {
go func() {
exited <- run(ctx, map[string]string{
listenAddr: localhost + ":0",
listenAddr: addr,
upstreamURL: "http://" + localhost + ":1",
stateDir: t.TempDir(),
rulesDir: t.TempDir(),
"SWWAF_BLOCKLIST_URLS": lists.URL + "/tor.txt",
"SWWAF_LOG_LEVEL": "warn",
}, out)
}()
out.line(t, "msg", "fetching a list failed")
waitUntilServing(t, url)
wantStatus(t, url, localhost, http.StatusBadGateway)
out.line(t, "action", "upstream_error")
out.line(t, "msg", "request to the app failed")
stop()
select {
@@ -286,7 +287,8 @@ func TestLogLevelHoldsBackTheLessSevereProcessLines(t *testing.T) {
t.Fatal("still running after being told to stop")
}
// Not one of the info lines from the start to the stop.
// Not one of the info lines from the start to the stop; a request line
// has no level.
for line := range strings.Lines(out.text()) {
var fields map[string]any
@@ -1145,6 +1147,52 @@ func runUntilStopped(
return out
}
// freeAddress returns an address on localhost that nothing listens on as
// it returns, for a test that cannot learn smallwebwaf's own address from
// its starting line.
func freeAddress(t *testing.T) string {
t.Helper()
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
if err != nil {
t.Fatalf("listen: %v", err)
}
addr := listener.Addr().String()
_ = listener.Close()
return addr
}
// waitUntilServing waits until smallwebwaf at url answers its health
// check, for a test that has no starting line to wait for.
func waitUntilServing(t *testing.T, url string) {
t.Helper()
transport := &http.Transport{}
defer transport.CloseIdleConnections()
deadline := time.Now().Add(waitLimit)
for time.Now().Before(deadline) {
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet,
url+"_smallwebwaf/healthz", http.NoBody)
if err != nil {
t.Fatalf("new request: %v", err)
}
res, err := (&http.Client{Transport: transport}).Do(req)
if err == nil {
_ = res.Body.Close()
return
}
time.Sleep(pollInterval)
}
t.Fatalf("no answer at %s after %s", url, waitLimit)
}
// wantStartingLine checks that the line at start gives the version and
// every setting's value.
func wantStartingLine(t *testing.T, line map[string]any, appURL, dir string) {