From a63b7e4d2d4ededbf6ca18d9435d6e49dc331a0d Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Fri, 6 Nov 2026 20:02:17 +0100 Subject: [PATCH] Rename internal/proxy to internal/servewaf (closes #173) The package passes nothing on since the one-process mode went: it answers rproxy's questions, takes its reports and answers servewaf's own endpoints. It is now named after the command it serves, with `git mv`, and its constructor is `servewaf.New`. `SPEC.md` and the other documents follow. No change of behaviour. Model: opus-5-5 --- README.md | 4 +- SPEC.md | 8 +- internal/rproxy/limits_test.go | 122 +++++------ internal/rproxy/question_test.go | 102 ++++----- internal/rproxy/request.go | 6 +- internal/rproxy/rproxy.go | 10 +- internal/rproxy/rproxy_test.go | 160 +++++++------- internal/rproxy/servewaf.go | 22 +- internal/{proxy => servewaf}/admin.go | 2 +- internal/{proxy => servewaf}/admin_test.go | 44 ++-- internal/{proxy => servewaf}/alerts_test.go | 8 +- .../anomaly_internal_test.go | 2 +- internal/{proxy => servewaf}/anomaly_test.go | 6 +- .../{proxy => servewaf}/answertime_test.go | 56 ++--- internal/{proxy => servewaf}/bans.go | 2 +- internal/{proxy => servewaf}/bans_test.go | 6 +- internal/{proxy => servewaf}/biased.go | 2 +- internal/{proxy => servewaf}/biased_test.go | 12 +- .../{proxy => servewaf}/bytelimits_test.go | 2 +- internal/{proxy => servewaf}/client.go | 2 +- internal/{proxy => servewaf}/coreruleset.go | 2 +- .../{proxy => servewaf}/coreruleset_test.go | 2 +- internal/{proxy => servewaf}/countries.go | 2 +- .../{proxy => servewaf}/countries_test.go | 2 +- internal/{proxy => servewaf}/crowdsec_test.go | 6 +- .../{proxy => servewaf}/errorburst_test.go | 6 +- internal/{proxy => servewaf}/health_test.go | 6 +- internal/{proxy => servewaf}/history_test.go | 10 +- internal/{proxy => servewaf}/lookup.go | 2 +- internal/{proxy => servewaf}/lookup_test.go | 4 +- internal/{proxy => servewaf}/metrics_test.go | 28 +-- internal/{proxy => servewaf}/observe_test.go | 2 +- internal/{proxy => servewaf}/proxy.go | 13 +- internal/{proxy => servewaf}/proxy_test.go | 20 +- .../{proxy => servewaf}/ratelimits_test.go | 2 +- internal/{proxy => servewaf}/reputation.go | 2 +- .../{proxy => servewaf}/reputation_test.go | 14 +- internal/{proxy => servewaf}/request.go | 2 +- .../{proxy => servewaf}/requestlog_test.go | 6 +- .../{proxy => servewaf}/rulefiles_test.go | 2 +- internal/{proxy => servewaf}/rules.go | 2 +- internal/{proxy => servewaf}/servewaf.go | 6 +- internal/{proxy => servewaf}/servewaf_test.go | 203 +++++++++--------- .../{proxy => servewaf}/staticlists_test.go | 2 +- .../{proxy => servewaf}/trappaths_test.go | 2 +- internal/smallwebwaf/healthcheck.go | 4 +- internal/smallwebwaf/rproxy_test.go | 20 +- internal/smallwebwaf/servewaf_test.go | 16 +- internal/smallwebwaf/smallwebwaf.go | 10 +- internal/smallwebwaf/smallwebwaf_test.go | 4 +- 50 files changed, 491 insertions(+), 489 deletions(-) rename internal/{proxy => servewaf}/admin.go (99%) rename internal/{proxy => servewaf}/admin_test.go (88%) rename internal/{proxy => servewaf}/alerts_test.go (98%) rename internal/{proxy => servewaf}/anomaly_internal_test.go (97%) rename internal/{proxy => servewaf}/anomaly_test.go (98%) rename internal/{proxy => servewaf}/answertime_test.go (90%) rename internal/{proxy => servewaf}/bans.go (99%) rename internal/{proxy => servewaf}/bans_test.go (99%) rename internal/{proxy => servewaf}/biased.go (99%) rename internal/{proxy => servewaf}/biased_test.go (98%) rename internal/{proxy => servewaf}/bytelimits_test.go (99%) rename internal/{proxy => servewaf}/client.go (99%) rename internal/{proxy => servewaf}/coreruleset.go (99%) rename internal/{proxy => servewaf}/coreruleset_test.go (99%) rename internal/{proxy => servewaf}/countries.go (97%) rename internal/{proxy => servewaf}/countries_test.go (99%) rename internal/{proxy => servewaf}/crowdsec_test.go (97%) rename internal/{proxy => servewaf}/errorburst_test.go (98%) rename internal/{proxy => servewaf}/health_test.go (86%) rename internal/{proxy => servewaf}/history_test.go (92%) rename internal/{proxy => servewaf}/lookup.go (99%) rename internal/{proxy => servewaf}/lookup_test.go (99%) rename internal/{proxy => servewaf}/metrics_test.go (93%) rename internal/{proxy => servewaf}/observe_test.go (99%) rename internal/{proxy => servewaf}/proxy.go (94%) rename internal/{proxy => servewaf}/proxy_test.go (97%) rename internal/{proxy => servewaf}/ratelimits_test.go (99%) rename internal/{proxy => servewaf}/reputation.go (99%) rename internal/{proxy => servewaf}/reputation_test.go (98%) rename internal/{proxy => servewaf}/request.go (99%) rename internal/{proxy => servewaf}/requestlog_test.go (98%) rename internal/{proxy => servewaf}/rulefiles_test.go (99%) rename internal/{proxy => servewaf}/rules.go (99%) rename internal/{proxy => servewaf}/servewaf.go (99%) rename internal/{proxy => servewaf}/servewaf_test.go (87%) rename internal/{proxy => servewaf}/staticlists_test.go (99%) rename internal/{proxy => servewaf}/trappaths_test.go (99%) diff --git a/README.md b/README.md index d823595..fdd12bc 100644 --- a/README.md +++ b/README.md @@ -689,8 +689,8 @@ aside as `.bad`, and never stops the running process (see health check. - `internal/config`: reads the settings, the one place they are read. - `internal/rproxy`: the reverse proxy in front of one app. -- `internal/proxy`: `servewaf`'s server, which runs the checks and answers its - own endpoints. +- `internal/servewaf`: `servewaf`'s server, which runs the checks and answers + its own endpoints. - `internal/ratelimit`, `internal/bans`, `internal/rules`, `internal/waf`, `internal/lookup`, `internal/reputation`, `internal/anomaly` and `internal/alerts`: one check or source each. diff --git a/SPEC.md b/SPEC.md index d2d6f01..6d5f5f3 100644 --- a/SPEC.md +++ b/SPEC.md @@ -66,10 +66,10 @@ to them. one it does not know, the binary prints the commands and exits with status 2. - `internal/config`: reads the settings, the one place they are read. - - `internal/proxy`: `servewaf`'s server. It runs the checks in `check`, and - holds `servewaf`'s question, answer and report. It answers `servewaf`'s - own endpoints in `answerOwnEndpoint`. It counts bytes, refusals and - anomalies, and writes the log lines. + - `internal/servewaf`: `servewaf`'s server. It runs the checks in `check`, + and holds `servewaf`'s question, answer and report. It answers + `servewaf`'s own endpoints in `answerOwnEndpoint`. It counts bytes, + refusals and anomalies, and writes the log lines. - `internal/rproxy`: the reverse proxy in front of one app that asks `servewaf` about each request. It passes a request to the app with the standard library's `httputil.ReverseProxy`, within the time and size diff --git a/internal/rproxy/limits_test.go b/internal/rproxy/limits_test.go index b8df0f8..0c5073b 100644 --- a/internal/rproxy/limits_test.go +++ b/internal/rproxy/limits_test.go @@ -16,9 +16,9 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/config" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" "sneak.berlin/go/smallwebwaf/internal/rproxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // post is the start of a POST whose headers end with what follows it. @@ -71,11 +71,11 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceServeWAFAllowsIt(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(http.ResponseWriter, *http.Request) { t.Error("the app was asked") }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestSize: tc.limit, }) @@ -85,11 +85,11 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceServeWAFAllowsIt(t *testing.T) { t.Errorf("answered %+v, want 413", got) } - if asked := servewaf.question(t); len(asked.body) != tc.asked { + if asked := standIn.question(t); len(asked.body) != tc.asked { t.Errorf("the question carries %d bytes, want %d", len(asked.body), tc.asked) } - report := servewaf.report(t) + report := standIn.report(t) if report.Status != http.StatusRequestEntityTooLarge { t.Errorf("report %+v, want status 413", report) } @@ -100,9 +100,9 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceServeWAFAllowsIt(t *testing.T) { func TestADenyOutranksTheBodySizeLimit(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestSize: "1K", }) @@ -132,12 +132,12 @@ func TestABodyNotAnnouncedOverItsLimitIsRefused(t *testing.T) { var asked atomic.Bool - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, r *http.Request) { asked.Store(true) greet(w, r) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestSize: tc.limit, }) @@ -165,12 +165,12 @@ func TestAResponseOverItsLimit(t *testing.T) { t.Run("announced, is refused", func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { w.Header().Set(contentLengthHeader, strconv.Itoa(len(large))) _, _ = io.WriteString(w, large) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxResponseSize: "1K", }) @@ -179,7 +179,7 @@ func TestAResponseOverItsLimit(t *testing.T) { t.Errorf("answered %d, want 502", got.status) } - if report := servewaf.report(t); report.Status != http.StatusBadGateway { + if report := standIn.report(t); report.Status != http.StatusBadGateway { t.Errorf("report %+v, want status 502", report) } }) @@ -187,13 +187,13 @@ func TestAResponseOverItsLimit(t *testing.T) { t.Run("not announced, is cut off", func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, large[:1]) _ = http.NewResponseController(w).Flush() _, _ = io.WriteString(w, large[1:]) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxResponseSize: "1K", }) @@ -205,7 +205,7 @@ func TestAResponseOverItsLimit(t *testing.T) { t.Errorf("read %d bytes (%v), want the answer cut off", len(text), err) } - report := servewaf.report(t) + report := standIn.report(t) if report.Status != http.StatusOK || report.ResponseBytes > 1<<10 { t.Errorf("report %+v, want status 200 and at most 1K of the answer", report) } @@ -217,11 +217,11 @@ func TestAHeadWhoseAnswerAnnouncesMoreThanTheResponseLimitIsPassedOn(t *testing. const announced = "2048" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { w.Header().Set(contentLengthHeader, announced) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxResponseSize: "1K", }) @@ -240,9 +240,9 @@ func TestASettingOfOffSetsNoLimit(t *testing.T) { t.Run(setting, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ setting: "off", }) @@ -251,7 +251,7 @@ func TestASettingOfOffSetsNoLimit(t *testing.T) { wantGreeting(t, visit(t, newRequest(t, http.MethodPost, addr, "/", strings.NewReader("a body")))) - if got := get(t, addr, proxy.HealthPath); got.status != http.StatusOK { + if got := get(t, addr, servewaf.HealthPath); got.status != http.StatusOK { t.Errorf("answered %+v, want rproxy's own 200", got) } }) @@ -270,9 +270,9 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: "500", }) @@ -288,11 +288,11 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) { t.Errorf("line %v, want no disposition and 10 bytes", line) } - if len(servewaf.questions) != 0 { + if len(standIn.questions) != 0 { t.Error("servewaf was asked") } - servewaf.wantNoReport(t) + standIn.wantNoReport(t) }) } } @@ -300,9 +300,9 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) { func TestAVisitorThatGoesAwayBeforeTheFirstPartIsNotAsked(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, post+announcedLength(100)+"some of it") @@ -313,7 +313,7 @@ func TestAVisitorThatGoesAwayBeforeTheFirstPartIsNotAsked(t *testing.T) { t.Errorf("line %v, want no answer and no disposition", line) } - if len(servewaf.questions) != 0 { + if len(standIn.questions) != 0 { t.Error("servewaf was asked") } } @@ -334,9 +334,9 @@ func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, tc.request) @@ -364,7 +364,7 @@ func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(t *testing.T) { t.Errorf("line %v, want no answer and no disposition", line) } - if len(servewaf.questions) != 0 { + if len(standIn.questions) != 0 { t.Error("servewaf was asked") } }) @@ -404,11 +404,11 @@ func TestABodyLeftUnreadIsReadOnlyUntilTheRequestTimeLimit(t *testing.T) { announced = 100 * questionBodyMaxBytes ) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) }) - _, _, server := startServer(t, servewaf.URL, app.URL, map[string]string{ + _, _, server := startServer(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: strconv.FormatInt(limit.Milliseconds(), 10), }) @@ -454,9 +454,9 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) - _, _, server := startServer(t, servewaf.URL, app.URL, map[string]string{ + _, _, server := startServer(t, standIn.URL, app.URL, map[string]string{ maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10), }) @@ -484,9 +484,9 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) { func TestAVisitorTooSlowToSendTheRestIsAnswered408(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: "3000", }) @@ -498,11 +498,11 @@ func TestAVisitorTooSlowToSendTheRestIsAnswered408(t *testing.T) { t.Errorf("status %d, want 408", status) } - if asked := servewaf.question(t); len(asked.body) != questionBodyMaxBytes { + if asked := standIn.question(t); len(asked.body) != questionBodyMaxBytes { t.Errorf("the question carries %d bytes", len(asked.body)) } - if report := servewaf.report(t); report.Status != http.StatusRequestTimeout { + if report := standIn.report(t); report.Status != http.StatusRequestTimeout { t.Errorf("report %+v, want status 408", report) } } @@ -553,8 +553,8 @@ func TestAnAppTooSlowToTakeTheRequestIsAnswered504(t *testing.T) { writeSize = 32 << 10 ) - servewaf := startServeWAF(t, allow) - addr, _ := startRProxy(t, servewaf.URL, startAppThatTakesNothing(t), + standIn := startServeWAF(t, allow) + addr, _ := startRProxy(t, standIn.URL, startAppThatTakesNothing(t), map[string]string{maxRequestTime: "3000"}) conn := dial(t, addr) @@ -580,14 +580,14 @@ func TestAnAppTooSlowToAnswerIsAnswered504(t *testing.T) { t.Parallel() release := make(chan struct{}) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(http.ResponseWriter, *http.Request) { <-release }) t.Cleanup(func() { close(release) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxResponseTime: "300", }) @@ -596,7 +596,7 @@ func TestAnAppTooSlowToAnswerIsAnswered504(t *testing.T) { t.Errorf("answered %d, want 504", got.status) } - if report := servewaf.report(t); report.Status != http.StatusGatewayTimeout { + if report := standIn.report(t); report.Status != http.StatusGatewayTimeout { t.Errorf("report %+v, want status 504", report) } } @@ -607,7 +607,7 @@ func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) { const start = "the start" release := make(chan struct{}) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, start) _ = http.NewResponseController(w).Flush() @@ -619,7 +619,7 @@ func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) { // The time limit is long enough for a loaded host to pass on the start of the // answer before it runs out. - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxResponseTime: "3000", }) @@ -642,7 +642,7 @@ func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) { res.StatusCode, body, err, start) } - if report := servewaf.report(t); report.Status != http.StatusOK { + if report := standIn.report(t); report.Status != http.StatusOK { t.Errorf("report %+v, want the status the visitor got", report) } } @@ -656,7 +656,7 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) { writeSize = 32 << 10 ) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { part := make([]byte, writeSize) @@ -669,7 +669,7 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) { }) // The time limit is long enough for a loaded host to start passing on the // answer before it runs out. - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxResponseTime: "3000", }) @@ -684,7 +684,7 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) { t.Errorf("line %v, want status 200 and the answer cut off", line) } - report := servewaf.report(t) + report := standIn.report(t) if report.Status != http.StatusOK || report.ResponseBytes >= bodySize { t.Errorf("report %+v, want status 200 and the answer cut off", report) } @@ -693,15 +693,15 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) { func TestAnAppThatCannotBeReachedIsAnswered502WithAReport(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) - addr, _ := startRProxy(t, servewaf.URL, nowhere, nil) + standIn := startServeWAF(t, allow) + addr, _ := startRProxy(t, standIn.URL, nowhere, nil) got := get(t, addr, "/") if got.status != http.StatusBadGateway { t.Errorf("answered %d, want 502", got.status) } - if report := servewaf.report(t); report.Status != http.StatusBadGateway { + if report := standIn.report(t); report.Status != http.StatusBadGateway { t.Errorf("report %+v, want status 502", report) } } @@ -709,11 +709,11 @@ func TestAnAppThatCannotBeReachedIsAnswered502WithAReport(t *testing.T) { func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, greeting) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: "3000", }) @@ -732,12 +732,12 @@ func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) { t.Parallel() working := make(chan struct{}) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(_ http.ResponseWriter, r *http.Request) { close(working) <-r.Context().Done() }) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n") @@ -773,7 +773,7 @@ func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(t *testing const answer = "the end of a long answer" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, r *http.Request) { _, _ = io.Copy(io.Discard, r.Body) _ = http.NewResponseController(w).Flush() @@ -782,7 +782,7 @@ func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(t *testing _, _ = io.WriteString(w, answer) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: "2000", }) @@ -802,18 +802,18 @@ func TestAVisitorThatStopsSendingGetsAnAnswerRProxyGivesByTheDeadline(t *testing {"a deny", post, http.StatusForbidden}, { "the health check", - "GET " + proxy.HealthPath + " HTTP/1.1\r\nHost: site.example\r\n", + "GET " + servewaf.HealthPath + " HTTP/1.1\r\nHost: site.example\r\n", http.StatusOK, }, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) // The time limit is long enough for a loaded host to read the first part of // the body before it runs out. - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: "3000", }) diff --git a/internal/rproxy/question_test.go b/internal/rproxy/question_test.go index 16cda4e..d1f7784 100644 --- a/internal/rproxy/question_test.go +++ b/internal/rproxy/question_test.go @@ -12,11 +12,11 @@ import ( "strings" "testing" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // questionBodyMaxBytes is the most of a body a question carries. -const questionBodyMaxBytes = proxy.QuestionBodyMaxBytes +const questionBodyMaxBytes = servewaf.QuestionBodyMaxBytes // chunked is the encoding of a body whose length the visitor did not announce. const chunked = "chunked" @@ -29,9 +29,9 @@ const pathGoReencodes = "/some%2Fpath/with%20space|pipe" func TestQuestionIsTheVisitorsRequestWithRProxysHeaders(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) const target = pathGoReencodes + "?b=2&a=1&bad=%zz;x" @@ -42,16 +42,16 @@ func TestQuestionIsTheVisitorsRequestWithRProxysHeaders(t *testing.T) { req.Header.Set(forwardedForHeader, "198.51.100.1") req.Header.Set(forwardedProtoHeader, "gopher") req.Header.Set(requestIDHeader, "the-visitor's") - req.Header.Set(proxy.ClientHeader, "198.51.100.2") - req.Header.Set(proxy.PeerHeader, "198.51.100.3") - req.Header.Set(proxy.ContentLengthHeader, "12345") + req.Header.Set(servewaf.ClientHeader, "198.51.100.2") + req.Header.Set(servewaf.PeerHeader, "198.51.100.3") + req.Header.Set(servewaf.ContentLengthHeader, "12345") // Go's client then sends no User-Agent, so that one in the question would be // Go's own. req.Header["User-Agent"] = nil wantGreeting(t, visit(t, req)) - asked := servewaf.question(t) + asked := standIn.question(t) if asked.method != http.MethodDelete || asked.target != target || asked.host != site { t.Errorf("asked about %s %s at %s", asked.method, asked.target, asked.host) @@ -68,8 +68,8 @@ func TestQuestionIsTheVisitorsRequestWithRProxysHeaders(t *testing.T) { want.Set(forwardedForHeader, "198.51.100.1") want.Set(forwardedProtoHeader, "http") want.Set(requestIDHeader, asked.header.Get(requestIDHeader)) - want.Set(proxy.ClientHeader, localhost) - want.Set(proxy.PeerHeader, localhost) + want.Set(servewaf.ClientHeader, localhost) + want.Set(servewaf.PeerHeader, localhost) if !maps.EqualFunc(asked.header, want, slices.Equal) { t.Errorf("the question's headers are %v, want %v", asked.header, want) @@ -93,14 +93,14 @@ func TestQuestionNamesTheVisitorATrustedProxySpeaksFor(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) forwarded := make(chan http.Header, 1) app := startApp(t, func(w http.ResponseWriter, r *http.Request) { forwarded <- r.Header.Clone() greet(w, r) }) - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ trustedProxies: tc.trusted, }) @@ -109,9 +109,9 @@ func TestQuestionNamesTheVisitorATrustedProxySpeaksFor(t *testing.T) { req.Header.Set(forwardedProtoHeader, "https") wantGreeting(t, visit(t, req)) - asked := servewaf.question(t) - if asked.header.Get(proxy.ClientHeader) != tc.client || - asked.header.Get(proxy.PeerHeader) != localhost || + asked := standIn.question(t) + if asked.header.Get(servewaf.ClientHeader) != tc.client || + asked.header.Get(servewaf.PeerHeader) != localhost || asked.header.Get(forwardedForHeader) != visitor || asked.header.Get(forwardedProtoHeader) != tc.proto { t.Errorf("the question's headers are %v", asked.header) @@ -157,9 +157,9 @@ func TestQuestionNamesTheClientAsTheForwardedAddressesSay(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ trustedProxies: localhost + "/32,10.0.0.0/8", }) @@ -167,7 +167,7 @@ func TestQuestionNamesTheClientAsTheForwardedAddressesSay(t *testing.T) { req.Header[forwardedForHeader] = tc.header wantGreeting(t, visit(t, req)) - if got := servewaf.question(t).header.Get(proxy.ClientHeader); got != tc.client { + if got := standIn.question(t).header.Get(servewaf.ClientHeader); got != tc.client { t.Errorf("the question names %s, want %s", got, tc.client) } }) @@ -197,7 +197,7 @@ func TestFirst64KiBReachServeWAFAndTheWholeBodyTheApp(t *testing.T) { t.Parallel() body := randomBytes(size) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) received := make(chan []byte, 1) app := startApp(t, func(w http.ResponseWriter, r *http.Request) { whole, _ := io.ReadAll(r.Body) @@ -205,7 +205,7 @@ func TestFirst64KiBReachServeWAFAndTheWholeBodyTheApp(t *testing.T) { greet(w, r) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) req := newRequest(t, http.MethodPost, addr, "/", bytes.NewReader(body)) if !announced { @@ -215,7 +215,7 @@ func TestFirst64KiBReachServeWAFAndTheWholeBodyTheApp(t *testing.T) { wantGreeting(t, visit(t, req)) - asked := servewaf.question(t) + asked := standIn.question(t) want := body[:min(size, questionBodyMaxBytes)] wantQuestionBody(t, asked, want, size, announced) @@ -223,7 +223,7 @@ func TestFirst64KiBReachServeWAFAndTheWholeBodyTheApp(t *testing.T) { t.Error("the app did not get the whole body") } - if report := servewaf.report(t); report.RequestBytes != int64(size) { + if report := standIn.report(t); report.RequestBytes != int64(size) { t.Errorf("report %+v, of %d bytes", report, size) } }) @@ -243,7 +243,7 @@ func wantQuestionBody( len(want)) } - values := asked.header.Values(proxy.ContentLengthHeader) + values := asked.header.Values(servewaf.ContentLengthHeader) if announced { if !slices.Equal(values, []string{strconv.Itoa(size)}) || @@ -264,9 +264,9 @@ func wantQuestionBody( func TestDenyNeedsOnlyTheFirstPartOfALargeBody(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) // Far more announced than sent. Go's server does not wait for that much, so // the answer comes at once. @@ -278,7 +278,7 @@ func TestDenyNeedsOnlyTheFirstPartOfALargeBody(t *testing.T) { t.Errorf("status %d, want servewaf's 403", status) } - if report := servewaf.report(t); report.RequestBytes != questionBodyMaxBytes { + if report := standIn.report(t); report.RequestBytes != questionBodyMaxBytes { t.Errorf("report %+v, want the first part's bytes", report) } } @@ -306,8 +306,8 @@ func TestAQuestionCarriesItsBodyWhateverTheMethod(t *testing.T) { t.Run(method+" "+tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) - addr, _ := startRProxy(t, servewaf.URL, nowhere, nil) + standIn := startServeWAF(t, deny) + addr, _ := startRProxy(t, standIn.URL, nowhere, nil) conn := dial(t, addr) send(t, conn, method+" /x HTTP/1.1\r\nHost: site.example\r\n"+tc.framing) @@ -316,7 +316,7 @@ func TestAQuestionCarriesItsBodyWhateverTheMethod(t *testing.T) { t.Errorf("status %d, want servewaf's 403", status) } - asked := servewaf.question(t) + asked := standIn.question(t) if string(asked.body) != tc.body || !slices.Equal(asked.transferEncoding, tc.encoding) { t.Errorf("the question carries %q, encoded %v, want %q, encoded %v", @@ -334,11 +334,11 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) { t.Run(method, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(http.ResponseWriter, *http.Request) { t.Error("the app was asked") }) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) got := visit(t, newRequest(t, method, addr, "/", http.NoBody)) if got.status != http.StatusMethodNotAllowed || @@ -352,11 +352,11 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) { t.Errorf("line %v, want status 405 and no disposition", line) } - if len(servewaf.questions) != 0 { + if len(standIn.questions) != 0 { t.Error("servewaf was asked") } - servewaf.wantNoReport(t) + standIn.wantNoReport(t) }) } } @@ -372,18 +372,18 @@ func TestEachMethodPassedOnReachesServeWAFAndTheApp(t *testing.T) { t.Parallel() reached := make(chan string, 1) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(_ http.ResponseWriter, r *http.Request) { reached <- r.Method }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) got := visit(t, newRequest(t, method, addr, "/", http.NoBody)) if got.status != http.StatusOK { t.Errorf("answered %+v, want the app's 200", got) } - if asked := servewaf.question(t); asked.method != method { + if asked := standIn.question(t); asked.method != method { t.Errorf("servewaf was asked about %s", asked.method) } @@ -416,9 +416,9 @@ func TestARequestThatCannotGoToServeWAFAsSentIsRefused(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, request) @@ -433,11 +433,11 @@ func TestARequestThatCannotGoToServeWAFAsSentIsRefused(t *testing.T) { t.Errorf("line %v, want status 400 and no disposition", line) } - if len(servewaf.questions) != 0 { + if len(standIn.questions) != 0 { t.Error("servewaf was asked") } - servewaf.wantNoReport(t) + standIn.wantNoReport(t) }) } } @@ -447,9 +447,9 @@ func TestAHostWithAPercentSignButNoBracketGoesToServeWAF(t *testing.T) { const host = "a%b" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, "GET / HTTP/1.1\r\nHost: "+host+"\r\n\r\n") @@ -458,7 +458,7 @@ func TestAHostWithAPercentSignButNoBracketGoesToServeWAF(t *testing.T) { t.Errorf("status %d, want the app's 200", status) } - if asked := servewaf.question(t); asked.host != host { + if asked := standIn.question(t); asked.host != host { t.Errorf("asked about the host %q, want %q", asked.host, host) } } @@ -468,15 +468,15 @@ func TestATrustedPeerWithNoForwardedProtoIsAskedAboutAsHTTP(t *testing.T) { const want = "http" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ trustedProxies: localhost + "/32", }) wantGreeting(t, get(t, addr, "/")) - asked := servewaf.question(t) + asked := standIn.question(t) if got := asked.header.Get(forwardedProtoHeader); got != want { t.Errorf("the question's %s is %q, want %s", forwardedProtoHeader, got, want) } @@ -497,9 +497,9 @@ func TestAQuestionNamesNoZone(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) - _, out, server := startServer(t, servewaf.URL, app.URL, map[string]string{ + _, out, server := startServer(t, standIn.URL, app.URL, map[string]string{ trustedProxies: localhost + "/32", }) @@ -509,9 +509,9 @@ func TestAQuestionNamesNoZone(t *testing.T) { server.Handler.ServeHTTP(httptest.NewRecorder(), req) - asked := servewaf.question(t) - if asked.header.Get(proxy.ClientHeader) != tc.client || - asked.header.Get(proxy.PeerHeader) != tc.peer { + asked := standIn.question(t) + if asked.header.Get(servewaf.ClientHeader) != tc.client || + asked.header.Get(servewaf.PeerHeader) != tc.peer { t.Errorf("the question's headers are %v", asked.header) } diff --git a/internal/rproxy/request.go b/internal/rproxy/request.go index b7d39be..cd30dc5 100644 --- a/internal/rproxy/request.go +++ b/internal/rproxy/request.go @@ -16,9 +16,9 @@ import ( "sync/atomic" "time" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" "sneak.berlin/go/smallwebwaf/internal/rules" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // flushAfterEachWrite has ReverseProxy pass on each part of the app's answer as @@ -129,7 +129,7 @@ func (rq *request) readFirstPartOfBody() bool { } _ = rq.rc.SetReadDeadline(rq.requestDeadline()) - held, err := io.ReadAll(io.LimitReader(rq.body, proxy.QuestionBodyMaxBytes)) + held, err := io.ReadAll(io.LimitReader(rq.body, servewaf.QuestionBodyMaxBytes)) // From here on, the request timer ends a read of the rest of the body, so that // the answer can name the side rproxy was waiting on. _ = rq.rc.SetReadDeadline(time.Time{}) @@ -384,7 +384,7 @@ func (rq *request) finish(ctx context.Context) { return } - report := &proxy.Report{ + report := &servewaf.Report{ RequestID: rq.id, Status: min(rq.out.status, reportStatusMax), RequestBytes: requestBytes, diff --git a/internal/rproxy/rproxy.go b/internal/rproxy/rproxy.go index e662104..eb3a225 100644 --- a/internal/rproxy/rproxy.go +++ b/internal/rproxy/rproxy.go @@ -22,7 +22,7 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/config" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // How rproxy keeps connections to the app and to servewaf open between @@ -85,7 +85,7 @@ func New(params Params) *Server { transport: newTransport(), serveWAFTransport: newServeWAFTransport(), reportURL: (&url.URL{ - Scheme: backend.Scheme, Host: backend.Host, Path: proxy.ReportPath, + Scheme: backend.Scheme, Host: backend.Host, Path: servewaf.ReportPath, }).String(), } @@ -166,7 +166,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { rq := h.newRequest(w, r) defer rq.finish(r.Context()) - if r.Method == http.MethodGet && r.URL.Path == proxy.HealthPath { + if r.Method == http.MethodGet && r.URL.Path == servewaf.HealthPath { rq.answerHealth() return @@ -199,9 +199,9 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { rq.noteAnswer(answer) switch rq.disposition { - case proxy.DispositionAllow: + case servewaf.DispositionAllow: rq.passToApp(r.Context()) - case proxy.DispositionClose: + case servewaf.DispositionClose: // This panic has Go's server close the connection without an answer, // and log nothing; the deferred finish still runs. panic(http.ErrAbortHandler) diff --git a/internal/rproxy/rproxy_test.go b/internal/rproxy/rproxy_test.go index 79711e2..b48fb0e 100644 --- a/internal/rproxy/rproxy_test.go +++ b/internal/rproxy/rproxy_test.go @@ -19,9 +19,9 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/config" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" "sneak.berlin/go/smallwebwaf/internal/rproxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -54,7 +54,7 @@ const ( // The headers the tests set and look at. const ( - dispositionHeader = proxy.DispositionHeader + dispositionHeader = servewaf.DispositionHeader forwardedForHeader = "X-Forwarded-For" forwardedProtoHeader = "X-Forwarded-Proto" requestIDHeader = "X-Request-ID" @@ -70,9 +70,9 @@ const ( // What the stand-in for servewaf answers. const ( - dispositionAllow = proxy.DispositionAllow - dispositionDeny = proxy.DispositionDeny - dispositionClose = proxy.DispositionClose + dispositionAllow = servewaf.DispositionAllow + dispositionDeny = servewaf.DispositionDeny + dispositionClose = servewaf.DispositionClose // deniedBody is the body of a deny. deniedBody = "refused by servewaf" ) @@ -173,7 +173,7 @@ type serveWAF struct { *httptest.Server questions chan question - reports chan proxy.Report + reports chan servewaf.Report } // startServeWAF starts a stand-in for servewaf. @@ -182,11 +182,11 @@ func startServeWAF(t *testing.T, answer http.HandlerFunc) *serveWAF { s := &serveWAF{ questions: make(chan question, 10), - reports: make(chan proxy.Report, 10), + reports: make(chan servewaf.Report, 10), } s.Server = httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get(proxy.ClientHeader) == "" { + if r.Header.Get(servewaf.ClientHeader) == "" { s.takeReport(t, w, r) return @@ -213,10 +213,10 @@ func startServeWAF(t *testing.T, answer http.HandlerFunc) *serveWAF { func (s *serveWAF) takeReport(t *testing.T, w http.ResponseWriter, r *http.Request) { t.Helper() - var report proxy.Report + var report servewaf.Report err := json.NewDecoder(r.Body).Decode(&report) - if err != nil || r.Method != http.MethodPost || r.URL.Path != proxy.ReportPath { + if err != nil || r.Method != http.MethodPost || r.URL.Path != servewaf.ReportPath { t.Errorf("%s %s is no report: %v", r.Method, r.URL.Path, err) } @@ -237,7 +237,7 @@ func (s *serveWAF) wantNoReport(t *testing.T) { } // report waits for a report. -func (s *serveWAF) report(t *testing.T) proxy.Report { +func (s *serveWAF) report(t *testing.T) servewaf.Report { t.Helper() select { @@ -246,7 +246,7 @@ func (s *serveWAF) report(t *testing.T) proxy.Report { case <-time.After(waitLimit): t.Fatal("no report") - return proxy.Report{} + return servewaf.Report{} } } @@ -273,7 +273,7 @@ func startServeWAFAnsweringReports( backend := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get(proxy.ClientHeader) != "" { + if r.Header.Get(servewaf.ClientHeader) != "" { answer(w, r) return @@ -498,7 +498,7 @@ func readStatus(t *testing.T, conn net.Conn) int { func TestPassesAnAllowedRequestToTheAppAndItsAnswerBack(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(w http.ResponseWriter, r *http.Request) { + standIn := startServeWAF(t, func(w http.ResponseWriter, r *http.Request) { w.Header().Set(asnHeader, "AS64496") w.Header().Set(countryHeader, "DE") allow(w, r) @@ -512,7 +512,7 @@ func TestPassesAnAllowedRequestToTheAppAndItsAnswerBack(t *testing.T) { w.WriteHeader(http.StatusTeapot) _, _ = io.WriteString(w, greeting) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) const target = pathGoReencodes + "?b=2&a=1&bad=%zz;x" @@ -530,7 +530,7 @@ func TestPassesAnAllowedRequestToTheAppAndItsAnswerBack(t *testing.T) { } received := <-seen - asked := servewaf.question(t) + asked := standIn.question(t) if received.RequestURI != target || received.Host != site { t.Errorf("the app saw %s for %s", received.RequestURI, received.Host) @@ -557,14 +557,14 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) { "X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip", } - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) seen := make(chan http.Header, 1) app := startApp(t, func(w http.ResponseWriter, r *http.Request) { seen <- r.Header.Clone() greet(w, r) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ trustedProxies: "10.0.0.0/8", }) @@ -602,14 +602,14 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) { func TestATrustedProxysForwardedHeadersPassOn(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) seen := make(chan http.Header, 1) app := startApp(t, func(w http.ResponseWriter, r *http.Request) { seen <- r.Header.Clone() greet(w, r) }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ trustedProxies: localhost + "/32", }) @@ -641,12 +641,12 @@ func TestATrustedProxysForwardedHeadersPassOn(t *testing.T) { func TestAnAnswerWithoutAContentTypeGetsNone(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { w.Header()["Content-Type"] = nil _, _ = io.WriteString(w, "") }) - _, _, server := startServer(t, servewaf.URL, app.URL, nil) + _, _, server := startServer(t, standIn.URL, app.URL, nil) req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody) w := httptest.NewRecorder() @@ -704,11 +704,11 @@ func TestRemovesTheVisitorsLookupHeadersWhenServeWAFGivesNone(t *testing.T) { func TestDenyGivesTheVisitorServeWAFsAnswer(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, func(http.ResponseWriter, *http.Request) { t.Error("the app was asked") }) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) got := get(t, addr, "/") if got.status != http.StatusForbidden || got.body != deniedBody || @@ -720,7 +720,7 @@ func TestDenyGivesTheVisitorServeWAFsAnswer(t *testing.T) { t.Errorf("the visitor got %s", dispositionHeader) } - report := servewaf.report(t) + report := standIn.report(t) if report.Status != http.StatusForbidden || report.ResponseBytes != int64(len(deniedBody)) || report.DurationUpstreamTotal != 0 { @@ -736,9 +736,9 @@ func TestDenyGivesTheVisitorServeWAFsAnswer(t *testing.T) { func TestADeniedHeadKeepsServeWAFsContentLength(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) got := visit(t, newRequest(t, http.MethodHead, addr, "/", http.NoBody)) if got.status != http.StatusForbidden || @@ -758,15 +758,15 @@ func TestAnAnswerRProxyHoldsWholeIsSentBeforeItsLogLine(t *testing.T) { }{ {"a deny", "GET /" + host, deniedBody, http.StatusForbidden}, {"rproxy's own answer", "GET x:admin" + host, "Bad Request\n", http.StatusBadRequest}, - {"the health check", "GET " + proxy.HealthPath + host, "ok\n", http.StatusOK}, + {"the health check", "GET " + servewaf.HealthPath + host, "ok\n", http.StatusOK}, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() log := heldLog{release: make(chan struct{})} - servewaf := startServeWAF(t, deny) + standIn := startServeWAF(t, deny) app := startApp(t, greet) - addr, _ := startServerLogging(t, servewaf.URL, app.URL, nil, log, io.Discard) + addr, _ := startServerLogging(t, standIn.URL, app.URL, nil, log, io.Discard) t.Cleanup(func() { close(log.release) }) @@ -794,9 +794,9 @@ func TestAnAnswerRProxyHoldsWholeIsSentBeforeItsLogLine(t *testing.T) { func TestRProxysOwnAnswerIsPlainText(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, "GET x:admin HTTP/1.1\r\nHost: site.example\r\n\r\n") @@ -817,14 +817,14 @@ func TestRProxysOwnAnswerIsPlainText(t *testing.T) { func TestCloseClosesTheConnectionWithoutAnAnswer(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { + standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { w.Header().Set(dispositionHeader, dispositionClose) w.WriteHeader(http.StatusOK) }) app := startApp(t, func(http.ResponseWriter, *http.Request) { t.Error("the app was asked") }) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n") @@ -835,7 +835,7 @@ func TestCloseClosesTheConnectionWithoutAnAnswer(t *testing.T) { text, err) } - report := servewaf.report(t) + report := standIn.report(t) if report.Status != 0 || report.ResponseBytes != 0 { t.Errorf("report %+v, of a visitor sent nothing", report) } @@ -935,7 +935,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) { t.Run(tc.name+" with the default "+setting, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { + standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { if tc.disposition != "" { w.Header().Set(dispositionHeader, tc.disposition) } @@ -947,7 +947,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) { app := startApp(t, func(http.ResponseWriter, *http.Request) { t.Error("the app was asked") }) - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ defaultDisposition: setting, }) @@ -977,11 +977,11 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) { func TestAnAnswerWithoutAValidDispositionGetsNoReport(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { + standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "refused", http.StatusBadRequest) }) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) if got := get(t, addr, "/"); got.status != http.StatusBadRequest { t.Errorf("answered %+v, want servewaf's 400", got) @@ -991,18 +991,18 @@ func TestAnAnswerWithoutAValidDispositionGetsNoReport(t *testing.T) { t.Errorf("line %v, want no disposition", line) } - servewaf.wantNoReport(t) + standIn.wantNoReport(t) } func TestAnAnswerWithoutAContentTypeGoesToTheVisitorWithNone(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { + standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { w.Header()["Content-Type"] = nil w.WriteHeader(http.StatusInternalServerError) _, _ = io.WriteString(w, "broken") }) - addr, _ := startRProxy(t, servewaf.URL, nowhere, nil) + addr, _ := startRProxy(t, standIn.URL, nowhere, nil) got := get(t, addr, "/") if contentType, sent := got.header["Content-Type"]; sent { @@ -1015,11 +1015,11 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) { const size = 1 << 20 - servewaf := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { + standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write(make([]byte, size)) }) - addr, _ := startRProxy(t, servewaf.URL, nowhere, nil) + addr, _ := startRProxy(t, standIn.URL, nowhere, nil) got := get(t, addr, "/") if got.status != http.StatusInternalServerError || len(got.body) != size { @@ -1135,15 +1135,15 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) { func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, overOneMiB(dispositionDeny)) - addr, out := startRProxy(t, servewaf.URL, nowhere, nil) + standIn := startServeWAF(t, overOneMiB(dispositionDeny)) + addr, out := startRProxy(t, standIn.URL, nowhere, nil) if got := get(t, addr, "/"); got.status != http.StatusBadGateway { t.Errorf("answered %+v, want 502", got) } out.requestLine(t) - servewaf.wantNoReport(t) + standIn.wantNoReport(t) } func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) { @@ -1199,20 +1199,20 @@ func TestAVisitorThatGoesAwayWhileServeWAFIsAskedGetsNoDefault(t *testing.T) { t.Run("with the default "+disposition, func(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(_ http.ResponseWriter, r *http.Request) { + standIn := startServeWAF(t, func(_ http.ResponseWriter, r *http.Request) { <-r.Context().Done() }) app := startApp(t, func(http.ResponseWriter, *http.Request) { t.Error("the app was asked") }) - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ timeoutSetting: "60000", defaultDisposition: disposition, }) conn := dial(t, addr) send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n") - servewaf.question(t) + standIn.question(t) _ = conn.Close() @@ -1226,7 +1226,7 @@ func TestAVisitorThatGoesAwayWhileServeWAFIsAskedGetsNoDefault(t *testing.T) { t.Error("rproxy warned that servewaf gave no answer") } - servewaf.wantNoReport(t) + standIn.wantNoReport(t) }) } } @@ -1244,17 +1244,17 @@ func TestRProxyWarnsWhenTheDefaultDecides(t *testing.T) { func TestSendsNoReportWithoutAnAnswer(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, func(_ http.ResponseWriter, r *http.Request) { + standIn := startServeWAF(t, func(_ http.ResponseWriter, r *http.Request) { <-r.Context().Done() }) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ timeoutSetting: "200", }) wantGreeting(t, get(t, addr, "/")) out.requestLine(t) - servewaf.wantNoReport(t) + standIn.wantNoReport(t) } func TestAPathUnderOwnPrefixNeverReachesTheApp(t *testing.T) { @@ -1274,25 +1274,25 @@ func TestAPathUnderOwnPrefixNeverReachesTheApp(t *testing.T) { func TestHealthCheckIsAnsweredWhileServeWAFIsDown(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) addr, out := startRProxy(t, nowhere, app.URL, nil) - got := get(t, addr, proxy.HealthPath) + got := get(t, addr, servewaf.HealthPath) if got.status != http.StatusOK || got.body != "ok\n" { t.Errorf("answered %+v, want ok", got) } line := out.requestLine(t) - if line["path"] != proxy.HealthPath || line["disposition"] != nil { + if line["path"] != servewaf.HealthPath || line["disposition"] != nil { t.Errorf("line %v, want one with no disposition", line) } // With servewaf up too, it is not asked. - addr, _ = startRProxy(t, servewaf.URL, app.URL, nil) - get(t, addr, proxy.HealthPath) + addr, _ = startRProxy(t, standIn.URL, app.URL, nil) + get(t, addr, servewaf.HealthPath) - if len(servewaf.questions) != 0 { + if len(standIn.questions) != 0 { t.Error("servewaf was asked about the health check") } } @@ -1396,15 +1396,15 @@ func TestAReportAnswered101IsDroppedAtOnce(t *testing.T) { func TestAReportDueAfterWaitForReportsIsDropped(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out, server := startServer(t, servewaf.URL, app.URL, nil) + addr, out, server := startServer(t, standIn.URL, app.URL, nil) server.WaitForReports() wantGreeting(t, get(t, addr, "/")) out.requestLine(t) - servewaf.wantNoReport(t) + standIn.wantNoReport(t) } func TestReportGivesWhatHappened(t *testing.T) { @@ -1412,14 +1412,14 @@ func TestReportGivesWhatHappened(t *testing.T) { const body = "the visitor's body" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) wantGreeting(t, visit(t, newRequest(t, http.MethodPost, addr, "/", strings.NewReader(body)))) - report := servewaf.report(t) + report := standIn.report(t) if report.RequestID == "" || report.Status != http.StatusOK || report.RequestBytes != int64(len(body)) || @@ -1442,7 +1442,7 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) { release := make(chan struct{}) backend := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get(proxy.ClientHeader) != "" { + if r.Header.Get(servewaf.ClientHeader) != "" { allow(w, r) return @@ -1497,9 +1497,9 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) { func TestReportOfAWebSocketCountsWhatItCarried(t *testing.T) { t.Parallel() - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, echoAfterUpgrade) - addr, out := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ maxRequestTime: "2000", maxResponseTime: "2000", }) @@ -1535,7 +1535,7 @@ func TestReportOfAWebSocketCountsWhatItCarried(t *testing.T) { _ = conn.Close() - report := servewaf.report(t) + report := standIn.report(t) if report.Status != http.StatusSwitchingProtocols || report.RequestBytes != int64(len(message)) || report.ResponseBytes != int64(len(message)) { @@ -1552,7 +1552,7 @@ func TestAWebSocketPassesOnThatTheVisitorStoppedSending(t *testing.T) { const goodbye = "sent after the visitor stopped sending\n" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { conn, buffered, err := http.NewResponseController(w).Hijack() if err != nil { @@ -1572,7 +1572,7 @@ func TestAWebSocketPassesOnThatTheVisitorStoppedSending(t *testing.T) { _, _ = buffered.WriteString(goodbye) _ = buffered.Flush() }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, "GET /socket HTTP/1.1\r\nHost: app\r\n"+ @@ -1610,7 +1610,7 @@ func TestAWebSocketPassesOnThatTheVisitorStoppedSending(t *testing.T) { //nolint:paralleltest // see above func TestASwitchNotPassedOnClosesTheAppsConnection(t *testing.T) { closed := make(chan error, 1) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, func(w http.ResponseWriter, _ *http.Request) { conn, buffered, err := http.NewResponseController(w).Hijack() if err != nil { @@ -1633,7 +1633,7 @@ func TestASwitchNotPassedOnClosesTheAppsConnection(t *testing.T) { _, err = buffered.ReadByte() closed <- err }) - addr, _ := startRProxy(t, servewaf.URL, app.URL, nil) + addr, _ := startRProxy(t, standIn.URL, app.URL, nil) conn := dial(t, addr) send(t, conn, "GET /socket HTTP/1.1\r\nHost: app\r\n"+ @@ -1681,9 +1681,9 @@ func TestEachRequestGetsAFreshID(t *testing.T) { const fromTraefik = "an-id-from-traefik" - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, _ := startRProxy(t, servewaf.URL, app.URL, map[string]string{ + addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{ trustedProxies: localhost + "/32", }) @@ -1694,7 +1694,7 @@ func TestEachRequestGetsAFreshID(t *testing.T) { req.Header.Set(requestIDHeader, fromTraefik) wantGreeting(t, visit(t, req)) - ids = append(ids, servewaf.question(t).header.Get(requestIDHeader)) + ids = append(ids, standIn.question(t).header.Get(requestIDHeader)) } if slices.Contains(ids, fromTraefik) || ids[0] == "" || ids[0] == ids[1] { @@ -1711,16 +1711,16 @@ func TestLogLineGivesTheRequestAsSent(t *testing.T) { body = "the visitor's body" ) - servewaf := startServeWAF(t, allow) + standIn := startServeWAF(t, allow) app := startApp(t, greet) - addr, out := startRProxy(t, servewaf.URL, app.URL, nil) + addr, out := startRProxy(t, standIn.URL, app.URL, nil) req := newRequest(t, http.MethodPost, addr, path+"?"+query, strings.NewReader(body)) req.Host = site wantGreeting(t, visit(t, req)) line := out.requestLine(t) - asked := servewaf.question(t) + asked := standIn.question(t) for name, want := range map[string]any{ "type": "request", diff --git a/internal/rproxy/servewaf.go b/internal/rproxy/servewaf.go index 3328e18..2c2e601 100644 --- a/internal/rproxy/servewaf.go +++ b/internal/rproxy/servewaf.go @@ -11,7 +11,7 @@ import ( "net/http" "strings" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // answerBodyMaxBytes is the most of the body of servewaf's answer that is read. @@ -38,10 +38,10 @@ type servewafAnswer struct { // notes nothing, and no report is due for it: servewaf holds no decision to report // on. func (rq *request) noteAnswer(answer *servewafAnswer) { - disposition := answer.header.Get(proxy.DispositionHeader) + disposition := answer.header.Get(servewaf.DispositionHeader) switch disposition { - case proxy.DispositionAllow, proxy.DispositionDeny, proxy.DispositionClose: + case servewaf.DispositionAllow, servewaf.DispositionDeny, servewaf.DispositionClose: rq.disposition = disposition rq.answered = true rq.asn = answer.header.Get(asnHeader) @@ -74,7 +74,7 @@ func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) { rq.disposition = rq.h.config.DefaultDisposition rq.byDefault = true - if rq.disposition == proxy.DispositionAllow { + if rq.disposition == servewaf.DispositionAllow { rq.passToApp(ctx) } else { rq.answer(http.StatusServiceUnavailable) @@ -169,13 +169,13 @@ func (rq *request) newQuestion(ctx context.Context) (*http.Request, error) { question.TransferEncoding = in.TransferEncoding question.Header = in.Header.Clone() - question.Header.Set(proxy.ClientHeader, rq.client.String()) - question.Header.Set(proxy.PeerHeader, rq.peer.String()) - question.Header.Del(proxy.ContentLengthHeader) + question.Header.Set(servewaf.ClientHeader, rq.client.String()) + question.Header.Set(servewaf.PeerHeader, rq.peer.String()) + question.Header.Del(servewaf.ContentLengthHeader) announced := in.Header.Get("Content-Length") if announced != "" { - question.Header.Set(proxy.ContentLengthHeader, announced) + question.Header.Set(servewaf.ContentLengthHeader, announced) } question.Header.Set(requestIDHeader, rq.id) @@ -196,7 +196,7 @@ func (rq *request) answerWith(answer *servewafAnswer) { header := rq.out.Header() maps.Copy(header, answer.header) - header.Del(proxy.DispositionHeader) + header.Del(servewaf.DispositionHeader) // Go's server adds a Content-Type it guesses from the body to a response whose // headers have no Content-Type key. The key with no value sends none. @@ -210,7 +210,7 @@ func (rq *request) answerWith(answer *servewafAnswer) { // sendReport sends servewaf report on a goroutine of its own, so that the // visitor does not wait for it. The report goes on after ctx, the request's own // context, has ended. -func (h *handler) sendReport(ctx context.Context, report *proxy.Report) { +func (h *handler) sendReport(ctx context.Context, report *servewaf.Report) { h.reportsMu.Lock() defer h.reportsMu.Unlock() @@ -228,7 +228,7 @@ func (h *handler) sendReport(ctx context.Context, report *proxy.Report) { // postReport posts report to servewaf, and waits for its answer for up to // SWRP_TIMEOUT_MS. -func (h *handler) postReport(ctx context.Context, report *proxy.Report) error { +func (h *handler) postReport(ctx context.Context, report *servewaf.Report) error { body, err := json.Marshal(report) if err != nil { return fmt.Errorf("encode the report: %w", err) diff --git a/internal/proxy/admin.go b/internal/servewaf/admin.go similarity index 99% rename from internal/proxy/admin.go rename to internal/servewaf/admin.go index 1332101..c77c321 100644 --- a/internal/proxy/admin.go +++ b/internal/servewaf/admin.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "bytes" diff --git a/internal/proxy/admin_test.go b/internal/servewaf/admin_test.go similarity index 88% rename from internal/proxy/admin_test.go rename to internal/servewaf/admin_test.go index 480616f..0aea41e 100644 --- a/internal/proxy/admin_test.go +++ b/internal/servewaf/admin_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "encoding/json" @@ -10,9 +10,9 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" "sneak.berlin/go/smallwebwaf/internal/state" ) @@ -88,11 +88,11 @@ func TestAdminEndpointsNeedTheAdminToken(t *testing.T) { // Any other request under /_smallwebwaf/ is not found. for _, e := range []adminEndpoint{ - {http.MethodPut, proxy.BansPath, banOtherClient}, - {http.MethodDelete, proxy.BansPath, ""}, - {http.MethodGet, proxy.BansPath + "/" + otherClient, ""}, - {http.MethodPost, proxy.ClientsPath + otherClient, ""}, - {http.MethodGet, strings.TrimSuffix(proxy.ClientsPath, "/"), ""}, + {http.MethodPut, servewaf.BansPath, banOtherClient}, + {http.MethodDelete, servewaf.BansPath, ""}, + {http.MethodGet, servewaf.BansPath + "/" + otherClient, ""}, + {http.MethodPost, servewaf.ClientsPath + otherClient, ""}, + {http.MethodGet, strings.TrimSuffix(servewaf.ClientsPath, "/"), ""}, } { s.admin(e.method, e.path, e.body, http.StatusNotFound) } @@ -118,7 +118,7 @@ func TestBanAddedListedAndLiftedThroughTheEndpoints(t *testing.T) { Reason: "probes for logins", } - wantBans(t, s.admin(http.MethodPost, proxy.BansPath, banOtherClient, http.StatusOK), + wantBans(t, s.admin(http.MethodPost, servewaf.BansPath, banOtherClient, http.StatusOK), want) line := s.get(client, http.StatusForbidden, requestlog.ActionBanned) @@ -129,7 +129,7 @@ func TestBanAddedListedAndLiftedThroughTheEndpoints(t *testing.T) { // Its notes count the request it refused. want.Notes.Requests, want.Notes.Refused = 1, 1 - wantBans(t, s.admin(http.MethodGet, proxy.BansPath, "", http.StatusOK), want) + wantBans(t, s.admin(http.MethodGet, servewaf.BansPath, "", http.StatusOK), want) // Ten minutes on, lifting the bans on client lifts that one, which is // kept, marked lifted. @@ -138,13 +138,13 @@ func TestBanAddedListedAndLiftedThroughTheEndpoints(t *testing.T) { lifted := clk.Now() want.Lifted = &lifted - wantBans(t, s.admin(http.MethodDelete, proxy.BansPath+"/"+client, "", + wantBans(t, s.admin(http.MethodDelete, servewaf.BansPath+"/"+client, "", http.StatusOK), want) s.get(client, http.StatusOK, requestlog.ActionForward) - wantBans(t, s.admin(http.MethodGet, proxy.BansPath, "", http.StatusOK), want) + wantBans(t, s.admin(http.MethodGet, servewaf.BansPath, "", http.StatusOK), want) // No ban on it is active any more. - s.admin(http.MethodDelete, proxy.BansPath+"/"+client, "", http.StatusNotFound) + s.admin(http.MethodDelete, servewaf.BansPath+"/"+client, "", http.StatusNotFound) } func TestBanToAddGivesItsNetblockAndDuration(t *testing.T) { @@ -181,7 +181,7 @@ func TestBanToAddGivesItsNetblockAndDuration(t *testing.T) { want.Expires = &expires } - wantBans(t, s.admin(http.MethodPost, proxy.BansPath, body, http.StatusOK), want) + wantBans(t, s.admin(http.MethodPost, servewaf.BansPath, body, http.StatusOK), want) } } @@ -250,7 +250,7 @@ func TestBanToAddThatCannotBeReadIsRefused(t *testing.T) { "request body too large", }, } { - got := s.admin(http.MethodPost, proxy.BansPath, tc.body, http.StatusBadRequest) + got := s.admin(http.MethodPost, servewaf.BansPath, tc.body, http.StatusBadRequest) if !strings.Contains(string(got.body), tc.want) { t.Errorf("%.80s was answered %q, want it to say %q", tc.body, got.body, tc.want) } @@ -283,7 +283,7 @@ func TestClientEndpointShowsTheClientAndItsBans(t *testing.T) { Bans []state.BanEntry `json:"bans"` } - decode(t, s.admin(http.MethodGet, proxy.ClientsPath+addr, "", http.StatusOK), &got) + decode(t, s.admin(http.MethodGet, servewaf.ClientsPath+addr, "", http.StatusOK), &got) if got.Client == nil { t.Fatalf("%s: no client", addr) @@ -305,13 +305,13 @@ func TestClientEndpointShowsTheClientAndItsBans(t *testing.T) { // Of an address no request came from and no ban covers, nothing is // known. - got := s.admin(http.MethodGet, proxy.ClientsPath+"198.51.100.99", "", http.StatusOK) + got := s.admin(http.MethodGet, servewaf.ClientsPath+"198.51.100.99", "", http.StatusOK) if string(got.body) != "{\n \"client\": null,\n \"bans\": []\n}\n" { t.Errorf("an unknown client is answered\n%s", got.body) } - s.admin(http.MethodGet, proxy.ClientsPath+"203.0.113", "", http.StatusBadRequest) - s.admin(http.MethodDelete, proxy.BansPath+"/203.0.113.0/24", "", + s.admin(http.MethodGet, servewaf.ClientsPath+"203.0.113", "", http.StatusBadRequest) + s.admin(http.MethodDelete, servewaf.BansPath+"/203.0.113.0/24", "", http.StatusBadRequest) } @@ -342,10 +342,10 @@ type adminEndpoint struct { // bans on otherClient, and asking about otherClient. func adminEndpoints() []adminEndpoint { return []adminEndpoint{ - {http.MethodGet, proxy.BansPath, ""}, - {http.MethodPost, proxy.BansPath, banOtherClient}, - {http.MethodDelete, proxy.BansPath + "/" + otherClient, ""}, - {http.MethodGet, proxy.ClientsPath + otherClient, ""}, + {http.MethodGet, servewaf.BansPath, ""}, + {http.MethodPost, servewaf.BansPath, banOtherClient}, + {http.MethodDelete, servewaf.BansPath + "/" + otherClient, ""}, + {http.MethodGet, servewaf.ClientsPath + otherClient, ""}, } } diff --git a/internal/proxy/alerts_test.go b/internal/servewaf/alerts_test.go similarity index 98% rename from internal/proxy/alerts_test.go rename to internal/servewaf/alerts_test.go index ed83044..de4d2f1 100644 --- a/internal/proxy/alerts_test.go +++ b/internal/servewaf/alerts_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "maps" @@ -10,8 +10,8 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -201,7 +201,7 @@ func TestObserveModeWorksOutABanOnlyWhenItsAlertWouldBeSent(t *testing.T) { // never sent them, and returns the queue they wait in as well. func startWithAlerts( t *testing.T, env map[string]string, -) (*sender, *clock, *proxy.Server, *alerts.Queue) { +) (*sender, *clock, *servewaf.Server, *alerts.Queue) { t.Helper() return startAppWithAlerts(t, func(http.ResponseWriter, *http.Request) {}, env) @@ -210,7 +210,7 @@ func startWithAlerts( // startAppWithAlerts is startWithAlerts in front of the app handler. func startAppWithAlerts( t *testing.T, handler http.HandlerFunc, env map[string]string, -) (*sender, *clock, *proxy.Server, *alerts.Queue) { +) (*sender, *clock, *servewaf.Server, *alerts.Queue) { t.Helper() app := startApp(t, handler) diff --git a/internal/proxy/anomaly_internal_test.go b/internal/servewaf/anomaly_internal_test.go similarity index 97% rename from internal/proxy/anomaly_internal_test.go rename to internal/servewaf/anomaly_internal_test.go index 88fc46f..8f70c3a 100644 --- a/internal/proxy/anomaly_internal_test.go +++ b/internal/servewaf/anomaly_internal_test.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "net/netip" diff --git a/internal/proxy/anomaly_test.go b/internal/servewaf/anomaly_test.go similarity index 98% rename from internal/proxy/anomaly_test.go rename to internal/servewaf/anomaly_test.go index 36adc3f..a04941f 100644 --- a/internal/proxy/anomaly_test.go +++ b/internal/servewaf/anomaly_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "maps" @@ -13,9 +13,9 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/anomaly" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // The anomaly thresholds: the prefix of a scope followed by the end of a @@ -319,7 +319,7 @@ func TestRequestCountsForTheASNumberGeoJSGivesBeforeItEnds(t *testing.T) { // kept. geojsURL, _, release := startHeldGeoJS(t) - var server atomic.Pointer[proxy.Server] + var server atomic.Pointer[servewaf.Server] app := startApp(t, func(http.ResponseWriter, *http.Request) { release() diff --git a/internal/proxy/answertime_test.go b/internal/servewaf/answertime_test.go similarity index 90% rename from internal/proxy/answertime_test.go rename to internal/servewaf/answertime_test.go index cea0ea5..7f47f8f 100644 --- a/internal/proxy/answertime_test.go +++ b/internal/servewaf/answertime_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "fmt" @@ -16,10 +16,10 @@ import ( "sneak.berlin/go/smallwebwaf/internal/bans" "sneak.berlin/go/smallwebwaf/internal/lookup" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/reputation" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -81,7 +81,7 @@ func TestSixteenThousandCookiesAreRefusedWellWithinTheTimeLimit(t *testing.T) { t.Errorf("answered after %s, want well within %s", took, answerTimeLimit) } - wantDisposition(t, got, tooLarge, proxy.DispositionDeny) + wantDisposition(t, got, tooLarge, servewaf.DispositionDeny) line := s.out.requestLine(t) wantLine(t, line, tooLarge, requestlog.ActionTooLarge) @@ -144,13 +144,13 @@ func TestEachHeaderLimitRefusesARequestOverIt(t *testing.T) { line := s.out.requestLine(t) if tc.refused { - wantDisposition(t, got, tooLarge, proxy.DispositionDeny) + wantDisposition(t, got, tooLarge, servewaf.DispositionDeny) wantLine(t, line, tooLarge, requestlog.ActionTooLarge) return } - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) wantLine(t, line, http.StatusOK, requestlog.ActionForward) if line.DurationWAF == nil { @@ -175,7 +175,7 @@ func TestHeaderLimitsHoldWhateverTheCoreRuleSetSettingsSay(t *testing.T) { req := s.question(siteA, client, "/") req.Header.Set(cookieHeader, cookies(cookieLimit+1)) - wantDisposition(t, do(t, req), tooLarge, proxy.DispositionDeny) + wantDisposition(t, do(t, req), tooLarge, servewaf.DispositionDeny) }) } } @@ -187,7 +187,7 @@ func TestClientInTheAllowNetsSkipsTheHeaderLimits(t *testing.T) { req := s.question(siteA, client, "/") req.Header.Set(cookieHeader, cookies(cookieLimit+1)) - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) } func TestObserveModeAllowsARequestOverTheHeaderLimitsUnread(t *testing.T) { @@ -204,7 +204,7 @@ func TestObserveModeAllowsARequestOverTheHeaderLimitsUnread(t *testing.T) { t.Errorf("answered after %s, want well within %s", took, answerTimeLimit) } - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) line := s.out.requestLine(t) if line.WouldAction != requestlog.ActionTooLarge || line.DurationWAF != nil { @@ -224,8 +224,8 @@ func TestRequestsOverTheHeaderLimitsCountTowardTheRateLimits(t *testing.T) { return do(t, req) } - wantDisposition(t, ask(), tooLarge, proxy.DispositionDeny) - wantDisposition(t, ask(), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, ask(), tooLarge, servewaf.DispositionDeny) + wantDisposition(t, ask(), http.StatusForbidden, servewaf.DispositionDeny) lines := s.out.requestLines(t, 2) wantLine(t, lines[1], http.StatusForbidden, requestlog.ActionRateLimited) @@ -249,7 +249,7 @@ func TestTheHeaderLimitsComeAfterTheTrapPathsAndTheRuleFiles(t *testing.T) { req := s.question(siteA, client, tc.path) req.Header.Set(cookieHeader, cookies(cookieLimit+1)) - wantDisposition(t, do(t, req), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, do(t, req), http.StatusForbidden, servewaf.DispositionDeny) wantLine(t, s.out.requestLine(t), http.StatusForbidden, tc.action) }) } @@ -265,7 +265,7 @@ func TestNotWaitingForGeoJSNeverLowersAClientsLimit(t *testing.T) { req := s.question(siteA, fromDE, "/") req.Header.Set(cookieHeader, cookies(cookieLimit+1)) - wantDisposition(t, do(t, req), tooLarge, proxy.DispositionDeny) + wantDisposition(t, do(t, req), tooLarge, servewaf.DispositionDeny) line := s.out.requestLine(t) wantLine(t, line, tooLarge, requestlog.ActionTooLarge) @@ -275,7 +275,7 @@ func TestNotWaitingForGeoJSNeverLowersAClientsLimit(t *testing.T) { } // The client is under no ban, and GeoJS places it in a country. - wantDisposition(t, s.ask(fromDE, "/"), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, s.ask(fromDE, "/"), http.StatusOK, servewaf.DispositionAllow) } func TestAClientGeoJSPlacedInNoCountryCountsAsUnknownWithoutWaiting(t *testing.T) { @@ -291,7 +291,7 @@ func TestAClientGeoJSPlacedInNoCountryCountsAsUnknownWithoutWaiting(t *testing.T req := s.question(siteA, client, "/") req.Header.Set(cookieHeader, cookies(cookieLimit+1)) - wantDisposition(t, do(t, req), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, do(t, req), http.StatusForbidden, servewaf.DispositionDeny) wantLine(t, s.out.requestLine(t), http.StatusForbidden, requestlog.ActionRateLimited) } @@ -306,7 +306,7 @@ func TestObserveModeWaitsForGeoJSForARequestOverTheHeaderLimits(t *testing.T) { req.Header.Set(cookieHeader, cookies(cookieLimit+1)) got := do(t, req) - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) asn, country := got.header.Get("X-Client-Asn"), got.header.Get("X-Client-Country") if asn != asnDE || country != "DE" { @@ -318,7 +318,9 @@ func TestObserveModeWaitsForGeoJSForARequestOverTheHeaderLimits(t *testing.T) { // newServeWAFInBubble makes a servewaf for a synctest bubble: GeoJS never // answers, a setting needs its answer, and everything is at its default but // for env. No request to it may use the network. -func newServeWAFInBubble(t *testing.T, env map[string]string) (*proxy.Server, *output) { +func newServeWAFInBubble( + t *testing.T, env map[string]string, +) (*servewaf.Server, *output) { t.Helper() settings := map[string]string{deniedCountries: "KP"} @@ -332,13 +334,13 @@ func newServeWAFInBubble(t *testing.T, env map[string]string) (*proxy.Server, *o // askDirectly hands a question about a request from client straight to server, // as change leaves it, and returns the answer's status and how long it took. func askDirectly( - t *testing.T, server *proxy.Server, change func(*http.Request), + t *testing.T, server *servewaf.Server, change func(*http.Request), ) (int, time.Duration) { t.Helper() req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody) req.RemoteAddr = net.JoinHostPort(rproxyPeer, "1234") - req.Header.Set(proxy.ClientHeader, client) + req.Header.Set(servewaf.ClientHeader, client) req.Header.Set(requestIDHeader, "the-id") change(req) @@ -351,17 +353,17 @@ func askDirectly( } // banClient bans client at now. -func banClient(_ *testing.T, server *proxy.Server, now time.Time) { +func banClient(_ *testing.T, server *servewaf.Server, now time.Time) { server.Ledger.BanForLimit(netip.MustParsePrefix(client+"/32"), now, bans.Notes{}) } // countClient counts one request from client at now for the rate limits. -func countClient(_ *testing.T, server *proxy.Server, now time.Time) { +func countClient(_ *testing.T, server *servewaf.Server, now time.Time) { server.Limiter.Count(netip.MustParsePrefix(client+"/32"), now, wholeLimit) } // listClient puts client on the blocklist at dropURL. -func listClient(t *testing.T, server *proxy.Server, now time.Time) { +func listClient(t *testing.T, server *servewaf.Server, now time.Time) { t.Helper() err := server.Lists.Load([]reputation.List{{ @@ -373,13 +375,13 @@ func listClient(t *testing.T, server *proxy.Server, now time.Time) { } // decideOnClient puts client on the CrowdSec decision list. -func decideOnClient(t *testing.T, server *proxy.Server, now time.Time) { +func decideOnClient(t *testing.T, server *servewaf.Server, now time.Time) { t.Helper() loadDecisions(t, server, now, clientsDecision) } // leaveAsItIs prepares nothing. -func leaveAsItIs(*testing.T, *proxy.Server, time.Time) {} +func leaveAsItIs(*testing.T, *servewaf.Server, time.Time) {} func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) { t.Parallel() @@ -390,7 +392,7 @@ func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) { name string env map[string]string // prepare gets the server ready, at now, before the question. - prepare func(t *testing.T, server *proxy.Server, now time.Time) + prepare func(t *testing.T, server *servewaf.Server, now time.Time) // cookie is the request's Cookie header. cookie string status int @@ -461,7 +463,7 @@ func TestAQuestionWaitsForASlowGeoJSOnlyAsLongAsTheDefaultSays(t *testing.T) { name string env map[string]string // prepare gets the server ready, at now, before the question. - prepare func(t *testing.T, server *proxy.Server, now time.Time) + prepare func(t *testing.T, server *servewaf.Server, now time.Time) status int action string }{ @@ -519,10 +521,10 @@ func TestServeWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(t *testin t.Cleanup(func() { _ = resolver.Close() }) - server, out, _ := newServeWAFWith(t, func(params proxy.Params) *proxy.Server { + server, out, _ := newServeWAFWith(t, func(params servewaf.Params) *servewaf.Server { params.AbuseIPDBURL = silentAbuseIPDBURL - return proxy.NewServeWAF(params) + return servewaf.New(params) }, held, time.Now, map[string]string{ deniedCountries: "KP", wafMode: block, dnsblZones: dnsblZone, dnsblResolver: resolver.LocalAddr().String(), diff --git a/internal/proxy/bans.go b/internal/servewaf/bans.go similarity index 99% rename from internal/proxy/bans.go rename to internal/servewaf/bans.go index 6b64168..44d5996 100644 --- a/internal/proxy/bans.go +++ b/internal/servewaf/bans.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "net/http" diff --git a/internal/proxy/bans_test.go b/internal/servewaf/bans_test.go similarity index 99% rename from internal/proxy/bans_test.go rename to internal/servewaf/bans_test.go index 164faa9..2ca896a 100644 --- a/internal/proxy/bans_test.go +++ b/internal/servewaf/bans_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "bufio" @@ -15,8 +15,8 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -382,7 +382,7 @@ func (c *clock) advance(d time.Duration) { // window. func startWithClock( t *testing.T, geojsURL string, env map[string]string, -) (*sender, *clock, *proxy.Server) { +) (*sender, *clock, *servewaf.Server) { t.Helper() app := startApp(t, func(http.ResponseWriter, *http.Request) {}) diff --git a/internal/proxy/biased.go b/internal/servewaf/biased.go similarity index 99% rename from internal/proxy/biased.go rename to internal/servewaf/biased.go index 294cae9..6554b95 100644 --- a/internal/proxy/biased.go +++ b/internal/servewaf/biased.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "sneak.berlin/go/smallwebwaf/internal/config" diff --git a/internal/proxy/biased_test.go b/internal/servewaf/biased_test.go similarity index 98% rename from internal/proxy/biased_test.go rename to internal/servewaf/biased_test.go index 821b1d2..3c4462b 100644 --- a/internal/proxy/biased_test.go +++ b/internal/servewaf/biased_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "fmt" @@ -16,8 +16,8 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/bans" "sneak.berlin/go/smallwebwaf/internal/lookup/lookuptest" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // The biased thresholds. @@ -419,7 +419,7 @@ func TestBanForALoweredLimitGivesThePercentageInItsNotesAndItsAlert(t *testing.T // clock. func startWithLookups( t *testing.T, env map[string]string, -) (*sender, *proxy.Server, *alerts.Queue) { +) (*sender, *servewaf.Server, *alerts.Queue) { t.Helper() s, _, server, queue := startWithLookupsAndClock(t, env) @@ -434,7 +434,7 @@ func startWithLookups( // country, and no other address. func startWithLookupsAndClock( t *testing.T, env map[string]string, -) (*sender, *clock, *proxy.Server, *alerts.Queue) { +) (*sender, *clock, *servewaf.Server, *alerts.Queue) { t.Helper() path := filepath.Join(t.TempDir(), "ipinfo_lite.mmdb") @@ -463,11 +463,11 @@ func (s *sender) uploadFrom(from string) logLine { // serveFromDE hands a question about a request from fromDE with method and body // straight to server's handler, without the network, and returns once it is // answered. -func serveFromDE(t *testing.T, server *proxy.Server, method string, body io.Reader) { +func serveFromDE(t *testing.T, server *servewaf.Server, method string, body io.Reader) { t.Helper() req := httptest.NewRequestWithContext(t.Context(), method, "/", body) - req.Header.Set(proxy.ClientHeader, fromDE) + req.Header.Set(servewaf.ClientHeader, fromDE) req.Header.Set(requestIDHeader, "the-id") server.Handler.ServeHTTP(httptest.NewRecorder(), req) diff --git a/internal/proxy/bytelimits_test.go b/internal/servewaf/bytelimits_test.go similarity index 99% rename from internal/proxy/bytelimits_test.go rename to internal/servewaf/bytelimits_test.go index 327f233..37b1370 100644 --- a/internal/proxy/bytelimits_test.go +++ b/internal/servewaf/bytelimits_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "bufio" diff --git a/internal/proxy/client.go b/internal/servewaf/client.go similarity index 99% rename from internal/proxy/client.go rename to internal/servewaf/client.go index 8a1da45..a82ad1f 100644 --- a/internal/proxy/client.go +++ b/internal/servewaf/client.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "crypto/rand" diff --git a/internal/proxy/coreruleset.go b/internal/servewaf/coreruleset.go similarity index 99% rename from internal/proxy/coreruleset.go rename to internal/servewaf/coreruleset.go index 7fb81fc..75a382f 100644 --- a/internal/proxy/coreruleset.go +++ b/internal/servewaf/coreruleset.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "time" diff --git a/internal/proxy/coreruleset_test.go b/internal/servewaf/coreruleset_test.go similarity index 99% rename from internal/proxy/coreruleset_test.go rename to internal/servewaf/coreruleset_test.go index cc877bc..bab2605 100644 --- a/internal/proxy/coreruleset_test.go +++ b/internal/servewaf/coreruleset_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "io" diff --git a/internal/proxy/countries.go b/internal/servewaf/countries.go similarity index 97% rename from internal/proxy/countries.go rename to internal/servewaf/countries.go index b3099f2..fdea122 100644 --- a/internal/proxy/countries.go +++ b/internal/servewaf/countries.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "slices" diff --git a/internal/proxy/countries_test.go b/internal/servewaf/countries_test.go similarity index 99% rename from internal/proxy/countries_test.go rename to internal/servewaf/countries_test.go index d148226..edf30f7 100644 --- a/internal/proxy/countries_test.go +++ b/internal/servewaf/countries_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "encoding/json" diff --git a/internal/proxy/crowdsec_test.go b/internal/servewaf/crowdsec_test.go similarity index 97% rename from internal/proxy/crowdsec_test.go rename to internal/servewaf/crowdsec_test.go index 4c7add8..83fc694 100644 --- a/internal/proxy/crowdsec_test.go +++ b/internal/servewaf/crowdsec_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" @@ -9,9 +9,9 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/reputation" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // The CrowdSec settings, and the tests' engine, which is never asked: each @@ -168,7 +168,7 @@ func TestObserveModeForwardsAClientTheCrowdSecDecisionListListsAndAlertsTheBan( // decisionsURL, answer, the engine's answer, fetched at fetched, as // reputation.json would at start. func loadDecisions( - t *testing.T, server *proxy.Server, fetched time.Time, answer string, + t *testing.T, server *servewaf.Server, fetched time.Time, answer string, ) { t.Helper() diff --git a/internal/proxy/errorburst_test.go b/internal/servewaf/errorburst_test.go similarity index 98% rename from internal/proxy/errorburst_test.go rename to internal/servewaf/errorburst_test.go index 57607d1..ef924c2 100644 --- a/internal/proxy/errorburst_test.go +++ b/internal/servewaf/errorburst_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "maps" @@ -10,9 +10,9 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const errorBurstThreshold = "SWWAF_ERROR_BURST_THRESHOLD" @@ -51,7 +51,7 @@ func (r refused) send(s *sender, from string) logLine { // SWWAF_ERROR_BURST_THRESHOLD at threshold, and the settings in env. func startForErrorBurst( t *testing.T, threshold string, env map[string]string, -) (*sender, *clock, *proxy.Server) { +) (*sender, *clock, *servewaf.Server) { t.Helper() settings := map[string]string{ diff --git a/internal/proxy/health_test.go b/internal/servewaf/health_test.go similarity index 86% rename from internal/proxy/health_test.go rename to internal/servewaf/health_test.go index 106c479..8d412ca 100644 --- a/internal/proxy/health_test.go +++ b/internal/servewaf/health_test.go @@ -1,11 +1,11 @@ -package proxy_test +package servewaf_test import ( "net/http" "testing" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) func TestHealthEndpointCountsTowardNoLimit(t *testing.T) { @@ -21,7 +21,7 @@ func TestHealthEndpointCountsTowardNoLimit(t *testing.T) { ) for range healthChecks { - got := s.adminRequest("", http.MethodGet, proxy.HealthPath, "", http.StatusOK) + got := s.adminRequest("", http.MethodGet, servewaf.HealthPath, "", http.StatusOK) if string(got.body) != "ok\n" || got.header.Get("Content-Type") != contentType { t.Errorf("health endpoint answered %q with Content-Type %q, want ok "+ diff --git a/internal/proxy/history_test.go b/internal/servewaf/history_test.go similarity index 92% rename from internal/proxy/history_test.go rename to internal/servewaf/history_test.go index 25ca614..bb82226 100644 --- a/internal/proxy/history_test.go +++ b/internal/servewaf/history_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "io" @@ -8,9 +8,9 @@ import ( "testing" "time" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) func TestHistoryKeepsEachRequestOfTheClient(t *testing.T) { @@ -100,8 +100,8 @@ func TestOwnEndpointsAreNotInTheHistory(t *testing.T) { s, _, server := startWithClock(t, "", map[string]string{metricsToken: token}) - s.adminRequest("", http.MethodGet, proxy.HealthPath, "", http.StatusOK) - s.adminRequest("", http.MethodGet, proxy.MetricsPath, "", http.StatusUnauthorized) + s.adminRequest("", http.MethodGet, servewaf.HealthPath, "", http.StatusOK) + s.adminRequest("", http.MethodGet, servewaf.MetricsPath, "", http.StatusUnauthorized) s.scrape() if clients := server.Limiter.Snapshot(); len(clients) != 0 { @@ -126,7 +126,7 @@ func TestPathUnderTheOwnPrefixIsNeitherForwardedNorRefused(t *testing.T) { } // historyOf returns the history of the client at addr. -func historyOf(t *testing.T, server *proxy.Server, addr string) ratelimit.History { +func historyOf(t *testing.T, server *servewaf.Server, addr string) ratelimit.History { t.Helper() client := netip.MustParsePrefix(addr + "/32") diff --git a/internal/proxy/lookup.go b/internal/servewaf/lookup.go similarity index 99% rename from internal/proxy/lookup.go rename to internal/servewaf/lookup.go index a19f550..6dd22e9 100644 --- a/internal/proxy/lookup.go +++ b/internal/servewaf/lookup.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "context" diff --git a/internal/proxy/lookup_test.go b/internal/servewaf/lookup_test.go similarity index 99% rename from internal/proxy/lookup_test.go rename to internal/servewaf/lookup_test.go index e45a9f4..189d25d 100644 --- a/internal/proxy/lookup_test.go +++ b/internal/servewaf/lookup_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" @@ -379,7 +379,7 @@ func TestRequestWaitsAsLongAsTheLookupTimeoutSays(t *testing.T) { // clock of the test's own: the wait lasts exactly as long as it should, // however slowly the test process runs. Nothing in it may wait on the // network, which would keep that clock from moving on: the request is - // handed to the proxy's handler, and GeoJS is one that never answers. + // handed to servewaf's handler, and GeoJS is one that never answers. synctest.Test(t, func(t *testing.T) { // Not the default. The exclusive list needs the answer, and the app // is never reached. diff --git a/internal/proxy/metrics_test.go b/internal/servewaf/metrics_test.go similarity index 93% rename from internal/proxy/metrics_test.go rename to internal/servewaf/metrics_test.go index 0f7c317..7c903c1 100644 --- a/internal/proxy/metrics_test.go +++ b/internal/servewaf/metrics_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "io" @@ -12,8 +12,8 @@ import ( "sneak.berlin/go/smallwebwaf/internal/bans" "sneak.berlin/go/smallwebwaf/internal/lookup" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -32,7 +32,7 @@ func TestMetricsAreOffWhileTheTokenIsUnset(t *testing.T) { // An empty token does not match the unset one either. for _, authorization := range []string{bearer, "Bearer ", ""} { - s.adminRequest(authorization, http.MethodGet, proxy.MetricsPath, "", + s.adminRequest(authorization, http.MethodGet, servewaf.MetricsPath, "", http.StatusNotFound) } } @@ -46,18 +46,18 @@ func TestMetricsNeedTheTokenAndOtherPathsAreNotFound(t *testing.T) { method, path, authorization string status int }{ - {http.MethodGet, proxy.MetricsPath, "", http.StatusUnauthorized}, + {http.MethodGet, servewaf.MetricsPath, "", http.StatusUnauthorized}, { - http.MethodGet, proxy.MetricsPath, "Bearer " + strings.ToUpper(token), + http.MethodGet, servewaf.MetricsPath, "Bearer " + strings.ToUpper(token), http.StatusUnauthorized, }, - {http.MethodGet, proxy.MetricsPath, "Basic " + token, http.StatusUnauthorized}, - {http.MethodGet, proxy.MetricsPath, bearer, http.StatusOK}, - {http.MethodGet, proxy.MetricsPath, "bearer " + token, http.StatusOK}, - {http.MethodPost, proxy.MetricsPath, bearer, http.StatusNotFound}, - {http.MethodGet, proxy.MetricsPath + "/", bearer, http.StatusNotFound}, + {http.MethodGet, servewaf.MetricsPath, "Basic " + token, http.StatusUnauthorized}, + {http.MethodGet, servewaf.MetricsPath, bearer, http.StatusOK}, + {http.MethodGet, servewaf.MetricsPath, "bearer " + token, http.StatusOK}, + {http.MethodPost, servewaf.MetricsPath, bearer, http.StatusNotFound}, + {http.MethodGet, servewaf.MetricsPath + "/", bearer, http.StatusNotFound}, {http.MethodGet, "/_smallwebwaf/bans", bearer, http.StatusNotFound}, - {http.MethodPost, proxy.HealthPath, "", http.StatusNotFound}, + {http.MethodPost, servewaf.HealthPath, "", http.StatusNotFound}, } { got := s.adminRequest(tc.authorization, tc.method, tc.path, "", tc.status) @@ -180,7 +180,7 @@ func TestMetricsCountTheBansAnAdminMakes(t *testing.T) { wantMetric(t, s.scrape(), admins, 1) // And a ban made through the endpoint. - s.admin(http.MethodPost, proxy.BansPath, banOtherClient, http.StatusOK) + s.admin(http.MethodPost, servewaf.BansPath, banOtherClient, http.StatusOK) wantMetric(t, s.scrape(), admins, 2) } @@ -365,7 +365,7 @@ func keptAnswer(addr, country string) lookup.Answer { func scrape(t *testing.T, addr string) string { t.Helper() - req := newRequest(t, http.MethodGet, addr, proxy.MetricsPath, http.NoBody) + req := newRequest(t, http.MethodGet, addr, servewaf.MetricsPath, http.NoBody) req.Header.Set("Authorization", bearer) got := do(t, req) @@ -380,7 +380,7 @@ func scrape(t *testing.T, addr string) string { func (s *sender) scrape() string { s.t.Helper() - return string(s.adminRequest(bearer, http.MethodGet, proxy.MetricsPath, "", + return string(s.adminRequest(bearer, http.MethodGet, servewaf.MetricsPath, "", http.StatusOK).body) } diff --git a/internal/proxy/observe_test.go b/internal/servewaf/observe_test.go similarity index 99% rename from internal/proxy/observe_test.go rename to internal/servewaf/observe_test.go index 5aa8725..ac95327 100644 --- a/internal/proxy/observe_test.go +++ b/internal/servewaf/observe_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" diff --git a/internal/proxy/proxy.go b/internal/servewaf/proxy.go similarity index 94% rename from internal/proxy/proxy.go rename to internal/servewaf/proxy.go index 2d0cb6d..fb7c639 100644 --- a/internal/proxy/proxy.go +++ b/internal/servewaf/proxy.go @@ -1,10 +1,7 @@ -// Package proxy is servewaf's server. It runs the checks on the request each of -// rproxy's questions carries, and answers rproxy what to do with that request. It -// takes rproxy's report once the visitor's answer has ended, which counts the -// request's bytes and writes its last log line. It answers servewaf's own -// endpoints: the health check, the metrics, the bans and the clients. It passes -// nothing to an app: internal/rproxy does that. -package proxy +// Package servewaf is the server the servewaf command runs. It answers rproxy's +// questions, takes rproxy's reports and answers its own endpoints, and writes a +// log line for each. +package servewaf import ( "io" @@ -46,7 +43,7 @@ const BansPath = "/_smallwebwaf/bans" // client's address after it, with SWWAF_ADMIN_TOKEN. const ClientsPath = "/_smallwebwaf/clients/" -// Params are what NewServeWAF needs. +// Params are what New needs. type Params struct { Config *config.Config // RequestLog receives two JSON lines per question, one at its decision and diff --git a/internal/proxy/proxy_test.go b/internal/servewaf/proxy_test.go similarity index 97% rename from internal/proxy/proxy_test.go rename to internal/servewaf/proxy_test.go index 773794f..53ec78e 100644 --- a/internal/proxy/proxy_test.go +++ b/internal/servewaf/proxy_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "bufio" @@ -18,10 +18,10 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/config" "sneak.berlin/go/smallwebwaf/internal/lookup" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/requestlog" "sneak.berlin/go/smallwebwaf/internal/rproxy" "sneak.berlin/go/smallwebwaf/internal/rules" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -300,7 +300,7 @@ func startProxyWithGeoJS( func startProxyWithClock( t *testing.T, appURL, geojsURL string, now func() time.Time, env map[string]string, -) (string, *output, *proxy.Server) { +) (string, *output, *servewaf.Server) { t.Helper() addr, out, server, _ := startProxyWithAlerts(t, appURL, geojsURL, now, env) @@ -313,7 +313,7 @@ func startProxyWithClock( func startProxyWithAlerts( t *testing.T, appURL, geojsURL string, now func() time.Time, env map[string]string, -) (string, *output, *proxy.Server, *alerts.Queue) { +) (string, *output, *servewaf.Server, *alerts.Queue) { t.Helper() server, out, alertQueue := newServeWAF(t, geojsURL, now, env) @@ -324,7 +324,7 @@ func startProxyWithAlerts( // serve serves server on a port of its own until the test ends, and sets its // Addr to where it listens. -func serve(t *testing.T, server *proxy.Server) { +func serve(t *testing.T, server *servewaf.Server) { t.Helper() listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0") @@ -401,17 +401,17 @@ func startRProxy( // inspected by it. func newServeWAF( t *testing.T, geojsURL string, now func() time.Time, env map[string]string, -) (*proxy.Server, *output, *alerts.Queue) { +) (*servewaf.Server, *output, *alerts.Queue) { t.Helper() - return newServeWAFWith(t, proxy.NewServeWAF, geojsURL, now, env) + return newServeWAFWith(t, servewaf.New, geojsURL, now, env) } // newServeWAFWith is newServeWAF for the server build makes. func newServeWAFWith( - t *testing.T, build func(proxy.Params) *proxy.Server, + t *testing.T, build func(servewaf.Params) *servewaf.Server, geojsURL string, now func() time.Time, env map[string]string, -) (*proxy.Server, *output, *alerts.Queue) { +) (*servewaf.Server, *output, *alerts.Queue) { t.Helper() settings := map[string]string{ @@ -463,7 +463,7 @@ func newServeWAFWith( } } - server := build(proxy.Params{ + server := build(servewaf.Params{ Config: cfg, RequestLog: out, ProcessLog: processLog, diff --git a/internal/proxy/ratelimits_test.go b/internal/servewaf/ratelimits_test.go similarity index 99% rename from internal/proxy/ratelimits_test.go rename to internal/servewaf/ratelimits_test.go index b2c3cda..c119f83 100644 --- a/internal/proxy/ratelimits_test.go +++ b/internal/servewaf/ratelimits_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" diff --git a/internal/proxy/reputation.go b/internal/servewaf/reputation.go similarity index 99% rename from internal/proxy/reputation.go rename to internal/servewaf/reputation.go index 8ecaf5f..7a0bc10 100644 --- a/internal/proxy/reputation.go +++ b/internal/servewaf/reputation.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "context" diff --git a/internal/proxy/reputation_test.go b/internal/servewaf/reputation_test.go similarity index 98% rename from internal/proxy/reputation_test.go rename to internal/servewaf/reputation_test.go index dae83bc..0989375 100644 --- a/internal/proxy/reputation_test.go +++ b/internal/servewaf/reputation_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "fmt" @@ -15,10 +15,10 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/reputation" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // The reputation settings. @@ -617,7 +617,7 @@ const ( // AbuseIPDB. const abuseipdb = reputation.AbuseIPDBSource -// abuseIPDBURL is where newProxy has clients checked with AbuseIPDB: at +// abuseIPDBURL is where newServeWAF has clients checked with AbuseIPDB: at // abuseIPDBStandIn, which TestMain registers with Go's default transport, // through which AbuseIPDB is asked. const abuseIPDBURL = "abuseipdb://stand-in/api/v2/check" @@ -975,7 +975,7 @@ func verdictsFetched() time.Time { // verdictsFetched, as reputation.json would at start: one that lists the // client from each zone listedBy gives for it, and one that does not from // each other zone. -func loadVerdicts(server *proxy.Server, listedBy map[string][]string) { +func loadVerdicts(server *servewaf.Server, listedBy map[string][]string) { verdicts := make([]reputation.Verdict, 0, len(listedBy)*len(server.DNSBL.Zones())) for client, zones := range listedBy { @@ -993,7 +993,7 @@ func loadVerdicts(server *proxy.Server, listedBy map[string][]string) { // loadScores puts into server's AbuseIPDB the score scores gives each // client, an IPv4 address, fetched at verdictsFetched, as reputation.json // would at start. -func loadScores(server *proxy.Server, scores map[string]int64) { +func loadScores(server *servewaf.Server, scores map[string]int64) { kept := make([]reputation.Score, 0, len(scores)) for client, score := range scores { kept = append(kept, reputation.Score{ @@ -1007,7 +1007,7 @@ func loadScores(server *proxy.Server, scores map[string]int64) { // wantAbuseIPDBChecks checks how many clients server has checked with // AbuseIPDB. -func wantAbuseIPDBChecks(t *testing.T, server *proxy.Server, want int) { +func wantAbuseIPDBChecks(t *testing.T, server *servewaf.Server, want int) { t.Helper() if got := server.AbuseIPDB.Checked(); got != want { @@ -1017,7 +1017,7 @@ func wantAbuseIPDBChecks(t *testing.T, server *proxy.Server, want int) { // loadLists puts copies of lists into server's lists, by URL, each with // its lines, fetched at listsFetched, as reputation.json would at start. -func loadLists(t *testing.T, server *proxy.Server, copies map[string][]string) { +func loadLists(t *testing.T, server *servewaf.Server, copies map[string][]string) { t.Helper() lists := make([]reputation.List, 0, len(copies)) diff --git a/internal/proxy/request.go b/internal/servewaf/request.go similarity index 99% rename from internal/proxy/request.go rename to internal/servewaf/request.go index 210fa8a..21f4ce1 100644 --- a/internal/proxy/request.go +++ b/internal/servewaf/request.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "context" diff --git a/internal/proxy/requestlog_test.go b/internal/servewaf/requestlog_test.go similarity index 98% rename from internal/proxy/requestlog_test.go rename to internal/servewaf/requestlog_test.go index 8442465..4022cf2 100644 --- a/internal/proxy/requestlog_test.go +++ b/internal/servewaf/requestlog_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "io" @@ -10,9 +10,9 @@ import ( "testing" "time" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -220,7 +220,7 @@ func TestTimingsAreInOrder(t *testing.T) { wantStatus(t, do(t, req), http.StatusForbidden) refused := out.endedLines(t, 2)[1] - wantStatus(t, get(t, server.Addr, proxy.HealthPath), http.StatusOK) + wantStatus(t, get(t, server.Addr, servewaf.HealthPath), http.StatusOK) health := out.endedLines(t, 3)[2] // A request passed to the app has every timing; one refused, none of diff --git a/internal/proxy/rulefiles_test.go b/internal/servewaf/rulefiles_test.go similarity index 99% rename from internal/proxy/rulefiles_test.go rename to internal/servewaf/rulefiles_test.go index 0c8fe6c..7858327 100644 --- a/internal/proxy/rulefiles_test.go +++ b/internal/servewaf/rulefiles_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" diff --git a/internal/proxy/rules.go b/internal/servewaf/rules.go similarity index 99% rename from internal/proxy/rules.go rename to internal/servewaf/rules.go index 9b11f7d..f425528 100644 --- a/internal/proxy/rules.go +++ b/internal/servewaf/rules.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "slices" diff --git a/internal/proxy/servewaf.go b/internal/servewaf/servewaf.go similarity index 99% rename from internal/proxy/servewaf.go rename to internal/servewaf/servewaf.go index 3e49ad5..5ffa461 100644 --- a/internal/proxy/servewaf.go +++ b/internal/servewaf/servewaf.go @@ -1,4 +1,4 @@ -package proxy +package servewaf import ( "bytes" @@ -103,13 +103,13 @@ type Report struct { DurationUpstreamTotal float64 `json:"duration_upstream_total"` } -// NewServeWAF returns the server `smallwebwaf servewaf` runs. It answers +// New returns the server `smallwebwaf servewaf` runs. It answers // rproxy's questions, takes rproxy's reports and answers its own endpoints. It // passes nothing to an app. rproxy applies the size and time limits to the // visitor. Go's default limit on a request's line and headers, 1 MiB, stays: it // is far above rproxy's, and a question refused for its size would have rproxy // let the request through. -func NewServeWAF(params Params) *Server { +func New(params Params) *Server { h := newHandler(params) return &Server{ diff --git a/internal/proxy/servewaf_test.go b/internal/servewaf/servewaf_test.go similarity index 87% rename from internal/proxy/servewaf_test.go rename to internal/servewaf/servewaf_test.go index 55bf113..2c355e1 100644 --- a/internal/proxy/servewaf_test.go +++ b/internal/servewaf/servewaf_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "context" @@ -19,9 +19,9 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/bans" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/ratelimit" "sneak.berlin/go/smallwebwaf/internal/requestlog" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) const ( @@ -57,7 +57,7 @@ type serveWAF struct { t *testing.T addr string out *output - server *proxy.Server + server *servewaf.Server clock *clock queue *alerts.Queue } @@ -67,13 +67,13 @@ type serveWAF struct { func startServeWAF(t *testing.T, geojsURL string, env map[string]string) *serveWAF { t.Helper() - return startServeWAFWith(t, geojsURL, env, func(*proxy.Server) {}) + return startServeWAFWith(t, geojsURL, env, func(*servewaf.Server) {}) } // startServeWAFWith is startServeWAF with change applied to the server before // it starts. func startServeWAFWith( - t *testing.T, geojsURL string, env map[string]string, change func(*proxy.Server), + t *testing.T, geojsURL string, env map[string]string, change func(*servewaf.Server), ) *serveWAF { t.Helper() @@ -103,8 +103,8 @@ func (s *serveWAF) questionWith( req := newRequest(s.t, method, s.addr, path, body) req.Host = host - req.Header.Set(proxy.ClientHeader, from) - req.Header.Set(proxy.PeerHeader, rproxyPeer) + req.Header.Set(servewaf.ClientHeader, from) + req.Header.Set(servewaf.PeerHeader, rproxyPeer) req.Header.Set(requestIDHeader, rand.Text()) return req @@ -131,13 +131,13 @@ func (s *serveWAF) allowed(from string) string { s.t.Helper() req := s.question(siteA, from, "/") - wantDisposition(s.t, do(s.t, req), http.StatusOK, proxy.DispositionAllow) + wantDisposition(s.t, do(s.t, req), http.StatusOK, servewaf.DispositionAllow) return req.Header.Get(requestIDHeader) } // report sends rproxy's report and returns the answer. -func (s *serveWAF) report(report proxy.Report) answer { +func (s *serveWAF) report(report servewaf.Report) answer { s.t.Helper() body, err := json.Marshal(report) @@ -152,7 +152,7 @@ func (s *serveWAF) report(report proxy.Report) answer { func (s *serveWAF) reportBody(body string) answer { s.t.Helper() - return do(s.t, newRequest(s.t, http.MethodPost, s.addr, proxy.ReportPath, + return do(s.t, newRequest(s.t, http.MethodPost, s.addr, servewaf.ReportPath, strings.NewReader(body))) } @@ -161,7 +161,7 @@ func (s *serveWAF) reportBody(body string) answer { func (s *serveWAF) passedOn(from string) { s.t.Helper() - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: s.allowed(from), Status: http.StatusOK, RequestBytes: bodyBytes, @@ -182,7 +182,7 @@ func (s *serveWAF) history() ratelimit.History { func (s *serveWAF) metrics() string { s.t.Helper() - req := newRequest(s.t, http.MethodGet, s.addr, proxy.MetricsPath, http.NoBody) + req := newRequest(s.t, http.MethodGet, s.addr, servewaf.MetricsPath, http.NoBody) req.Header.Set("Authorization", bearer) got := do(s.t, req) @@ -196,7 +196,7 @@ func wantDisposition(t *testing.T, got answer, status int, disposition string) { t.Helper() wantStatus(t, got, status) - if answered := got.header.Get(proxy.DispositionHeader); answered != disposition { + if answered := got.header.Get(servewaf.DispositionHeader); answered != disposition { t.Errorf("answered with disposition %q, want %q", answered, disposition) } } @@ -248,10 +248,10 @@ func TestQuestionAllowedIsAnsweredAndLogged(t *testing.T) { req := s.question(siteA, client, "/page?q=1&token=visible") req.Header.Set(requestIDHeader, "the-id") req.Header.Set("X-Forwarded-Proto", "https") - req.Header.Set(proxy.ContentLengthHeader, "1234") + req.Header.Set(servewaf.ContentLengthHeader, "1234") got := do(t, req) - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) wantBody(t, got, "") if id := got.header.Get(requestIDHeader); id != "" { @@ -278,7 +278,7 @@ func TestIPv6VisitorIsCountedInItsGroup(t *testing.T) { s.allowed("2001:db8::1") got := s.ask("2001:db8::2", "/") - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) } func TestIPv4VisitorInIPv6FormIsTheIPv4Client(t *testing.T) { @@ -287,9 +287,9 @@ func TestIPv4VisitorInIPv6FormIsTheIPv4Client(t *testing.T) { s := startServeWAF(t, "", map[string]string{denyNets: client}) req := s.question(siteA, "::ffff:"+client, "/") - req.Header.Set(proxy.PeerHeader, "::ffff:"+rproxyPeer) + req.Header.Set(servewaf.PeerHeader, "::ffff:"+rproxyPeer) - wantDisposition(t, do(t, req), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, do(t, req), http.StatusForbidden, servewaf.DispositionDeny) line := s.out.requestLine(t) if line.ClientIP != client || line.PeerIP != rproxyPeer { @@ -305,13 +305,13 @@ func TestMalformedQuestionGets400AndAWarningAndNoLine(t *testing.T) { change func(*http.Request) }{ {"client not an address", func(req *http.Request) { - req.Header.Set(proxy.ClientHeader, "not an address") + req.Header.Set(servewaf.ClientHeader, "not an address") }}, {"client empty", func(req *http.Request) { - req.Header.Set(proxy.ClientHeader, "") + req.Header.Set(servewaf.ClientHeader, "") }}, {"client address with a zone", func(req *http.Request) { - req.Header.Set(proxy.ClientHeader, "2001:db8::1%eth0") + req.Header.Set(servewaf.ClientHeader, "2001:db8::1%eth0") }}, {"id absent", func(req *http.Request) { req.Header.Del(requestIDHeader) @@ -353,7 +353,7 @@ func TestLongestValidIDIsTaken(t *testing.T) { req := s.question(siteA, client, "/") req.Header.Set(requestIDHeader, strings.Repeat("x", 128)) - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) } func TestQuestionWithABodyOver64KiBGets400(t *testing.T) { @@ -361,15 +361,15 @@ func TestQuestionWithABodyOver64KiBGets400(t *testing.T) { s := startServeWAF(t, "", nil) - over := strings.NewReader(strings.Repeat("x", proxy.QuestionBodyMaxBytes+1)) + over := strings.NewReader(strings.Repeat("x", servewaf.QuestionBodyMaxBytes+1)) got := do(t, s.questionWith(http.MethodPost, siteA, client, "/", over)) wantDisposition(t, got, http.StatusBadRequest, "") wantProcessMessages(t, s, "a question is refused", 1) wantNoRequestLines(t, s) - whole := strings.NewReader(strings.Repeat("x", proxy.QuestionBodyMaxBytes)) + whole := strings.NewReader(strings.Repeat("x", servewaf.QuestionBodyMaxBytes)) got = do(t, s.questionWith(http.MethodPost, siteA, client, "/", whole)) - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) } func TestQuestionWithAHeldIDIsRefused(t *testing.T) { @@ -385,15 +385,15 @@ func TestQuestionWithAHeldIDIsRefused(t *testing.T) { wantProcessMessages(t, s, "a question is refused", 1) // A denied request's id is held as well. - denied := s.question(siteA, otherClient, proxy.HealthPath) - wantDisposition(t, do(t, denied), http.StatusNotFound, proxy.DispositionDeny) + denied := s.question(siteA, otherClient, servewaf.HealthPath) + wantDisposition(t, do(t, denied), http.StatusNotFound, servewaf.DispositionDeny) again = s.question(siteA, client, "/") again.Header.Set(requestIDHeader, denied.Header.Get(requestIDHeader)) wantDisposition(t, do(t, again), http.StatusBadRequest, "") // The report ends the holding, and the refused questions changed nothing. - got := s.report(proxy.Report{RequestID: id, ResponseBytes: answerBytes}) + got := s.report(servewaf.Report{RequestID: id, ResponseBytes: answerBytes}) wantStatus(t, got, http.StatusNoContent) if history := s.history(); history.ResponseBytes != answerBytes { @@ -402,7 +402,7 @@ func TestQuestionWithAHeldIDIsRefused(t *testing.T) { again = s.question(siteA, client, "/") again.Header.Set(requestIDHeader, id) - wantDisposition(t, do(t, again), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, do(t, again), http.StatusOK, servewaf.DispositionAllow) } func TestReportNeverReachesAQuestionStillBeingHandled(t *testing.T) { @@ -436,7 +436,7 @@ func TestReportNeverReachesAQuestionStillBeingHandled(t *testing.T) { // A report for a question not yet answered is ignored, and that question's // id is taken. - got := s.report(proxy.Report{RequestID: id, ResponseBytes: 1000}) + got := s.report(servewaf.Report{RequestID: id, ResponseBytes: 1000}) wantStatus(t, got, http.StatusNoContent) // This would wait on the same lookup were the id not taken. @@ -451,7 +451,7 @@ func TestReportNeverReachesAQuestionStillBeingHandled(t *testing.T) { select { case got := <-answered: - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) case <-time.After(waitLimit): t.Fatal("the question got no answer") } @@ -466,7 +466,7 @@ func TestReportNeverReachesAQuestionStillBeingHandled(t *testing.T) { } // The true report comes after, and is the one that counts. - got = s.report(proxy.Report{RequestID: id, ResponseBytes: answerBytes}) + got = s.report(servewaf.Report{RequestID: id, ResponseBytes: answerBytes}) wantStatus(t, got, http.StatusNoContent) if history := s.history(); history.ResponseBytes != answerBytes { @@ -484,13 +484,13 @@ func TestPeerAbsentOrUnreadableIsAnsweredAndLoggedInvalid(t *testing.T) { s := startServeWAF(t, "", nil) req := s.question(siteA, client, "/") - req.Header.Del(proxy.PeerHeader) + req.Header.Del(servewaf.PeerHeader) if peer != "" { - req.Header.Set(proxy.PeerHeader, peer) + req.Header.Set(servewaf.PeerHeader, peer) } - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) if line := s.out.requestLine(t); line.PeerIP != "invalid IP" { t.Errorf("log line has peer_ip %q, want invalid IP", line.PeerIP) @@ -511,8 +511,8 @@ func TestContentLengthUnreadableOrNegativeCountsAsAbsent(t *testing.T) { }) req := s.question(siteA, client, "/") - req.Header.Set(proxy.ContentLengthHeader, announced) - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + req.Header.Set(servewaf.ContentLengthHeader, announced) + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) if line := s.out.requestLine(t); line.ContentLength != 0 { t.Errorf("log line has content_length %d, want none", line.ContentLength) @@ -527,13 +527,13 @@ func TestHeaderSentTwiceUsesItsFirstValue(t *testing.T) { s := startServeWAF(t, "", nil) req := s.question(siteA, client, "/") - req.Header.Add(proxy.ClientHeader, otherClient) - req.Header.Add(proxy.PeerHeader, "172.18.0.3") + req.Header.Add(servewaf.ClientHeader, otherClient) + req.Header.Add(servewaf.PeerHeader, "172.18.0.3") req.Header.Set(requestIDHeader, "first") req.Header.Add(requestIDHeader, "second") - req.Header.Set(proxy.ContentLengthHeader, "1234") - req.Header.Add(proxy.ContentLengthHeader, "5678") - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + req.Header.Set(servewaf.ContentLengthHeader, "1234") + req.Header.Add(servewaf.ContentLengthHeader, "5678") + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) line := s.out.requestLine(t) if line.ClientIP != client || line.PeerIP != rproxyPeer || @@ -562,7 +562,7 @@ func sendQuestionWithPartOfItsBody(t *testing.T, conn net.Conn) { _, err := fmt.Fprintf(conn, "POST / HTTP/1.1\r\nHost: %s\r\n%s: %s\r\n%s: an-id\r\n"+ "Content-Length: 100\r\n\r\npart", - siteA, proxy.ClientHeader, client, requestIDHeader) + siteA, servewaf.ClientHeader, client, requestIDHeader) if err != nil { t.Fatalf("send the question: %v", err) } @@ -592,7 +592,7 @@ func TestQuestionWhoseBodyStopsArrivingGetsNoAnswer(t *testing.T) { t.Parallel() s := startServeWAFWith(t, "", map[string]string{metricsToken: token}, - func(server *proxy.Server) { server.ReadTimeout = readTimeout }) + func(server *servewaf.Server) { server.ReadTimeout = readTimeout }) conn := dial(t, s.addr) sendQuestionWithPartOfItsBody(t, conn) @@ -615,7 +615,7 @@ func TestQuestionWhoseBodyStopsArrivingGetsNoAnswer(t *testing.T) { // 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) + wantDisposition(t, do(t, again), http.StatusOK, servewaf.DispositionAllow) }) } } @@ -632,7 +632,7 @@ func TestQuestionDeniedGetsTheBanResponse(t *testing.T) { }) got := s.ask(client, "/") - wantDisposition(t, got, status, proxy.DispositionDeny) + wantDisposition(t, got, status, servewaf.DispositionDeny) wantBody(t, got, http.StatusText(status)+"\n") wantLine(t, s.out.requestLine(t), status, requestlog.ActionDenied) }) @@ -647,7 +647,7 @@ func TestBanResponseCloseIsAnAnswerOfItsOwn(t *testing.T) { }) got := s.ask(client, "/") - wantDisposition(t, got, http.StatusOK, proxy.DispositionClose) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionClose) wantBody(t, got, "") // The visitor is sent nothing. wantLine(t, s.out.requestLine(t), 0, requestlog.ActionDenied) @@ -664,12 +664,12 @@ func TestBlockRuleIsDeniedWith403AndABanRuleWithTheBanResponse(t *testing.T) { }) got := s.ask(client, "/blocked") - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) wantLine(t, s.out.requestLine(t), http.StatusForbidden, requestlog.ActionRuleBlocked) got = s.ask(client, "/.env") - wantDisposition(t, got, http.StatusTooManyRequests, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusTooManyRequests, servewaf.DispositionDeny) wantLine(t, s.out.requestLines(t, 2)[1], http.StatusTooManyRequests, requestlog.ActionBanned) } @@ -681,15 +681,17 @@ func TestQuestionUnderTheOwnPrefixIsDeniedWith404AfterTheChecks(t *testing.T) { metricsToken: token, denyNets: otherClient, }) - for _, path := range []string{proxy.HealthPath, proxy.MetricsPath, proxy.ReportPath} { + for _, path := range []string{ + servewaf.HealthPath, servewaf.MetricsPath, servewaf.ReportPath, + } { got := s.ask(client, path) - wantDisposition(t, got, http.StatusNotFound, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusNotFound, servewaf.DispositionDeny) } wantLine(t, s.out.requestLine(t), http.StatusNotFound, requestlog.ActionAdmin) - got := s.ask(otherClient, proxy.MetricsPath) - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + got := s.ask(otherClient, servewaf.MetricsPath) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) } func TestObserveModeAllowsAndLogsWhatEnforceModeWouldHaveDone(t *testing.T) { @@ -698,7 +700,7 @@ func TestObserveModeAllowsAndLogsWhatEnforceModeWouldHaveDone(t *testing.T) { s := startServeWAF(t, "", map[string]string{mode: observe, denyNets: client}) got := s.ask(client, "/") - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) line := s.out.requestLine(t) wantLine(t, line, http.StatusOK, requestlog.ActionForward) @@ -714,7 +716,7 @@ func TestObserveModeRefusalAnsweredAllowCountsForTheErrorBurst(t *testing.T) { for range 2 { got := s.ask(client, "/blocked") - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) } lines := s.out.requestLines(t, 2) @@ -733,7 +735,7 @@ func TestDeniedQuestionCountsForTheErrorBurst(t *testing.T) { for range 2 { got := s.ask(client, "/blocked") - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) } lines := s.out.requestLines(t, 2) @@ -762,7 +764,7 @@ func TestAllowCarriesTheLookupHeadersWhileTheSettingIsSet(t *testing.T) { }) got := s.ask(fromDE, "/") - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) asn, country := got.header.Get("X-Client-Asn"), got.header.Get("X-Client-Country") if asn != tc.wantASN || country != tc.wantCountry { @@ -787,7 +789,7 @@ func TestReportCountsTowardAByteLimitWhichThenBans(t *testing.T) { } // 100 bytes are over the limit of 99. - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: id, Status: http.StatusBadGateway, RequestBytes: bodyBytes, ResponseBytes: answerBytes, }) @@ -811,7 +813,7 @@ func TestReportCountsTowardAByteLimitWhichThenBans(t *testing.T) { } got = s.ask(client, "/") - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) } func TestReportCountsAtThePercentageItsQuestionWasGiven(t *testing.T) { @@ -825,10 +827,10 @@ func TestReportCountsAtThePercentageItsQuestionWasGiven(t *testing.T) { }) s.passedOn(fromKP) - wantDisposition(t, s.ask(fromKP, "/"), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, s.ask(fromKP, "/"), http.StatusOK, servewaf.DispositionAllow) s.passedOn(fromDE) - wantDisposition(t, s.ask(fromDE, "/"), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, s.ask(fromDE, "/"), http.StatusForbidden, servewaf.DispositionDeny) } func TestReportAddsTheRequestToItsClientsHistoryAndTheAnomalyCounters(t *testing.T) { @@ -846,7 +848,7 @@ func TestReportAddsTheRequestToItsClientsHistoryAndTheAnomalyCounters(t *testing t.Errorf("anomaly counters %v before the report, want none", counters) } - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: id, Status: http.StatusBadGateway, RequestBytes: bodyBytes, ResponseBytes: answerBytes, }) @@ -874,7 +876,7 @@ func TestDeniedRequestIsFinishedAtItsDecision(t *testing.T) { s.allowed(client) denied := s.question(siteA, client, "/") - wantDisposition(t, do(t, denied), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, do(t, denied), http.StatusForbidden, servewaf.DispositionDeny) counters := s.server.Anomalies.Snapshot() if len(counters) != 1 || counters[0].Minute.Current != 1 { @@ -891,7 +893,7 @@ func TestDeniedRequestIsFinishedAtItsDecision(t *testing.T) { wantMetric(t, s.metrics(), series, 1) - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: denied.Header.Get(requestIDHeader), Status: http.StatusForbidden, ResponseBytes: 1000, DurationTotal: 2, }) @@ -924,7 +926,7 @@ func TestMetricsCountAnAllowedRequestFromItsReport(t *testing.T) { wantMetric(t, metrics, `smallwebwaf_requests_in_flight{instance="`+alertInstance+`"}`, 1) // the scrape itself - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: id, Status: http.StatusBadGateway, RequestBytes: bodyBytes, ResponseBytes: answerBytes, DurationTotal: 5, DurationUpstreamTotal: 4, @@ -942,7 +944,7 @@ func TestMetricsCountAnAllowedRequestFromItsReport(t *testing.T) { // check needs a server that has not been scraped yet. unscraped := startServeWAF(t, "", map[string]string{metricsToken: token}) - got = unscraped.report(proxy.Report{ + got = unscraped.report(servewaf.Report{ RequestID: unscraped.allowed(client), Status: http.StatusBadGateway, DurationTotal: 5, }) wantStatus(t, got, http.StatusNoContent) @@ -955,7 +957,7 @@ func TestReportForARequestNotHeldIsIgnored(t *testing.T) { s := startServeWAF(t, "", map[string]string{bytesLimitPerMinute: byteLimit}) - got := s.report(proxy.Report{RequestID: "no-such-request", ResponseBytes: 1000}) + got := s.report(servewaf.Report{RequestID: "no-such-request", ResponseBytes: 1000}) wantStatus(t, got, http.StatusNoContent) if clients := s.server.Limiter.Snapshot(); len(clients) != 0 { @@ -972,7 +974,7 @@ func TestReportForARequestNotHeldIsIgnored(t *testing.T) { // A second report of the same request does not count it twice. id := s.allowed(client) for range 2 { - got = s.report(proxy.Report{RequestID: id, ResponseBytes: answerBytes}) + got = s.report(servewaf.Report{RequestID: id, ResponseBytes: answerBytes}) wantStatus(t, got, http.StatusNoContent) } @@ -1160,7 +1162,7 @@ func TestBytesAtTheirMostEachWayBanRatherThanOverflow(t *testing.T) { s := startServeWAF(t, "", map[string]string{bytesLimitPerMinute: byteLimit}) - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: s.allowed(client), Status: http.StatusOK, RequestBytes: reportedBytesLimit, ResponseBytes: reportedBytesLimit, }) @@ -1245,9 +1247,9 @@ func TestReportWritesTheLastLineWithTheDecisionsAndTheReportsFields(t *testing.T req.Header.Set("X-Forwarded-Proto", "https") id := req.Header.Get(requestIDHeader) - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: id, Status: http.StatusCreated, RequestBytes: bodyBytes, ResponseBytes: answerBytes, DurationTotal: 12.5, DurationUpstreamTotal: 10.25, @@ -1294,7 +1296,7 @@ func TestEveryDeniedRequestsReportGetsItsLastLine(t *testing.T) { "close", map[string]string{denyNets: client, banResponse: banResponseClose}, "/", 0, requestlog.ActionDenied, }, - {"own prefix", nil, proxy.HealthPath, http.StatusNotFound, requestlog.ActionAdmin}, + {"own prefix", nil, servewaf.HealthPath, http.StatusNotFound, requestlog.ActionAdmin}, } { t.Run(tc.name, func(t *testing.T) { t.Parallel() @@ -1305,7 +1307,7 @@ func TestEveryDeniedRequestsReportGetsItsLastLine(t *testing.T) { id := req.Header.Get(requestIDHeader) do(t, req) - got := s.report(proxy.Report{ + got := s.report(servewaf.Report{ RequestID: id, Status: tc.status, ResponseBytes: 8, DurationTotal: 3, }) wantStatus(t, got, http.StatusNoContent) @@ -1341,18 +1343,18 @@ func TestOldestAnsweredRequestIsDroppedPastTheBound(t *testing.T) { t.Fatalf("question: %v", err) } - wantDisposition(t, readAnswer(res), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, readAnswer(res), http.StatusOK, servewaf.DispositionAllow) } // The table is full again with next and those, without oldest. - got := s.report(proxy.Report{RequestID: oldest, ResponseBytes: answerBytes}) + got := s.report(servewaf.Report{RequestID: oldest, ResponseBytes: answerBytes}) wantStatus(t, got, http.StatusNoContent) if counted := s.history().Requests; counted != 0 { t.Errorf("history holds %d requests, want the dropped one not counted", counted) } - got = s.report(proxy.Report{RequestID: next, ResponseBytes: answerBytes}) + got = s.report(servewaf.Report{RequestID: next, ResponseBytes: answerBytes}) wantStatus(t, got, http.StatusNoContent) if counted := s.history().Requests; counted != 1 { @@ -1366,10 +1368,10 @@ func TestBanMadeThroughOneHostRefusesTheClientOnAnother(t *testing.T) { s := startServeWAF(t, "", map[string]string{rulesDir: writeRules(t, testRules)}) got := do(t, s.question(siteA, client, "/.env")) - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) got = do(t, s.question(siteB, client, "/")) - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) lines := s.out.requestLines(t, 2) if lines[0].Host != siteA || lines[1].Host != siteB { @@ -1395,7 +1397,7 @@ func TestRequestsOnEverySiteCountTowardOneSetOfLimits(t *testing.T) { s.allowed(client) got := do(t, s.question(siteB, client, "/")) - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) } func TestOwnEndpointsAnswerOnlyWithoutTheClientHeader(t *testing.T) { @@ -1410,13 +1412,13 @@ func TestOwnEndpointsAnswerOnlyWithoutTheClientHeader(t *testing.T) { path, authorization string want int }{ - {proxy.HealthPath, "", http.StatusOK}, - {proxy.MetricsPath, bearer, http.StatusOK}, - {proxy.MetricsPath, "", http.StatusUnauthorized}, - {proxy.BansPath, adminBearer, http.StatusOK}, - {proxy.BansPath, "", http.StatusUnauthorized}, - {proxy.ClientsPath + client, adminBearer, http.StatusOK}, - {proxy.ReportPath, "", http.StatusNotFound}, + {servewaf.HealthPath, "", http.StatusOK}, + {servewaf.MetricsPath, bearer, http.StatusOK}, + {servewaf.MetricsPath, "", http.StatusUnauthorized}, + {servewaf.BansPath, adminBearer, http.StatusOK}, + {servewaf.BansPath, "", http.StatusUnauthorized}, + {servewaf.ClientsPath + client, adminBearer, http.StatusOK}, + {servewaf.ReportPath, "", http.StatusNotFound}, {"/_smallwebwaf/other", "", http.StatusNotFound}, {"/", "", http.StatusNotFound}, } { @@ -1429,9 +1431,9 @@ func TestOwnEndpointsAnswerOnlyWithoutTheClientHeader(t *testing.T) { req.Header.Set("Authorization", tc.authorization) if strings.HasPrefix(tc.path, "/_smallwebwaf/") { - wantDisposition(t, do(t, req), http.StatusNotFound, proxy.DispositionDeny) + wantDisposition(t, do(t, req), http.StatusNotFound, servewaf.DispositionDeny) } else { - wantDisposition(t, do(t, req), http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, do(t, req), http.StatusOK, servewaf.DispositionAllow) } } } @@ -1441,10 +1443,11 @@ func TestOwnEndpointsLinesDoNotSayWhichOfTheTwoTheyAre(t *testing.T) { s := startServeWAF(t, "", nil) - wantStatus(t, get(t, s.addr, proxy.HealthPath), http.StatusOK) + wantStatus(t, get(t, s.addr, servewaf.HealthPath), http.StatusOK) line := s.out.requestLine(t) - if _, there := line.fields["servewaf_line"]; there || line.Path != proxy.HealthPath { + if _, there := line.fields["servewaf_line"]; there || + line.Path != servewaf.HealthPath { t.Errorf("the health check's log line is %+v", line.fields) } } @@ -1456,12 +1459,12 @@ func TestALargeBodyAnnouncedAndLongHeadersAreNoReasonToRefuse(t *testing.T) { req := s.questionWith(http.MethodPost, siteA, client, "/", strings.NewReader("a body")) - req.Header.Set(proxy.ContentLengthHeader, "1073741824") + req.Header.Set(servewaf.ContentLengthHeader, "1073741824") req.Header.Set(forwardedFor, otherClient) req.Header.Set("X-Long", strings.Repeat("x", 40<<10)) got := do(t, req) - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) line := s.out.requestLine(t) if line.ClientIP != client || line.ContentLength != 1<<30 { @@ -1529,7 +1532,7 @@ carried header:Content-Length block ^6$ strings.NewReader("a body")) if tc.announced != "" { - req.Header.Set(proxy.ContentLengthHeader, tc.announced) + req.Header.Set(servewaf.ContentLengthHeader, tc.announced) } wantStatus(t, do(t, req), tc.want) @@ -1547,19 +1550,19 @@ func TestCoreRuleSetReadsTheBodyAQuestionCarries(t *testing.T) { req := s.questionWith(http.MethodPost, siteA, client, "/", strings.NewReader(body)) req.Header.Set("Content-Type", contentType) - req.Header.Set(proxy.ContentLengthHeader, "100000") + req.Header.Set(servewaf.ContentLengthHeader, "100000") return do(t, req) } got := ask(formData, "id=1'%20OR%20'1'='1") - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) // The first 64 KiB of a longer JSON body cannot be parsed, which is no // attack, so the Core Rule Set does not read it. - cut := `{"text":"` + strings.Repeat("x", proxy.QuestionBodyMaxBytes) - got = ask("application/json", cut[:proxy.QuestionBodyMaxBytes]) - wantDisposition(t, got, http.StatusOK, proxy.DispositionAllow) + cut := `{"text":"` + strings.Repeat("x", servewaf.QuestionBodyMaxBytes) + got = ask("application/json", cut[:servewaf.QuestionBodyMaxBytes]) + wantDisposition(t, got, http.StatusOK, servewaf.DispositionAllow) } func TestCoreRuleSetAlertNamesTheSite(t *testing.T) { @@ -1568,7 +1571,7 @@ func TestCoreRuleSetAlertNamesTheSite(t *testing.T) { s := startServeWAF(t, "", map[string]string{wafMode: block}) got := s.ask(client, sqlInjection) - wantDisposition(t, got, http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, got, http.StatusForbidden, servewaf.DispositionDeny) waiting := s.queue.Snapshot().Waiting[alerts.DestinationWebhook] if len(waiting) != 1 || waiting[0].Event != alerts.EventWAFBlock || @@ -1583,7 +1586,7 @@ func TestReputationHitAlertNamesTheSite(t *testing.T) { s := startServeWAF(t, "", map[string]string{blocklistURLs: dropURL}) loadLists(t, s.server, map[string][]string{dropURL: {client}}) - wantDisposition(t, s.ask(client, "/"), http.StatusForbidden, proxy.DispositionDeny) + wantDisposition(t, s.ask(client, "/"), http.StatusForbidden, servewaf.DispositionDeny) waiting := s.queue.Snapshot().Waiting[alerts.DestinationWebhook] if len(waiting) != 1 || waiting[0].Event != alerts.EventReputationHit || diff --git a/internal/proxy/staticlists_test.go b/internal/servewaf/staticlists_test.go similarity index 99% rename from internal/proxy/staticlists_test.go rename to internal/servewaf/staticlists_test.go index 6f1712a..3ddffad 100644 --- a/internal/proxy/staticlists_test.go +++ b/internal/servewaf/staticlists_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" diff --git a/internal/proxy/trappaths_test.go b/internal/servewaf/trappaths_test.go similarity index 99% rename from internal/proxy/trappaths_test.go rename to internal/servewaf/trappaths_test.go index f8b1952..14be397 100644 --- a/internal/proxy/trappaths_test.go +++ b/internal/servewaf/trappaths_test.go @@ -1,4 +1,4 @@ -package proxy_test +package servewaf_test import ( "net/http" diff --git a/internal/smallwebwaf/healthcheck.go b/internal/smallwebwaf/healthcheck.go index c51df0b..a2465c6 100644 --- a/internal/smallwebwaf/healthcheck.go +++ b/internal/smallwebwaf/healthcheck.go @@ -11,7 +11,7 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/config" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" ) // healthCheckTimeout bounds the whole health check. @@ -107,7 +107,7 @@ func checkRProxyAndApp( // listenAddr, which the settings have checked has one. func askHealthEndpoint(ctx context.Context, listenAddr string) error { _, port, _ := net.SplitHostPort(listenAddr) - health := "http://" + net.JoinHostPort("127.0.0.1", port) + proxy.HealthPath + health := "http://" + net.JoinHostPort("127.0.0.1", port) + servewaf.HealthPath req, err := http.NewRequestWithContext(ctx, http.MethodGet, health, http.NoBody) if err != nil { diff --git a/internal/smallwebwaf/rproxy_test.go b/internal/smallwebwaf/rproxy_test.go index 6dca918..228fc31 100644 --- a/internal/smallwebwaf/rproxy_test.go +++ b/internal/smallwebwaf/rproxy_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" "sneak.berlin/go/smallwebwaf/internal/smallwebwaf" ) @@ -153,7 +153,7 @@ func TestABanThroughOneSiteRefusesTheClientOnTheOther(t *testing.T) { // One request's three lines carry one id, rproxy's with servewaf's answer. line := firstOut.line(t, "path", bannedProbe) - if line["disposition"] != proxy.DispositionDeny || line["client_ip"] != placed { + if line["disposition"] != servewaf.DispositionDeny || line["client_ip"] != placed { t.Errorf("rproxy's line is %v", line) } @@ -225,7 +225,7 @@ func TestTheSiteAnswersWhileServeWAFIsStoppedAndStartedAgain(t *testing.T) { wantStatus(t, site+"/after-stop", placed, http.StatusOK) line := out.line(t, "path", "/after-stop") - if line["disposition"] != proxy.DispositionAllow || + if line["disposition"] != servewaf.DispositionAllow || line["default_disposition"] != true { t.Errorf("rproxy's line is %v", line) } @@ -318,7 +318,7 @@ func TestAChunkedBodyStaysABodyAtServeWAF(t *testing.T) { const smuggledID = "smuggled-request" smuggled := "GET " + bannedProbe + " HTTP/1.1\r\nHost: site.example\r\n" + - proxy.ClientHeader + ": " + otherClient + "\r\n" + + servewaf.ClientHeader + ": " + otherClient + "\r\n" + "X-Request-ID: " + smuggledID + "\r\n\r\n" status := sendRaw(t, site, "POST /upload HTTP/1.1\r\nHost: site.example\r\n"+ @@ -459,10 +459,10 @@ func TestRProxyAsksAServeWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) { protocols := make(chan string, 10) backend := httptest.NewUnstartedServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get(proxy.ClientHeader) != "" { + if r.Header.Get(servewaf.ClientHeader) != "" { protocols <- r.Proto - w.Header().Set(proxy.DispositionHeader, proxy.DispositionDeny) + w.Header().Set(servewaf.DispositionHeader, servewaf.DispositionDeny) w.WriteHeader(http.StatusForbidden) } })) @@ -532,8 +532,8 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) { release := make(chan struct{}) backend := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get(proxy.ClientHeader) != "" { - w.Header().Set(proxy.DispositionHeader, proxy.DispositionAllow) + if r.Header.Get(servewaf.ClientHeader) != "" { + w.Header().Set(servewaf.DispositionHeader, servewaf.DispositionAllow) return } @@ -640,7 +640,7 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) { backend := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set(proxy.DispositionHeader, proxy.DispositionAllow) + w.Header().Set(servewaf.DispositionHeader, servewaf.DispositionAllow) })) t.Cleanup(backend.Close) @@ -757,7 +757,7 @@ func TestMainRunsRProxy(t *testing.T) { addr, cmd, out := startRProxyProcess(t, wafBackend+"=http://127.0.0.1:9") req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, - "http://"+addr+proxy.HealthPath, http.NoBody) + "http://"+addr+servewaf.HealthPath, http.NoBody) if err != nil { t.Fatalf("new request: %v", err) } diff --git a/internal/smallwebwaf/servewaf_test.go b/internal/smallwebwaf/servewaf_test.go index 6259523..affcd19 100644 --- a/internal/smallwebwaf/servewaf_test.go +++ b/internal/smallwebwaf/servewaf_test.go @@ -14,7 +14,7 @@ import ( "testing" "time" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" "sneak.berlin/go/smallwebwaf/internal/smallwebwaf" ) @@ -67,7 +67,7 @@ func TestMainRunsServeWAF(t *testing.T) { }) addr, _ := out.line(t, "msg", "starting")["address"].(string) - ask(t, addr, "/", http.StatusOK, proxy.DispositionAllow) + ask(t, addr, "/", http.StatusOK, servewaf.DispositionAllow) err = cmd.Process.Signal(syscall.SIGTERM) if err != nil { @@ -132,7 +132,7 @@ func ask(t *testing.T, addr, path string, status int, disposition string) { t.Fatalf("new request: %v", err) } - req.Header.Set(proxy.ClientHeader, placed) + req.Header.Set(servewaf.ClientHeader, placed) req.Header.Set("X-Request-ID", rand.Text()) transport := &http.Transport{} @@ -147,9 +147,9 @@ func ask(t *testing.T, addr, path string, status int, disposition string) { _ = res.Body.Close() if res.StatusCode != status || - res.Header.Get(proxy.DispositionHeader) != disposition { + res.Header.Get(servewaf.DispositionHeader) != disposition { t.Errorf("answered %d with disposition %q, want %d with %q", res.StatusCode, - res.Header.Get(proxy.DispositionHeader), status, disposition) + res.Header.Get(servewaf.DispositionHeader), status, disposition) } } @@ -193,12 +193,12 @@ func TestServeWAFKeepsItsBansAcrossARestartAndNeedsNoApp(t *testing.T) { } runServeWAF(t, env, func(addr string) { - ask(t, addr, "/", http.StatusOK, proxy.DispositionAllow) + ask(t, addr, "/", http.StatusOK, servewaf.DispositionAllow) // The default rule file bans a probe for this. - ask(t, addr, "/.env", http.StatusForbidden, proxy.DispositionDeny) + ask(t, addr, "/.env", http.StatusForbidden, servewaf.DispositionDeny) }) runServeWAF(t, env, func(addr string) { - ask(t, addr, "/", http.StatusForbidden, proxy.DispositionDeny) + ask(t, addr, "/", http.StatusForbidden, servewaf.DispositionDeny) }) } diff --git a/internal/smallwebwaf/smallwebwaf.go b/internal/smallwebwaf/smallwebwaf.go index 947cce1..14a30d9 100644 --- a/internal/smallwebwaf/smallwebwaf.go +++ b/internal/smallwebwaf/smallwebwaf.go @@ -23,11 +23,11 @@ import ( "sneak.berlin/go/smallwebwaf/internal/alerts" "sneak.berlin/go/smallwebwaf/internal/config" "sneak.berlin/go/smallwebwaf/internal/lookup" - "sneak.berlin/go/smallwebwaf/internal/proxy" "sneak.berlin/go/smallwebwaf/internal/remotelog" "sneak.berlin/go/smallwebwaf/internal/reputation" "sneak.berlin/go/smallwebwaf/internal/requestlog" "sneak.berlin/go/smallwebwaf/internal/rules" + "sneak.berlin/go/smallwebwaf/internal/servewaf" "sneak.berlin/go/smallwebwaf/internal/state" ) @@ -190,7 +190,7 @@ func RunServeWAF(ctx context.Context, params Params) int { func newServer( cfg *config.Config, stdout io.Writer, processLog *slog.Logger, now func() time.Time, ruleFiles *rules.Files, alertQueue *alerts.Queue, -) (*proxy.Server, error) { +) (*servewaf.Server, error) { var lookupFile *lookup.File if cfg.LookupSource == "file" { @@ -207,7 +207,7 @@ func newServer( } } - server := proxy.NewServeWAF(proxy.Params{ + server := servewaf.New(servewaf.Params{ Config: cfg, RequestLog: stdout, ProcessLog: processLog, @@ -230,7 +230,7 @@ func newServer( // loadStateFiles reads the state files into the parts of server and into // alertQueue, as state.Load does. func loadStateFiles( - cfg *config.Config, server *proxy.Server, alertQueue *alerts.Queue, + cfg *config.Config, server *servewaf.Server, alertQueue *alerts.Queue, now func() time.Time, processLog *slog.Logger, ) (*state.Files, error) { return state.Load(state.Params{ @@ -316,7 +316,7 @@ func startSending( // shutdownTimeout to finish, and writes every state file, alerts.json with // the alerts still waiting. func serve( - ctx context.Context, server *proxy.Server, listener net.Listener, + ctx context.Context, server *servewaf.Server, listener net.Listener, files *state.Files, ruleFiles *rules.Files, alertQueue *alerts.Queue, processLog *slog.Logger, ) int { diff --git a/internal/smallwebwaf/smallwebwaf_test.go b/internal/smallwebwaf/smallwebwaf_test.go index 44d560c..4fab2ff 100644 --- a/internal/smallwebwaf/smallwebwaf_test.go +++ b/internal/smallwebwaf/smallwebwaf_test.go @@ -20,7 +20,7 @@ import ( "time" "sneak.berlin/go/smallwebwaf/internal/lookup/lookuptest" - "sneak.berlin/go/smallwebwaf/internal/proxy" + "sneak.berlin/go/smallwebwaf/internal/servewaf" "sneak.berlin/go/smallwebwaf/internal/smallwebwaf" ) @@ -1627,7 +1627,7 @@ func statusFrom(t *testing.T, url, from string) int { } req.Header.Set("X-Forwarded-For", from) - req.Header.Set(proxy.ClientHeader, from) + req.Header.Set(servewaf.ClientHeader, from) req.Header.Set("X-Request-ID", rand.Text()) transport := &http.Transport{}