Rename the program to sww and its deciding daemon to discriminator (closes #218)
check / check (push) Failing after 2m47s

The program is now sww, built from cmd/sww, with two modes: sww rproxy and
sww discriminator. servewaf becomes discriminator everywhere it names that
daemon: the package (internal/discriminator), its types, functions, files, the
discriminator_line log field, and its docs and tests.

smallwebwaf is left only where sneak's directive allows it: the documents'
product name, the git.eeqj.de/sneak/smallwebwaf URLs, and the Go module path
sneak.berlin/go/smallwebwaf. Everywhere else a name containing it takes the sww
form: the package (internal/sww), metric names (sww_*), the X-Sww-* headers,
the /_sww/ admin endpoints, the OS user, and the state and rules directories.
The SWWAF_ environment variables take the SWW_ form.

No logic changes. make check passes.

Model: deepseek-v4-pro
This commit is contained in:
sneak committed 2026-11-07 05:27:58 +00:00
1 parent 2e2f6f7175
commit 4a030557f3
106 files changed
+3302 -3275

No files matched your search

+2 -2
View File
@@ -18,7 +18,7 @@ const (
)
// peerAddress is the address of the request's TCP peer, normally traefik, without
// any IPv6 zone, which servewaf refuses.
// any IPv6 zone, which discriminator refuses.
func peerAddress(r *http.Request) netip.Addr {
addrPort, err := netip.ParseAddrPort(r.RemoteAddr)
if err != nil {
@@ -34,7 +34,7 @@ func peerAddress(r *http.Request) netip.Addr {
// address outside them is the visitor; if every address in it is inside, the
// leftmost is, and with no header, the peer. An entry that is not an address
// ends the reading, since nothing to its left can be believed. The visitor's
// address never has an IPv6 zone, which servewaf refuses.
// address never has an IPv6 zone, which discriminator refuses.
func clientAddress(
peer netip.Addr, forwardedFor []string, trusted []netip.Prefix,
) netip.Addr {
@@ -11,15 +11,15 @@ import (
"net/http"
"strings"
"sneak.berlin/go/smallwebwaf/internal/servewaf"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
)
// answerBodyMaxBytes is the most of the body of servewaf's answer that is read.
// answerBodyMaxBytes is the most of the body of discriminator's answer that is read.
// It is far above any answer's.
const answerBodyMaxBytes = 1 << 20
// reportAnswerMaxBytes is the most of the answer to a report that is read,
// which has no body when servewaf takes the report.
// which has no body when discriminator takes the report.
const reportAnswerMaxBytes = 4 << 10
var (
@@ -27,21 +27,22 @@ var (
errReportRefused = errors.New("the report was answered")
)
// servewafAnswer is what servewaf answered to a question.
type servewafAnswer struct {
// discriminatorAnswer is what discriminator answered to a question.
type discriminatorAnswer struct {
status int
header http.Header
body []byte
}
// noteAnswer notes what servewaf answered. An answer without a valid disposition
// notes nothing, and no report is due for it: servewaf holds no decision to report
// noteAnswer notes what discriminator answered. An answer without a valid disposition
// notes nothing, and no report is due for it: discriminator holds no decision to report
// on.
func (rq *request) noteAnswer(answer *servewafAnswer) {
disposition := answer.header.Get(servewaf.DispositionHeader)
func (rq *request) noteAnswer(answer *discriminatorAnswer) {
disposition := answer.header.Get(discriminator.DispositionHeader)
switch disposition {
case servewaf.DispositionAllow, servewaf.DispositionDeny, servewaf.DispositionClose:
case discriminator.DispositionAllow, discriminator.DispositionDeny,
discriminator.DispositionClose:
rq.disposition = disposition
rq.answered = true
rq.asn = answer.header.Get(asnHeader)
@@ -54,27 +55,27 @@ func (rq *request) noteAnswer(answer *servewafAnswer) {
func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) {
switch {
case ctx.Err() != nil:
// The visitor went away while servewaf was being asked. servewaf has not
// The visitor went away while discriminator was being asked. discriminator has not
// failed, and nothing is noted. This panic has Go's server close the
// connection without an answer, and log nothing; the deferred finish still
// runs.
panic(http.ErrAbortHandler)
case errors.Is(err, errCannotPassOn):
rq.h.processLog.Warn("servewaf's answer is dropped", "error", err.Error())
rq.h.processLog.Warn("discriminator's answer is dropped", "error", err.Error())
rq.answer(http.StatusBadGateway)
default:
// servewaf cannot be connected to, does not answer at all, or does not answer
// within SWRP_TIMEOUT_MS. A request that keeps servewaf busy past
// SWRP_TIMEOUT_MS, like any request while servewaf is overloaded, gets
// discriminator cannot be connected to, does not answer at all, or does not answer
// within SWRP_TIMEOUT_MS. A request that keeps discriminator busy past
// SWRP_TIMEOUT_MS, like any request while discriminator is overloaded, gets
// SWRP_DEFAULT_DISPOSITION here. deny closes this, at the cost of answering 503
// whenever servewaf is slow. Making such requests hard to send is
// whenever discriminator is slow. Making such requests hard to send is
// https://git.eeqj.de/sneak/smallwebwaf/issues/182.
rq.h.processLog.Warn("servewaf gave no answer", "error", err.Error())
rq.h.processLog.Warn("discriminator gave no answer", "error", err.Error())
rq.disposition = rq.h.config.DefaultDisposition
rq.byDefault = true
if rq.disposition == servewaf.DispositionAllow {
if rq.disposition == discriminator.DispositionAllow {
rq.passToApp(ctx)
} else {
rq.answer(http.StatusServiceUnavailable)
@@ -82,10 +83,10 @@ func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) {
}
}
// ask sends servewaf the question, and returns its answer, which must have come
// ask sends discriminator the question, and returns its answer, which must have come
// whole within SWRP_TIMEOUT_MS. It ends when ctx does, as when the visitor goes
// away.
func (rq *request) ask(ctx context.Context) (*servewafAnswer, error) {
func (rq *request) ask(ctx context.Context) (*discriminatorAnswer, error) {
ctx, cancel := context.WithTimeout(ctx, rq.h.config.Timeout)
defer cancel()
@@ -94,7 +95,7 @@ func (rq *request) ask(ctx context.Context) (*servewafAnswer, error) {
return nil, err
}
res, err := rq.h.serveWAFTransport.RoundTrip(question)
res, err := rq.h.discriminatorTransport.RoundTrip(question)
if err != nil {
return nil, fmt.Errorf("send the question: %w", err)
}
@@ -119,17 +120,17 @@ func (rq *request) ask(ctx context.Context) (*servewafAnswer, error) {
return nil, fmt.Errorf("%w: its body is over 1 MiB", errCannotPassOn)
}
return &servewafAnswer{res.StatusCode, res.Header, body}, nil
return &discriminatorAnswer{res.StatusCode, res.Header, body}, nil
}
// canBeAsked reports whether Go's client can send servewaf r as the visitor sent
// canBeAsked reports whether Go's client can send discriminator r as the visitor sent
// it. It cannot send these as they came:
// - a target that does not start with /, or that starts with //;
// - an empty Host, for which it sends servewaf's own address;
// - an empty Host, for which it sends discriminator's own address;
// - a Host holding an IPv6 zone, which it removes;
// - more than one User-Agent, of which it sends the first;
// - an empty User-Agent, which it leaves out, the one case that still goes to
// servewaf.
// discriminator.
func canBeAsked(r *http.Request) bool {
target, host := r.RequestURI, r.Host
hostHasZone := strings.HasPrefix(host, "[") && strings.Contains(host, "%")
@@ -169,13 +170,13 @@ func (rq *request) newQuestion(ctx context.Context) (*http.Request, error) {
question.TransferEncoding = in.TransferEncoding
question.Header = in.Header.Clone()
question.Header.Set(servewaf.ClientHeader, rq.client.String())
question.Header.Set(servewaf.PeerHeader, rq.peer.String())
question.Header.Del(servewaf.ContentLengthHeader)
question.Header.Set(discriminator.ClientHeader, rq.client.String())
question.Header.Set(discriminator.PeerHeader, rq.peer.String())
question.Header.Del(discriminator.ContentLengthHeader)
announced := in.Header.Get("Content-Length")
if announced != "" {
question.Header.Set(servewaf.ContentLengthHeader, announced)
question.Header.Set(discriminator.ContentLengthHeader, announced)
}
question.Header.Set(requestIDHeader, rq.id)
@@ -190,13 +191,13 @@ func (rq *request) newQuestion(ctx context.Context) (*http.Request, error) {
return question, nil
}
// answerWith gives the visitor servewaf's answer, less its disposition.
func (rq *request) answerWith(answer *servewafAnswer) {
// answerWith gives the visitor discriminator's answer, less its disposition.
func (rq *request) answerWith(answer *discriminatorAnswer) {
rq.prepareOwnAnswer(rq.requestDeadline())
header := rq.out.Header()
maps.Copy(header, answer.header)
header.Del(servewaf.DispositionHeader)
header.Del(discriminator.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.
@@ -207,10 +208,10 @@ func (rq *request) answerWith(answer *servewafAnswer) {
rq.send(answer.status, answer.body)
}
// sendReport sends servewaf report on a goroutine of its own, so that the
// sendReport sends discriminator 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 *servewaf.Report) {
func (h *handler) sendReport(ctx context.Context, report *discriminator.Report) {
h.reportsMu.Lock()
defer h.reportsMu.Unlock()
@@ -226,9 +227,9 @@ func (h *handler) sendReport(ctx context.Context, report *servewaf.Report) {
})
}
// postReport posts report to servewaf, and waits for its answer for up to
// postReport posts report to discriminator, and waits for its answer for up to
// SWRP_TIMEOUT_MS.
func (h *handler) postReport(ctx context.Context, report *servewaf.Report) error {
func (h *handler) postReport(ctx context.Context, report *discriminator.Report) error {
body, err := json.Marshal(report)
if err != nil {
return fmt.Errorf("encode the report: %w", err)
@@ -245,7 +246,7 @@ func (h *handler) postReport(ctx context.Context, report *servewaf.Report) error
req.Header.Set("Content-Type", "application/json")
res, err := h.serveWAFTransport.RoundTrip(req)
res, err := h.discriminatorTransport.RoundTrip(req)
if err != nil {
return fmt.Errorf("send the report: %w", err)
}
+31 -31
View File
@@ -16,9 +16,9 @@ import (
"time"
"sneak.berlin/go/smallwebwaf/internal/config"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
"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.
@@ -58,7 +58,7 @@ func TestServerHasTheFixedLimits(t *testing.T) {
}
}
func TestABodyAnnouncedOverItsLimitIsRefusedOnceServeWAFAllowsIt(t *testing.T) {
func TestABodyAnnouncedOverItsLimitIsRefusedOnceDiscriminatorAllowsIt(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
@@ -71,7 +71,7 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceServeWAFAllowsIt(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(http.ResponseWriter, *http.Request) {
t.Error("the app was asked")
})
@@ -100,7 +100,7 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceServeWAFAllowsIt(t *testing.T) {
func TestADenyOutranksTheBodySizeLimit(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
maxRequestSize: "1K",
@@ -109,7 +109,7 @@ func TestADenyOutranksTheBodySizeLimit(t *testing.T) {
got := visit(t, newRequest(t, http.MethodPost, addr, "/",
bytes.NewReader(make([]byte, 1025))))
if got.status != http.StatusForbidden {
t.Errorf("answered %+v, want servewaf's 403", got)
t.Errorf("answered %+v, want discriminator's 403", got)
}
}
@@ -132,7 +132,7 @@ func TestABodyNotAnnouncedOverItsLimitIsRefused(t *testing.T) {
var asked atomic.Bool
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
asked.Store(true)
greet(w, r)
@@ -165,7 +165,7 @@ func TestAResponseOverItsLimit(t *testing.T) {
t.Run("announced, is refused", func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(contentLengthHeader, strconv.Itoa(len(large)))
_, _ = io.WriteString(w, large)
@@ -187,7 +187,7 @@ func TestAResponseOverItsLimit(t *testing.T) {
t.Run("not announced, is cut off", func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, large[:1])
_ = http.NewResponseController(w).Flush()
@@ -217,7 +217,7 @@ func TestAHeadWhoseAnswerAnnouncesMoreThanTheResponseLimitIsPassedOn(t *testing.
const announced = "2048"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(contentLengthHeader, announced)
})
@@ -240,7 +240,7 @@ func TestASettingOfOffSetsNoLimit(t *testing.T) {
t.Run(setting, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
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, servewaf.HealthPath); got.status != http.StatusOK {
if got := get(t, addr, discriminator.HealthPath); got.status != http.StatusOK {
t.Errorf("answered %+v, want rproxy's own 200", got)
}
})
@@ -270,7 +270,7 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
maxRequestTime: "500",
@@ -289,7 +289,7 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) {
}
if len(standIn.questions) != 0 {
t.Error("servewaf was asked")
t.Error("discriminator was asked")
}
standIn.wantNoReport(t)
@@ -300,7 +300,7 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) {
func TestAVisitorThatGoesAwayBeforeTheFirstPartIsNotAsked(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, nil)
@@ -314,7 +314,7 @@ func TestAVisitorThatGoesAwayBeforeTheFirstPartIsNotAsked(t *testing.T) {
}
if len(standIn.questions) != 0 {
t.Error("servewaf was asked")
t.Error("discriminator was asked")
}
}
@@ -334,7 +334,7 @@ func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, nil)
@@ -365,7 +365,7 @@ func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(t *testing.T) {
}
if len(standIn.questions) != 0 {
t.Error("servewaf was asked")
t.Error("discriminator was asked")
}
})
}
@@ -404,7 +404,7 @@ func TestABodyLeftUnreadIsReadOnlyUntilTheRequestTimeLimit(t *testing.T) {
announced = 100 * questionBodyMaxBytes
)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
@@ -448,13 +448,13 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
name, target string
status int
}{
{"servewaf's deny", "/", http.StatusForbidden},
{"discriminator's deny", "/", http.StatusForbidden},
{"rproxy's own 400", "x:admin", http.StatusBadRequest},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, greet)
_, _, server := startServer(t, standIn.URL, app.URL, map[string]string{
maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10),
@@ -484,7 +484,7 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
func TestAVisitorTooSlowToSendTheRestIsAnswered408(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
maxRequestTime: "3000",
@@ -553,7 +553,7 @@ func TestAnAppTooSlowToTakeTheRequestIsAnswered504(t *testing.T) {
writeSize = 32 << 10
)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
addr, _ := startRProxy(t, standIn.URL, startAppThatTakesNothing(t),
map[string]string{maxRequestTime: "3000"})
@@ -580,7 +580,7 @@ func TestAnAppTooSlowToAnswerIsAnswered504(t *testing.T) {
t.Parallel()
release := make(chan struct{})
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(http.ResponseWriter, *http.Request) {
<-release
})
@@ -607,7 +607,7 @@ func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) {
const start = "the start"
release := make(chan struct{})
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, start)
_ = http.NewResponseController(w).Flush()
@@ -656,7 +656,7 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) {
writeSize = 32 << 10
)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
part := make([]byte, writeSize)
@@ -693,7 +693,7 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) {
func TestAnAppThatCannotBeReachedIsAnswered502WithAReport(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
addr, _ := startRProxy(t, standIn.URL, nowhere, nil)
got := get(t, addr, "/")
@@ -709,7 +709,7 @@ func TestAnAppThatCannotBeReachedIsAnswered502WithAReport(t *testing.T) {
func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, greeting)
})
@@ -732,7 +732,7 @@ func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) {
t.Parallel()
working := make(chan struct{})
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(_ http.ResponseWriter, r *http.Request) {
close(working)
<-r.Context().Done()
@@ -773,7 +773,7 @@ func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(t *testing
const answer = "the end of a long answer"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
_, _ = io.Copy(io.Discard, r.Body)
_ = http.NewResponseController(w).Flush()
@@ -802,14 +802,14 @@ func TestAVisitorThatStopsSendingGetsAnAnswerRProxyGivesByTheDeadline(t *testing
{"a deny", post, http.StatusForbidden},
{
"the health check",
"GET " + servewaf.HealthPath + " HTTP/1.1\r\nHost: site.example\r\n",
"GET " + discriminator.HealthPath + " HTTP/1.1\r\nHost: site.example\r\n",
http.StatusOK,
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(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.
+35 -34
View File
@@ -12,11 +12,11 @@ import (
"strings"
"testing"
"sneak.berlin/go/smallwebwaf/internal/servewaf"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
)
// questionBodyMaxBytes is the most of a body a question carries.
const questionBodyMaxBytes = servewaf.QuestionBodyMaxBytes
const questionBodyMaxBytes = discriminator.QuestionBodyMaxBytes
// chunked is the encoding of a body whose length the visitor did not announce.
const chunked = "chunked"
@@ -29,7 +29,7 @@ const pathGoReencodes = "/some%2Fpath/with%20space|pipe"
func TestQuestionIsTheVisitorsRequestWithRProxysHeaders(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
@@ -42,9 +42,9 @@ 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(servewaf.ClientHeader, "198.51.100.2")
req.Header.Set(servewaf.PeerHeader, "198.51.100.3")
req.Header.Set(servewaf.ContentLengthHeader, "12345")
req.Header.Set(discriminator.ClientHeader, "198.51.100.2")
req.Header.Set(discriminator.PeerHeader, "198.51.100.3")
req.Header.Set(discriminator.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
@@ -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(servewaf.ClientHeader, localhost)
want.Set(servewaf.PeerHeader, localhost)
want.Set(discriminator.ClientHeader, localhost)
want.Set(discriminator.PeerHeader, localhost)
if !maps.EqualFunc(asked.header, want, slices.Equal) {
t.Errorf("the question's headers are %v, want %v", asked.header, want)
@@ -93,7 +93,7 @@ func TestQuestionNamesTheVisitorATrustedProxySpeaksFor(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
forwarded := make(chan http.Header, 1)
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
forwarded <- r.Header.Clone()
@@ -110,8 +110,8 @@ func TestQuestionNamesTheVisitorATrustedProxySpeaksFor(t *testing.T) {
wantGreeting(t, visit(t, req))
asked := standIn.question(t)
if asked.header.Get(servewaf.ClientHeader) != tc.client ||
asked.header.Get(servewaf.PeerHeader) != localhost ||
if asked.header.Get(discriminator.ClientHeader) != tc.client ||
asked.header.Get(discriminator.PeerHeader) != localhost ||
asked.header.Get(forwardedForHeader) != visitor ||
asked.header.Get(forwardedProtoHeader) != tc.proto {
t.Errorf("the question's headers are %v", asked.header)
@@ -157,7 +157,7 @@ func TestQuestionNamesTheClientAsTheForwardedAddressesSay(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
trustedProxies: localhost + "/32,10.0.0.0/8",
@@ -167,7 +167,8 @@ func TestQuestionNamesTheClientAsTheForwardedAddressesSay(t *testing.T) {
req.Header[forwardedForHeader] = tc.header
wantGreeting(t, visit(t, req))
if got := standIn.question(t).header.Get(servewaf.ClientHeader); got != tc.client {
if got := standIn.question(t).header.Get(discriminator.ClientHeader); got !=
tc.client {
t.Errorf("the question names %s, want %s", got, tc.client)
}
})
@@ -182,7 +183,7 @@ func randomBytes(n int) []byte {
return body
}
func TestFirst64KiBReachServeWAFAndTheWholeBodyTheApp(t *testing.T) {
func TestFirst64KiBReachDiscriminatorAndTheWholeBodyTheApp(t *testing.T) {
t.Parallel()
for _, size := range []int{
@@ -197,7 +198,7 @@ func TestFirst64KiBReachServeWAFAndTheWholeBodyTheApp(t *testing.T) {
t.Parallel()
body := randomBytes(size)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
received := make(chan []byte, 1)
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
whole, _ := io.ReadAll(r.Body)
@@ -243,7 +244,7 @@ func wantQuestionBody(
len(want))
}
values := asked.header.Values(servewaf.ContentLengthHeader)
values := asked.header.Values(discriminator.ContentLengthHeader)
if announced {
if !slices.Equal(values, []string{strconv.Itoa(size)}) ||
@@ -264,7 +265,7 @@ func wantQuestionBody(
func TestDenyNeedsOnlyTheFirstPartOfALargeBody(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
@@ -275,7 +276,7 @@ func TestDenyNeedsOnlyTheFirstPartOfALargeBody(t *testing.T) {
strings.Repeat("x", questionBodyMaxBytes))
if status := readStatus(t, conn); status != http.StatusForbidden {
t.Errorf("status %d, want servewaf's 403", status)
t.Errorf("status %d, want discriminator's 403", status)
}
if report := standIn.report(t); report.RequestBytes != questionBodyMaxBytes {
@@ -306,14 +307,14 @@ func TestAQuestionCarriesItsBodyWhateverTheMethod(t *testing.T) {
t.Run(method+" "+tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(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)
if status := readStatus(t, conn); status != http.StatusForbidden {
t.Errorf("status %d, want servewaf's 403", status)
t.Errorf("status %d, want discriminator's 403", status)
}
asked := standIn.question(t)
@@ -334,7 +335,7 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) {
t.Run(method, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(http.ResponseWriter, *http.Request) {
t.Error("the app was asked")
})
@@ -353,7 +354,7 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) {
}
if len(standIn.questions) != 0 {
t.Error("servewaf was asked")
t.Error("discriminator was asked")
}
standIn.wantNoReport(t)
@@ -361,7 +362,7 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) {
}
}
func TestEachMethodPassedOnReachesServeWAFAndTheApp(t *testing.T) {
func TestEachMethodPassedOnReachesDiscriminatorAndTheApp(t *testing.T) {
t.Parallel()
for _, method := range []string{
@@ -372,7 +373,7 @@ func TestEachMethodPassedOnReachesServeWAFAndTheApp(t *testing.T) {
t.Parallel()
reached := make(chan string, 1)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(_ http.ResponseWriter, r *http.Request) {
reached <- r.Method
})
@@ -384,7 +385,7 @@ func TestEachMethodPassedOnReachesServeWAFAndTheApp(t *testing.T) {
}
if asked := standIn.question(t); asked.method != method {
t.Errorf("servewaf was asked about %s", asked.method)
t.Errorf("discriminator was asked about %s", asked.method)
}
if atApp := <-reached; atApp != method {
@@ -394,7 +395,7 @@ func TestEachMethodPassedOnReachesServeWAFAndTheApp(t *testing.T) {
}
}
func TestARequestThatCannotGoToServeWAFAsSentIsRefused(t *testing.T) {
func TestARequestThatCannotGoToDiscriminatorAsSentIsRefused(t *testing.T) {
t.Parallel()
const host = "Host: site.example\r\n\r\n"
@@ -416,7 +417,7 @@ func TestARequestThatCannotGoToServeWAFAsSentIsRefused(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, nil)
@@ -434,7 +435,7 @@ func TestARequestThatCannotGoToServeWAFAsSentIsRefused(t *testing.T) {
}
if len(standIn.questions) != 0 {
t.Error("servewaf was asked")
t.Error("discriminator was asked")
}
standIn.wantNoReport(t)
@@ -442,12 +443,12 @@ func TestARequestThatCannotGoToServeWAFAsSentIsRefused(t *testing.T) {
}
}
func TestAHostWithAPercentSignButNoBracketGoesToServeWAF(t *testing.T) {
func TestAHostWithAPercentSignButNoBracketGoesToDiscriminator(t *testing.T) {
t.Parallel()
const host = "a%b"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
@@ -468,7 +469,7 @@ func TestATrustedPeerWithNoForwardedProtoIsAskedAboutAsHTTP(t *testing.T) {
const want = "http"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
trustedProxies: localhost + "/32",
@@ -497,7 +498,7 @@ func TestAQuestionNamesNoZone(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, greet)
_, out, server := startServer(t, standIn.URL, app.URL, map[string]string{
trustedProxies: localhost + "/32",
@@ -510,8 +511,8 @@ func TestAQuestionNamesNoZone(t *testing.T) {
server.Handler.ServeHTTP(httptest.NewRecorder(), req)
asked := standIn.question(t)
if asked.header.Get(servewaf.ClientHeader) != tc.client ||
asked.header.Get(servewaf.PeerHeader) != tc.peer {
if asked.header.Get(discriminator.ClientHeader) != tc.client ||
asked.header.Get(discriminator.PeerHeader) != tc.peer {
t.Errorf("the question's headers are %v", asked.header)
}
+11 -11
View File
@@ -16,16 +16,16 @@ import (
"sync/atomic"
"time"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
"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
// soon as it arrives.
const flushAfterEachWrite time.Duration = -1
// reportStatusMax is the highest status servewaf takes in a report. An app can
// reportStatusMax is the highest status discriminator takes in a report. An app can
// send a higher one, up to 999, and the visitor gets that as it is.
const reportStatusMax = 599
@@ -49,14 +49,14 @@ type request struct {
peerTrusted bool
start time.Time
// disposition is what is done with the request: servewaf's valid disposition,
// disposition is what is done with the request: discriminator's valid disposition,
// or SWRP_DEFAULT_DISPOSITION. It is "" when neither gave one.
disposition string
// answered is true when servewaf gave disposition, which makes a report due,
// answered is true when discriminator gave disposition, which makes a report due,
// and byDefault when SWRP_DEFAULT_DISPOSITION did.
answered bool
byDefault bool
// asn and country are servewaf's X-Client-ASN and X-Client-Country, for the
// asn and country are discriminator's X-Client-ASN and X-Client-Country, for the
// app.
asn, country string
@@ -122,14 +122,14 @@ func (rq *request) answerHealth() {
// visitor too slow to send it is answered 408. For a malformed chunk, or a
// visitor that went away or stopped its sending side, the connection is closed
// without an answer. A body over SWRP_MAX_REQUEST_SIZE goes on with the part
// read, so that servewaf is asked first; passToApp then refuses it.
// read, so that discriminator is asked first; passToApp then refuses it.
func (rq *request) readFirstPartOfBody() bool {
if rq.body == nil {
return true
}
_ = rq.rc.SetReadDeadline(rq.requestDeadline())
held, err := io.ReadAll(io.LimitReader(rq.body, servewaf.QuestionBodyMaxBytes))
held, err := io.ReadAll(io.LimitReader(rq.body, discriminator.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{})
@@ -192,7 +192,7 @@ func (rq *request) forward(ctx context.Context) {
// rewrite makes the request the app receives: the visitor's request, unchanged,
// sent to SWRP_UPSTREAM_URL, with the forwarded headers and the request's id set,
// without any X-Client-ASN or X-Client-Country the visitor sent, and with
// servewaf's.
// discriminator's.
func (rq *request) rewrite(pr *httputil.ProxyRequest) {
upstream := rq.h.config.UpstreamURL
pr.Out.URL.Scheme = upstream.Scheme
@@ -312,7 +312,7 @@ func (rq *request) answer(status int) {
// handler has returned, after finish has written the log line and sent the report.
// Without Content-Length, the flush would send the body in chunks, the last of
// which would still wait for the handler. A HEAD's answer has no body, so any
// Content-Length it has stays as servewaf gave it.
// Content-Length it has stays as discriminator gave it.
func (rq *request) send(status int, body []byte) {
if rq.in.Method != http.MethodHead {
rq.out.Header().Set("Content-Length", strconv.Itoa(len(body)))
@@ -347,7 +347,7 @@ func (rq *request) refuse(status int) {
}
}
// finish ends the request's time limits, writes its log line and, if servewaf
// finish ends the request's time limits, writes its log line and, if discriminator
// answered the question, sends the report. ctx is the request's own context.
func (rq *request) finish(ctx context.Context) {
rq.stopTimers()
@@ -384,7 +384,7 @@ func (rq *request) finish(ctx context.Context) {
return
}
report := &servewaf.Report{
report := &discriminator.Report{
RequestID: rq.id,
Status: min(rq.out.status, reportStatusMax),
RequestBytes: requestBytes,
+33 -33
View File
@@ -1,10 +1,10 @@
// Package rproxy is the reverse proxy in front of one app that asks servewaf
// about each request. It passes a request servewaf allows to the app and the
// Package rproxy is the reverse proxy in front of one app that asks discriminator
// about each request. It passes a request discriminator allows to the app and the
// app's answer back, within the size and time limits. Any other answer from
// servewaf goes to the visitor as servewaf gave it. SWRP_DEFAULT_DISPOSITION
// decides only when servewaf cannot be connected to, does not answer at all, or
// discriminator goes to the visitor as discriminator gave it. SWRP_DEFAULT_DISPOSITION
// decides only when discriminator cannot be connected to, does not answer at all, or
// does not answer within SWRP_TIMEOUT_MS. rproxy itself refuses a request with a
// method it does not pass on, or one it cannot put to servewaf as the visitor
// method it does not pass on, or one it cannot put to discriminator as the visitor
// sent it. It keeps nothing between requests and never touches disk. "rproxy" in
// SPEC.md says what it does.
package rproxy
@@ -22,10 +22,10 @@ import (
"time"
"sneak.berlin/go/smallwebwaf/internal/config"
"sneak.berlin/go/smallwebwaf/internal/servewaf"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
)
// How rproxy keeps connections to the app and to servewaf open between
// How rproxy keeps connections to the app and to discriminator open between
// requests.
const (
idleConns = 100
@@ -45,9 +45,9 @@ const (
idleTimeout = 120 * time.Second
)
// adminPrefix starts the path of every request for smallwebwaf itself, which
// adminPrefix starts the path of every request for sww itself, which
// never reaches the app.
const adminPrefix = "/_smallwebwaf/"
const adminPrefix = "/_sww/"
// allowedMethods lists the methods rproxy passes on, as an Allow header gives
// them. It answers any other method 405 itself.
@@ -62,14 +62,14 @@ type Params struct {
ProcessLog *slog.Logger
}
// Server is the server `smallwebwaf rproxy` runs.
// Server is the server `sww rproxy` runs.
type Server struct {
*http.Server
h *handler
}
// New returns the server `smallwebwaf rproxy` runs. Go's server itself refuses
// New returns the server `sww rproxy` runs. Go's server itself refuses
// a request line and headers over requestHeaderMaxBytes, with 431, closes a
// connection idle for idleTimeout, and applies SWRP_MAX_REQUEST_TIME_MS while
// the headers arrive. The handler applies the time and size limits from then
@@ -78,14 +78,14 @@ func New(params Params) *Server {
backend := params.Config.WAFBackend
errorLog := slog.NewLogLogger(params.ProcessLog.Handler(), slog.LevelWarn)
h := &handler{
config: params.Config,
requestLog: params.RequestLog,
processLog: params.ProcessLog,
errorLog: errorLog,
transport: newTransport(),
serveWAFTransport: newServeWAFTransport(),
config: params.Config,
requestLog: params.RequestLog,
processLog: params.ProcessLog,
errorLog: errorLog,
transport: newTransport(),
discriminatorTransport: newDiscriminatorTransport(),
reportURL: (&url.URL{
Scheme: backend.Scheme, Host: backend.Host, Path: servewaf.ReportPath,
Scheme: backend.Scheme, Host: backend.Host, Path: discriminator.ReportPath,
}).String(),
}
@@ -120,10 +120,10 @@ type handler struct {
requestLog io.Writer
processLog *slog.Logger
errorLog *log.Logger
// transport carries requests to the app, and serveWAFTransport questions and
// reports to servewaf.
transport http.RoundTripper
serveWAFTransport http.RoundTripper
// transport carries requests to the app, and discriminatorTransport questions and
// reports to discriminator.
transport http.RoundTripper
discriminatorTransport http.RoundTripper
// reportURL is where the reports go.
reportURL string
// reports waits for the reports being sent. reportsMu guards starting one
@@ -145,11 +145,11 @@ func newTransport() *http.Transport {
}
}
// newServeWAFTransport returns what carries questions and reports to servewaf.
// It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send a visitor's Upgrade
// header, or a Connection header other than close or keep-alive, and that would
// read as servewaf giving no answer.
func newServeWAFTransport() *http.Transport {
// newDiscriminatorTransport returns what carries questions and reports to
// discriminator. It speaks HTTP/1.1 only. Go's HTTP/2 client refuses to send
// a visitor's Upgrade header, or a Connection header other than close or
// keep-alive, and that would read as discriminator giving no answer.
func newDiscriminatorTransport() *http.Transport {
transport := newTransport()
transport.Protocols = new(http.Protocols)
transport.Protocols.SetHTTP1(true)
@@ -158,15 +158,15 @@ func newServeWAFTransport() *http.Transport {
}
// ServeHTTP handles one request: it answers the health check itself, refuses a
// request with a method it does not pass on or one it cannot put to servewaf as
// request with a method it does not pass on or one it cannot put to discriminator as
// the visitor sent it, and for any other request reads the first part of the
// body, asks servewaf, and does what the answer says. Once the answer has ended,
// body, asks discriminator, and does what the answer says. Once the answer has ended,
// it writes the request's log line and sends its report.
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 == servewaf.HealthPath {
if r.Method == http.MethodGet && r.URL.Path == discriminator.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 servewaf.DispositionAllow:
case discriminator.DispositionAllow:
rq.passToApp(r.Context())
case servewaf.DispositionClose:
case discriminator.DispositionClose:
// This panic has Go's server close the connection without an answer,
// and log nothing; the deferred finish still runs.
panic(http.ErrAbortHandler)
@@ -211,7 +211,7 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
// passToApp passes the request on to the app, unless a limit or its path keeps
// it from the app. servewaf answers a path under adminPrefix itself, so such a
// it from the app. discriminator answers a path under adminPrefix itself, so such a
// request gets here only when SWRP_DEFAULT_DISPOSITION allowed it. ctx is the
// request's own context.
func (rq *request) passToApp(ctx context.Context) {
+110 -106
View File
@@ -19,9 +19,9 @@ import (
"time"
"sneak.berlin/go/smallwebwaf/internal/config"
"sneak.berlin/go/smallwebwaf/internal/discriminator"
"sneak.berlin/go/smallwebwaf/internal/requestlog"
"sneak.berlin/go/smallwebwaf/internal/rproxy"
"sneak.berlin/go/smallwebwaf/internal/servewaf"
)
const (
@@ -54,27 +54,27 @@ const (
// The headers the tests set and look at.
const (
dispositionHeader = servewaf.DispositionHeader
dispositionHeader = discriminator.DispositionHeader
forwardedForHeader = "X-Forwarded-For"
forwardedProtoHeader = "X-Forwarded-Proto"
requestIDHeader = "X-Request-ID"
asnHeader = "X-Client-Asn"
countryHeader = "X-Client-Country"
contentLengthHeader = "Content-Length"
// fromServeWAFHeader is a header the stand-in for servewaf adds to a deny.
fromServeWAFHeader = "X-From-Servewaf"
// fromDiscriminatorHeader is a header the stand-in for discriminator adds to a deny.
fromDiscriminatorHeader = "X-From-Discriminator"
// addedHeaderValue is the value of a header a test adds to an answer, to see it
// reach the visitor.
addedHeaderValue = "yes"
)
// What the stand-in for servewaf answers.
// What the stand-in for discriminator answers.
const (
dispositionAllow = servewaf.DispositionAllow
dispositionDeny = servewaf.DispositionDeny
dispositionClose = servewaf.DispositionClose
dispositionAllow = discriminator.DispositionAllow
dispositionDeny = discriminator.DispositionDeny
dispositionClose = discriminator.DispositionClose
// deniedBody is the body of a deny.
deniedBody = "refused by servewaf"
deniedBody = "refused by discriminator"
)
// output collects what rproxy writes on stdout.
@@ -158,7 +158,7 @@ func (l heldLog) Write(p []byte) (int, error) {
return len(p), nil
}
// question is what the stand-in for servewaf received as a question.
// question is what the stand-in for discriminator received as a question.
type question struct {
method, target, host string
header http.Header
@@ -167,26 +167,26 @@ type question struct {
transferEncoding []string
}
// serveWAF is a stand-in for servewaf that answers each question as answer
// does, and keeps the questions and the reports it gets.
type serveWAF struct {
// Discriminator is a stand-in for discriminator that answers each question as
// answer does, and keeps the questions and the reports it gets.
type Discriminator struct {
*httptest.Server
questions chan question
reports chan servewaf.Report
reports chan discriminator.Report
}
// startServeWAF starts a stand-in for servewaf.
func startServeWAF(t *testing.T, answer http.HandlerFunc) *serveWAF {
// startDiscriminator starts a stand-in for discriminator.
func startDiscriminator(t *testing.T, answer http.HandlerFunc) *Discriminator {
t.Helper()
s := &serveWAF{
s := &Discriminator{
questions: make(chan question, 10),
reports: make(chan servewaf.Report, 10),
reports: make(chan discriminator.Report, 10),
}
s.Server = httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(servewaf.ClientHeader) == "" {
if r.Header.Get(discriminator.ClientHeader) == "" {
s.takeReport(t, w, r)
return
@@ -209,14 +209,17 @@ func startServeWAF(t *testing.T, answer http.HandlerFunc) *serveWAF {
return s
}
// takeReport keeps a report and answers it as servewaf does.
func (s *serveWAF) takeReport(t *testing.T, w http.ResponseWriter, r *http.Request) {
// takeReport keeps a report and answers it as discriminator does.
func (s *Discriminator) takeReport(
t *testing.T, w http.ResponseWriter, r *http.Request,
) {
t.Helper()
var report servewaf.Report
var report discriminator.Report
err := json.NewDecoder(r.Body).Decode(&report)
if err != nil || r.Method != http.MethodPost || r.URL.Path != servewaf.ReportPath {
if err != nil || r.Method != http.MethodPost ||
r.URL.Path != discriminator.ReportPath {
t.Errorf("%s %s is no report: %v", r.Method, r.URL.Path, err)
}
@@ -226,7 +229,7 @@ func (s *serveWAF) takeReport(t *testing.T, w http.ResponseWriter, r *http.Reque
}
// wantNoReport checks that no report comes for a moment.
func (s *serveWAF) wantNoReport(t *testing.T) {
func (s *Discriminator) wantNoReport(t *testing.T) {
t.Helper()
select {
@@ -237,7 +240,7 @@ func (s *serveWAF) wantNoReport(t *testing.T) {
}
// report waits for a report.
func (s *serveWAF) report(t *testing.T) servewaf.Report {
func (s *Discriminator) report(t *testing.T) discriminator.Report {
t.Helper()
select {
@@ -246,12 +249,12 @@ func (s *serveWAF) report(t *testing.T) servewaf.Report {
case <-time.After(waitLimit):
t.Fatal("no report")
return servewaf.Report{}
return discriminator.Report{}
}
}
// question waits for a question.
func (s *serveWAF) question(t *testing.T) question {
func (s *Discriminator) question(t *testing.T) question {
t.Helper()
select {
@@ -264,16 +267,17 @@ func (s *serveWAF) question(t *testing.T) question {
}
}
// startServeWAFAnsweringReports starts a stand-in for servewaf that answers each
// question as answer does and each report as report does, and returns its URL.
func startServeWAFAnsweringReports(
// startDiscriminatorAnsweringReports starts a stand-in for discriminator that
// answers each question as answer does and each report as report does, and
// returns its URL.
func startDiscriminatorAnsweringReports(
t *testing.T, answer, report http.HandlerFunc,
) string {
t.Helper()
backend := httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(servewaf.ClientHeader) != "" {
if r.Header.Get(discriminator.ClientHeader) != "" {
answer(w, r)
return
@@ -295,7 +299,7 @@ func allow(w http.ResponseWriter, _ *http.Request) {
// deny answers a question with deny, status 403 and a body of its own.
func deny(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(dispositionHeader, dispositionDeny)
w.Header().Set(fromServeWAFHeader, addedHeaderValue)
w.Header().Set(fromDiscriminatorHeader, addedHeaderValue)
w.WriteHeader(http.StatusForbidden)
_, _ = io.WriteString(w, deniedBody)
}
@@ -316,7 +320,7 @@ func greet(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, greeting)
}
// startRProxy starts rproxy with servewaf at backend and the app at appURL, and
// startRProxy starts rproxy with discriminator at backend and the app at appURL, and
// the settings in env on top of the defaults. It returns where rproxy listens
// and what it writes.
func startRProxy(
@@ -498,7 +502,7 @@ func readStatus(t *testing.T, conn net.Conn) int {
func TestPassesAnAllowedRequestToTheAppAndItsAnswerBack(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(w http.ResponseWriter, r *http.Request) {
standIn := startDiscriminator(t, func(w http.ResponseWriter, r *http.Request) {
w.Header().Set(asnHeader, "AS64496")
w.Header().Set(countryHeader, "DE")
allow(w, r)
@@ -557,7 +561,7 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
}
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
seen := make(chan http.Header, 1)
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
seen <- r.Header.Clone()
@@ -602,7 +606,7 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
func TestATrustedProxysForwardedHeadersPassOn(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
seen := make(chan http.Header, 1)
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
seen <- r.Header.Clone()
@@ -641,7 +645,7 @@ func TestATrustedProxysForwardedHeadersPassOn(t *testing.T) {
func TestAnAnswerWithoutAContentTypeGetsNone(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
w.Header()["Content-Type"] = nil
_, _ = io.WriteString(w, "<html>")
@@ -663,19 +667,19 @@ func TestAnAnswerWithoutAContentTypeGetsNone(t *testing.T) {
}
}
func TestRemovesTheVisitorsLookupHeadersWhenServeWAFGivesNone(t *testing.T) {
func TestRemovesTheVisitorsLookupHeadersWhenDiscriminatorGivesNone(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
backend func(t *testing.T) string
}{
{"servewaf allows", func(t *testing.T) string {
{"discriminator allows", func(t *testing.T) string {
t.Helper()
return startServeWAF(t, allow).URL
return startDiscriminator(t, allow).URL
}},
{"servewaf is down", func(*testing.T) string { return nowhere }},
{"discriminator is down", func(*testing.T) string { return nowhere }},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
@@ -701,10 +705,10 @@ func TestRemovesTheVisitorsLookupHeadersWhenServeWAFGivesNone(t *testing.T) {
}
}
func TestDenyGivesTheVisitorServeWAFsAnswer(t *testing.T) {
func TestDenyGivesTheVisitorDiscriminatorsAnswer(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, func(http.ResponseWriter, *http.Request) {
t.Error("the app was asked")
})
@@ -712,8 +716,8 @@ func TestDenyGivesTheVisitorServeWAFsAnswer(t *testing.T) {
got := get(t, addr, "/")
if got.status != http.StatusForbidden || got.body != deniedBody ||
got.header.Get(fromServeWAFHeader) != addedHeaderValue {
t.Errorf("answered %+v, want servewaf's answer", got)
got.header.Get(fromDiscriminatorHeader) != addedHeaderValue {
t.Errorf("answered %+v, want discriminator's answer", got)
}
if len(got.header.Values(dispositionHeader)) != 0 {
@@ -729,21 +733,21 @@ func TestDenyGivesTheVisitorServeWAFsAnswer(t *testing.T) {
line := out.requestLine(t)
if line["disposition"] != dispositionDeny || line["default_disposition"] != nil {
t.Errorf("line %v, want servewaf's deny", line)
t.Errorf("line %v, want discriminator's deny", line)
}
}
func TestADeniedHeadKeepsServeWAFsContentLength(t *testing.T) {
func TestADeniedHeadKeepsDiscriminatorsContentLength(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
got := visit(t, newRequest(t, http.MethodHead, addr, "/", http.NoBody))
if got.status != http.StatusForbidden ||
got.header.Get(contentLengthHeader) != strconv.Itoa(len(deniedBody)) {
t.Errorf("answered %+v, want servewaf's 403 and its length", got)
t.Errorf("answered %+v, want discriminator's 403 and its length", got)
}
}
@@ -758,13 +762,13 @@ 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 " + servewaf.HealthPath + host, "ok\n", http.StatusOK},
{"the health check", "GET " + discriminator.HealthPath + host, "ok\n", http.StatusOK},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
log := heldLog{release: make(chan struct{})}
standIn := startServeWAF(t, deny)
standIn := startDiscriminator(t, deny)
app := startApp(t, greet)
addr, _ := startServerLogging(t, standIn.URL, app.URL, nil, log, io.Discard)
@@ -794,7 +798,7 @@ func TestAnAnswerRProxyHoldsWholeIsSentBeforeItsLogLine(t *testing.T) {
func TestRProxysOwnAnswerIsPlainText(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
@@ -817,7 +821,7 @@ func TestRProxysOwnAnswerIsPlainText(t *testing.T) {
func TestCloseClosesTheConnectionWithoutAnAnswer(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) {
standIn := startDiscriminator(t, func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(dispositionHeader, dispositionClose)
w.WriteHeader(http.StatusOK)
})
@@ -849,7 +853,7 @@ func TestCloseClosesTheConnectionWithoutAnAnswer(t *testing.T) {
func TestALineForARequestClosedWithoutAnAnswerHasNoNegativeTime(t *testing.T) {
t.Parallel()
backend := startServeWAFAnsweringReports(t,
backend := startDiscriminatorAnsweringReports(t,
func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set(dispositionHeader, dispositionClose)
},
@@ -874,17 +878,17 @@ func TestALineForARequestClosedWithoutAnAnswerHasNoNegativeTime(t *testing.T) {
}
}
// answeringWith returns what starts a stand-in for servewaf that answers each
// answeringWith returns what starts a stand-in for discriminator that answers each
// question as answer does, and returns its URL.
func answeringWith(answer http.HandlerFunc) func(t *testing.T) string {
return func(t *testing.T) string {
t.Helper()
return startServeWAF(t, answer).URL
return startDiscriminator(t, answer).URL
}
}
// noAnswers are the ways servewaf can leave the decision to the default.
// noAnswers are the ways discriminator can leave the decision to the default.
func noAnswers() map[string]func(t *testing.T) string {
return map[string]func(t *testing.T) string{
"no connection": func(*testing.T) string { return nowhere },
@@ -916,7 +920,7 @@ func noAnswers() map[string]func(t *testing.T) string {
}
}
func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) {
func TestAnAnswerOtherThanAllowGoesToTheVisitorAsDiscriminatorGaveIt(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
@@ -925,7 +929,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) {
}{
{"a refusal", dispositionDeny, deniedBody, http.StatusForbidden},
{"a refusal, 429", dispositionDeny, deniedBody, http.StatusTooManyRequests},
{"servewaf's 400", "", "refused\n", http.StatusBadRequest},
{"discriminator's 400", "", "refused\n", http.StatusBadRequest},
{"a 500", "", "broken\n", http.StatusInternalServerError},
{"a 503 denied", dispositionDeny, "busy\n", http.StatusServiceUnavailable},
{"no header", "", "who knows\n", http.StatusOK},
@@ -935,12 +939,12 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) {
t.Run(tc.name+" with the default "+setting, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) {
standIn := startDiscriminator(t, func(w http.ResponseWriter, _ *http.Request) {
if tc.disposition != "" {
w.Header().Set(dispositionHeader, tc.disposition)
}
w.Header().Set(fromServeWAFHeader, addedHeaderValue)
w.Header().Set(fromDiscriminatorHeader, addedHeaderValue)
w.WriteHeader(tc.status)
_, _ = io.WriteString(w, tc.body)
})
@@ -953,8 +957,8 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) {
got := get(t, addr, "/")
if got.status != tc.status || got.body != tc.body ||
got.header.Get(fromServeWAFHeader) != addedHeaderValue {
t.Errorf("answered %+v, want servewaf's answer", got)
got.header.Get(fromDiscriminatorHeader) != addedHeaderValue {
t.Errorf("answered %+v, want discriminator's answer", got)
}
if len(got.header.Values(dispositionHeader)) != 0 {
@@ -966,8 +970,8 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) {
t.Errorf("line %v, want status %d and no default", line, tc.status)
}
if out.has("servewaf gave no answer") {
t.Error("rproxy warned that servewaf gave no answer")
if out.has("discriminator gave no answer") {
t.Error("rproxy warned that discriminator gave no answer")
}
})
}
@@ -977,14 +981,14 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsServeWAFGaveIt(t *testing.T) {
func TestAnAnswerWithoutAValidDispositionGetsNoReport(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) {
standIn := startDiscriminator(t, func(w http.ResponseWriter, _ *http.Request) {
http.Error(w, "refused", http.StatusBadRequest)
})
app := startApp(t, greet)
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)
t.Errorf("answered %+v, want discriminator's 400", got)
}
if line := out.requestLine(t); line["disposition"] != nil {
@@ -997,7 +1001,7 @@ func TestAnAnswerWithoutAValidDispositionGetsNoReport(t *testing.T) {
func TestAnAnswerWithoutAContentTypeGoesToTheVisitorWithNone(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) {
standIn := startDiscriminator(t, func(w http.ResponseWriter, _ *http.Request) {
w.Header()["Content-Type"] = nil
w.WriteHeader(http.StatusInternalServerError)
_, _ = io.WriteString(w, "<html>broken")
@@ -1015,7 +1019,7 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
const size = 1 << 20
standIn := startServeWAF(t, func(w http.ResponseWriter, _ *http.Request) {
standIn := startDiscriminator(t, func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write(make([]byte, size))
})
@@ -1023,15 +1027,15 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
got := get(t, addr, "/")
if got.status != http.StatusInternalServerError || len(got.body) != size {
t.Errorf("answered %d with %d bytes, want servewaf's 500 with %d",
t.Errorf("answered %d with %d bytes, want discriminator's 500 with %d",
got.status, len(got.body), size)
}
}
// startServeWAFWriting starts a stand-in for servewaf that reads each question and
// answers it with text, as it is, and returns its URL. It can send what Go's
// server would not.
func startServeWAFWriting(t *testing.T, text string) string {
// startDiscriminatorWriting starts a stand-in for discriminator that reads
// each question and answers it with text, as it is, and returns its URL. It
// can send what Go's server would not.
func startDiscriminatorWriting(t *testing.T, text string) string {
t.Helper()
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
@@ -1101,7 +1105,7 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
"a status below 100": func(t *testing.T) string {
t.Helper()
return startServeWAFWriting(t, "HTTP/1.1 099 Odd\r\nContent-Length: 0\r\n\r\n")
return startDiscriminatorWriting(t, "HTTP/1.1 099 Odd\r\nContent-Length: 0\r\n\r\n")
},
"a 101 that holds its connection open": answeringWith(switchAndHold),
} {
@@ -1126,7 +1130,7 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
t.Errorf("line %v, want status 502 and no disposition", line)
}
out.waitFor(t, "servewaf's answer is dropped")
out.waitFor(t, "discriminator's answer is dropped")
})
}
}
@@ -1135,7 +1139,7 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, overOneMiB(dispositionDeny))
standIn := startDiscriminator(t, overOneMiB(dispositionDeny))
addr, out := startRProxy(t, standIn.URL, nowhere, nil)
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
@@ -1192,14 +1196,14 @@ func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
}
}
func TestAVisitorThatGoesAwayWhileServeWAFIsAskedGetsNoDefault(t *testing.T) {
func TestAVisitorThatGoesAwayWhileDiscriminatorIsAskedGetsNoDefault(t *testing.T) {
t.Parallel()
for _, disposition := range []string{dispositionAllow, dispositionDeny} {
t.Run("with the default "+disposition, func(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(_ http.ResponseWriter, r *http.Request) {
standIn := startDiscriminator(t, func(_ http.ResponseWriter, r *http.Request) {
<-r.Context().Done()
})
app := startApp(t, func(http.ResponseWriter, *http.Request) {
@@ -1222,8 +1226,8 @@ func TestAVisitorThatGoesAwayWhileServeWAFIsAskedGetsNoDefault(t *testing.T) {
t.Errorf("line %v, want status 0 and no disposition", line)
}
if out.has("servewaf gave no answer") {
t.Error("rproxy warned that servewaf gave no answer")
if out.has("discriminator gave no answer") {
t.Error("rproxy warned that discriminator gave no answer")
}
standIn.wantNoReport(t)
@@ -1238,13 +1242,13 @@ func TestRProxyWarnsWhenTheDefaultDecides(t *testing.T) {
addr, out := startRProxy(t, nowhere, app.URL, nil)
wantGreeting(t, get(t, addr, "/"))
out.waitFor(t, "servewaf gave no answer")
out.waitFor(t, "discriminator gave no answer")
}
func TestSendsNoReportWithoutAnAnswer(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, func(_ http.ResponseWriter, r *http.Request) {
standIn := startDiscriminator(t, func(_ http.ResponseWriter, r *http.Request) {
<-r.Context().Done()
})
app := startApp(t, greet)
@@ -1265,35 +1269,35 @@ func TestAPathUnderOwnPrefixNeverReachesTheApp(t *testing.T) {
})
addr, _ := startRProxy(t, nowhere, app.URL, nil)
got := get(t, addr, "/_smallwebwaf/metrics")
got := get(t, addr, "/_sww/metrics")
if got.status != http.StatusNotFound {
t.Errorf("answered %+v, want 404", got)
}
}
func TestHealthCheckIsAnsweredWhileServeWAFIsDown(t *testing.T) {
func TestHealthCheckIsAnsweredWhileDiscriminatorIsDown(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, nowhere, app.URL, nil)
got := get(t, addr, servewaf.HealthPath)
got := get(t, addr, discriminator.HealthPath)
if got.status != http.StatusOK || got.body != "ok\n" {
t.Errorf("answered %+v, want ok", got)
}
line := out.requestLine(t)
if line["path"] != servewaf.HealthPath || line["disposition"] != nil {
if line["path"] != discriminator.HealthPath || line["disposition"] != nil {
t.Errorf("line %v, want one with no disposition", line)
}
// With servewaf up too, it is not asked.
// With discriminator up too, it is not asked.
addr, _ = startRProxy(t, standIn.URL, app.URL, nil)
get(t, addr, servewaf.HealthPath)
get(t, addr, discriminator.HealthPath)
if len(standIn.questions) != 0 {
t.Error("servewaf was asked about the health check")
t.Error("discriminator was asked about the health check")
}
}
@@ -1301,7 +1305,7 @@ func TestTheVisitorDoesNotWaitForTheReport(t *testing.T) {
t.Parallel()
release := make(chan struct{})
backend := startServeWAFAnsweringReports(t, deny,
backend := startDiscriminatorAnsweringReports(t, deny,
func(w http.ResponseWriter, r *http.Request) {
select {
case <-release:
@@ -1336,16 +1340,16 @@ func TestTheVisitorDoesNotWaitForTheReport(t *testing.T) {
_ = res.Body.Close()
if res.StatusCode != http.StatusForbidden {
t.Errorf("status %d, want servewaf's 403", res.StatusCode)
t.Errorf("status %d, want discriminator's 403", res.StatusCode)
}
}
}
func TestAReportServeWAFLeavesUnansweredIsDroppedAtTheTimeLimit(t *testing.T) {
func TestAReportDiscriminatorLeavesUnansweredIsDroppedAtTheTimeLimit(t *testing.T) {
t.Parallel()
release := make(chan struct{})
backend := startServeWAFAnsweringReports(t, allow,
backend := startDiscriminatorAnsweringReports(t, allow,
func(w http.ResponseWriter, _ *http.Request) {
<-release
@@ -1365,10 +1369,10 @@ func TestAReportServeWAFLeavesUnansweredIsDroppedAtTheTimeLimit(t *testing.T) {
out.waitFor(t, "a report is dropped")
}
func TestAReportServeWAFRefusesIsDroppedWithAWarning(t *testing.T) {
func TestAReportDiscriminatorRefusesIsDroppedWithAWarning(t *testing.T) {
t.Parallel()
backend := startServeWAFAnsweringReports(t, allow,
backend := startDiscriminatorAnsweringReports(t, allow,
func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusBadRequest)
})
@@ -1383,7 +1387,7 @@ func TestAReportServeWAFRefusesIsDroppedWithAWarning(t *testing.T) {
func TestAReportAnswered101IsDroppedAtOnce(t *testing.T) {
t.Parallel()
backend := startServeWAFAnsweringReports(t, allow, switchAndHold)
backend := startDiscriminatorAnsweringReports(t, allow, switchAndHold)
app := startApp(t, greet)
addr, out := startRProxy(t, backend, app.URL, map[string]string{
timeoutSetting: "60000",
@@ -1396,7 +1400,7 @@ func TestAReportAnswered101IsDroppedAtOnce(t *testing.T) {
func TestAReportDueAfterWaitForReportsIsDropped(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out, server := startServer(t, standIn.URL, app.URL, nil)
@@ -1412,7 +1416,7 @@ func TestReportGivesWhatHappened(t *testing.T) {
const body = "the visitor's body"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, nil)
@@ -1442,7 +1446,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(servewaf.ClientHeader) != "" {
if r.Header.Get(discriminator.ClientHeader) != "" {
allow(w, r)
return
@@ -1497,7 +1501,7 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) {
func TestReportOfAWebSocketCountsWhatItCarried(t *testing.T) {
t.Parallel()
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, echoAfterUpgrade)
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
maxRequestTime: "2000",
@@ -1552,7 +1556,7 @@ func TestAWebSocketPassesOnThatTheVisitorStoppedSending(t *testing.T) {
const goodbye = "sent after the visitor stopped sending\n"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
conn, buffered, err := http.NewResponseController(w).Hijack()
if err != nil {
@@ -1610,7 +1614,7 @@ func TestAWebSocketPassesOnThatTheVisitorStoppedSending(t *testing.T) {
//nolint:paralleltest // see above
func TestASwitchNotPassedOnClosesTheAppsConnection(t *testing.T) {
closed := make(chan error, 1)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
conn, buffered, err := http.NewResponseController(w).Hijack()
if err != nil {
@@ -1681,7 +1685,7 @@ func TestEachRequestGetsAFreshID(t *testing.T) {
const fromTraefik = "an-id-from-traefik"
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
trustedProxies: localhost + "/32",
@@ -1711,7 +1715,7 @@ func TestLogLineGivesTheRequestAsSent(t *testing.T) {
body = "the visitor's body"
)
standIn := startServeWAF(t, allow)
standIn := startDiscriminator(t, allow)
app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, nil)