Rename internal/proxy to internal/servewaf (closes #173)
check / check (push) Successful in 3m44s

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
This commit was merged in pull request #201.
This commit is contained in:
2026-11-06 20:02:17 +01:00
parent 3cb41f27a8
commit a63b7e4d2d
50 changed files with 491 additions and 489 deletions
+61 -61
View File
@@ -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",
})
+51 -51
View File
@@ -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)
}
+3 -3
View File
@@ -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,
+5 -5
View File
@@ -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)
+80 -80
View File
@@ -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, "<html>")
})
_, _, 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, "<html>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",
+11 -11
View File
@@ -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)