Rename the settings whose names do not say what they control (closes #234)
check / check (push) Waiting to run
check / check (push) Waiting to run
Applies the setting renames of the naming audit under the SWW_WAF_ and SWW_RPROXY_ prefixes, with sneak's names for the IP reputation cache time and the two time limits, which now take milliseconds. The rproxy log field default_disposition becomes waf_backend_no_answer, and config.Config and config.FromEnvironment become config.WAF and config.WAFFromEnvironment. Go fields, test names and prose follow. No aliases; nothing reads the old names. Model: opus-5-5
This commit is contained in:
1 parent
101cc0aba5
commit
4be0b6e7fc
97 files changed
+2030
-1810
No files matched your search
@@ -407,7 +407,7 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["status"] != float64(http.StatusMethodNotAllowed) ||
|
||||
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||
line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf("line %v, want status 405 and no disposition", line)
|
||||
}
|
||||
|
||||
@@ -490,7 +490,7 @@ func TestARequestThatCannotGoToWAFAsSentIsRefused(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["status"] != float64(http.StatusBadRequest) ||
|
||||
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||
line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf("line %v, want status 400 and no disposition", line)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,12 +61,13 @@ type request struct {
|
||||
start time.Time
|
||||
|
||||
// disposition is what is done with the request: waf's valid disposition,
|
||||
// or SWW_RPROXY_DEFAULT_DISPOSITION. It is "" when neither gave one.
|
||||
// or SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION. It is "" when neither
|
||||
// gave one.
|
||||
disposition string
|
||||
// answered is true when waf gave disposition, which makes a report due,
|
||||
// and byDefault when SWW_RPROXY_DEFAULT_DISPOSITION did.
|
||||
answered bool
|
||||
byDefault bool
|
||||
// and wafNoAnswer when SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION did.
|
||||
answered bool
|
||||
wafNoAnswer bool
|
||||
// asn and country are waf's X-Client-ASN and X-Client-Country, for the
|
||||
// app.
|
||||
asn, country string
|
||||
@@ -450,7 +451,7 @@ func (rq *request) finish(ctx context.Context) {
|
||||
),
|
||||
DurationResponseMS: requestlog.Milliseconds(end.Sub(requestEnd)),
|
||||
Disposition: rq.disposition,
|
||||
DefaultDisposition: rq.byDefault,
|
||||
WAFBackendNoAnswer: rq.wafNoAnswer,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Package rproxy is the reverse proxy in front of one app that asks waf
|
||||
// about each request. It passes a request waf allows to the app and the
|
||||
// app's answer back, within the size and time limits. Any other answer from
|
||||
// waf goes to the visitor as waf gave it. SWW_RPROXY_DEFAULT_DISPOSITION
|
||||
// decides only when waf cannot be connected to, does not answer at all, or
|
||||
// does not answer within SWW_RPROXY_TIMEOUT_MS. rproxy itself refuses a request with a
|
||||
// waf goes to the visitor as waf gave it.
|
||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION decides only when waf cannot
|
||||
// be connected to, does not answer at all, or does not answer within
|
||||
// SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. rproxy itself refuses a request with a
|
||||
// method it does not pass on, or one it cannot put to waf as the visitor
|
||||
// sent it. It keeps nothing between requests and writes nothing to disk. "rproxy" in
|
||||
// SPEC.md says what it does.
|
||||
@@ -108,7 +109,8 @@ func New(params Params) *Server {
|
||||
}
|
||||
|
||||
// WaitForReports returns once the reports being sent have gone or failed,
|
||||
// which SWW_RPROXY_TIMEOUT_MS bounds. Any report after this call is dropped: that of
|
||||
// which SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS bounds. Any report after this call
|
||||
// is dropped: that of
|
||||
// a WebSocket that closes while rproxy stops, for one.
|
||||
func (s *Server) WaitForReports() {
|
||||
s.h.reportsMu.Lock()
|
||||
@@ -217,7 +219,8 @@ 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. waf answers a path under ownEndpointPrefix itself, so
|
||||
// such a request gets here only when SWW_RPROXY_DEFAULT_DISPOSITION allowed it.
|
||||
// such a request gets here only when
|
||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION allowed it.
|
||||
// ctx is the request's own context.
|
||||
func (rq *request) passToApp(ctx context.Context) {
|
||||
maxBytes := rq.h.config.MaxRequestSize
|
||||
|
||||
@@ -44,13 +44,13 @@ const (
|
||||
|
||||
// The settings the tests set.
|
||||
const (
|
||||
timeoutSetting = "SWW_RPROXY_TIMEOUT_MS"
|
||||
defaultDisposition = "SWW_RPROXY_DEFAULT_DISPOSITION"
|
||||
trustedProxies = "SWW_RPROXY_TRUSTED_PROXIES"
|
||||
maxRequestSize = "SWW_RPROXY_MAX_REQUEST_SIZE"
|
||||
maxResponseSize = "SWW_RPROXY_MAX_RESPONSE_SIZE"
|
||||
maxRequestTime = "SWW_RPROXY_MAX_REQUEST_TIME_MS"
|
||||
maxResponseTime = "SWW_RPROXY_MAX_RESPONSE_TIME_MS"
|
||||
timeoutSetting = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
|
||||
noAnswerSetting = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION"
|
||||
trustedProxies = "SWW_RPROXY_TRUSTED_PROXIES"
|
||||
maxRequestSize = "SWW_RPROXY_MAX_REQUEST_SIZE"
|
||||
maxResponseSize = "SWW_RPROXY_MAX_RESPONSE_SIZE"
|
||||
maxRequestTime = "SWW_RPROXY_MAX_REQUEST_TIME_MS"
|
||||
maxResponseTime = "SWW_RPROXY_MAX_RESPONSE_TIME_MS"
|
||||
)
|
||||
|
||||
// The headers the tests set and look at.
|
||||
@@ -774,7 +774,7 @@ func TestDenyGivesTheVisitorWAFsAnswer(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["disposition"] != dispositionDeny ||
|
||||
line["default_disposition"] != nil {
|
||||
line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf("line %v, want waf's deny", line)
|
||||
}
|
||||
}
|
||||
@@ -956,7 +956,8 @@ func answeringWith(answer http.HandlerFunc) func(t *testing.T) string {
|
||||
}
|
||||
}
|
||||
|
||||
// noAnswers are the ways waf can leave the decision to the default.
|
||||
// noAnswers are the ways waf can leave the decision to
|
||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION.
|
||||
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 },
|
||||
@@ -1008,7 +1009,8 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
||||
{"an unknown disposition", "maybe", "who knows\n", http.StatusOK},
|
||||
} {
|
||||
for _, setting := range []string{dispositionAllow, dispositionDeny} {
|
||||
t.Run(tc.name+" with the default "+setting, func(t *testing.T) {
|
||||
subtest := tc.name + " with " + noAnswerSetting + "=" + setting
|
||||
t.Run(subtest, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
standIn := startWAF(
|
||||
@@ -1031,7 +1033,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
||||
standIn.URL,
|
||||
app.URL,
|
||||
map[string]string{
|
||||
defaultDisposition: setting,
|
||||
noAnswerSetting: setting,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1047,9 +1049,9 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["status"] != float64(tc.status) ||
|
||||
line["default_disposition"] != nil {
|
||||
line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf(
|
||||
"line %v, want status %d and no default",
|
||||
"line %v, want status %d and no waf_backend_no_answer",
|
||||
line,
|
||||
tc.status,
|
||||
)
|
||||
@@ -1202,7 +1204,8 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
||||
"a 101 that holds its connection open": answeringWith(switchAndHold),
|
||||
} {
|
||||
for _, setting := range []string{dispositionAllow, dispositionDeny} {
|
||||
t.Run(name+" with the default "+setting, func(t *testing.T) {
|
||||
subtest := name + " with " + noAnswerSetting + "=" + setting
|
||||
t.Run(subtest, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||
@@ -1213,8 +1216,8 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
||||
backend(t),
|
||||
app.URL,
|
||||
map[string]string{
|
||||
timeoutSetting: "60000",
|
||||
defaultDisposition: setting,
|
||||
timeoutSetting: "60000",
|
||||
noAnswerSetting: setting,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1224,7 +1227,7 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["status"] != float64(http.StatusBadGateway) ||
|
||||
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||
line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf(
|
||||
"line %v, want status 502 and no disposition",
|
||||
line,
|
||||
@@ -1253,7 +1256,9 @@ func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
|
||||
standIn.wantNoReport(t)
|
||||
}
|
||||
|
||||
func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
||||
func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
for name, backend := range noAnswers() {
|
||||
@@ -1274,8 +1279,11 @@ func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["disposition"] != dispositionAllow ||
|
||||
line["default_disposition"] != true {
|
||||
t.Errorf("line %v, want allow by default", line)
|
||||
line["waf_backend_no_answer"] != true {
|
||||
t.Errorf(
|
||||
"line %v, want allow and waf_backend_no_answer",
|
||||
line,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1290,8 +1298,8 @@ func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
||||
backend(t),
|
||||
app.URL,
|
||||
map[string]string{
|
||||
timeoutSetting: "200",
|
||||
defaultDisposition: dispositionDeny,
|
||||
timeoutSetting: "200",
|
||||
noAnswerSetting: dispositionDeny,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1302,18 +1310,21 @@ func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["disposition"] != dispositionDeny ||
|
||||
line["default_disposition"] != true {
|
||||
t.Errorf("line %v, want deny by default", line)
|
||||
line["waf_backend_no_answer"] != true {
|
||||
t.Errorf(
|
||||
"line %v, want deny and waf_backend_no_answer",
|
||||
line,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDefault(t *testing.T) {
|
||||
func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, disposition := range []string{dispositionAllow, dispositionDeny} {
|
||||
t.Run("with the default "+disposition, func(t *testing.T) {
|
||||
t.Run(noAnswerSetting+"="+disposition, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
standIn := startWAF(
|
||||
@@ -1330,8 +1341,8 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDefault(t *testing.T) {
|
||||
standIn.URL,
|
||||
app.URL,
|
||||
map[string]string{
|
||||
timeoutSetting: "60000",
|
||||
defaultDisposition: disposition,
|
||||
timeoutSetting: "60000",
|
||||
noAnswerSetting: disposition,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1343,7 +1354,7 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDefault(t *testing.T) {
|
||||
|
||||
line := out.requestLine(t)
|
||||
if line["status"] != 0.0 || line["disposition"] != nil ||
|
||||
line["default_disposition"] != nil {
|
||||
line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf("line %v, want status 0 and no disposition", line)
|
||||
}
|
||||
|
||||
@@ -1356,7 +1367,9 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDefault(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRProxyWarnsWhenTheDefaultDecides(t *testing.T) {
|
||||
func TestRProxyWarnsWhenTheWAFBackendNoAnswerDispositionDecides(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
app := startApp(t, greet)
|
||||
@@ -1892,10 +1905,10 @@ func TestLogLineGivesTheRequestAsSent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if line["default_disposition"] != nil {
|
||||
if line["waf_backend_no_answer"] != nil {
|
||||
t.Errorf(
|
||||
"default_disposition is %v, want it left out",
|
||||
line["default_disposition"],
|
||||
"waf_backend_no_answer is %v, want it left out",
|
||||
line["waf_backend_no_answer"],
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+12
-11
@@ -67,15 +67,16 @@ func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) {
|
||||
rq.answer(http.StatusBadGateway)
|
||||
default:
|
||||
// waf cannot be connected to, does not answer at all, or does not answer
|
||||
// within SWW_RPROXY_TIMEOUT_MS. A request that keeps waf busy past
|
||||
// SWW_RPROXY_TIMEOUT_MS, like any request while waf is overloaded, gets
|
||||
// SWW_RPROXY_DEFAULT_DISPOSITION here. deny closes this, at the cost of answering 503
|
||||
// whenever waf is slow. Making such requests hard to send is
|
||||
// within SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. A request that keeps waf
|
||||
// busy past SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS, like any request while
|
||||
// waf is overloaded, gets SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION
|
||||
// here. deny closes this, at the cost of answering 503 whenever waf is
|
||||
// slow. Making such requests hard to send is
|
||||
// https://git.eeqj.de/sneak/smallwebwaf/issues/182.
|
||||
rq.h.processLog.Warn("waf gave no answer", "error", err.Error())
|
||||
|
||||
rq.disposition = rq.h.config.DefaultDisposition
|
||||
rq.byDefault = true
|
||||
rq.disposition = rq.h.config.WAFBackendNoAnswerDisposition
|
||||
rq.wafNoAnswer = true
|
||||
|
||||
if rq.disposition == waf.DispositionAllow {
|
||||
rq.passToApp(ctx)
|
||||
@@ -86,10 +87,10 @@ func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) {
|
||||
}
|
||||
|
||||
// ask sends waf the question, and returns its answer, which must have come
|
||||
// whole within SWW_RPROXY_TIMEOUT_MS. It ends when ctx does, as when the visitor goes
|
||||
// away.
|
||||
// whole within SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. It ends when ctx does, as
|
||||
// when the visitor goes away.
|
||||
func (rq *request) ask(ctx context.Context) (*wafAnswer, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, rq.h.config.Timeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, rq.h.config.WAFBackendTimeout)
|
||||
defer cancel()
|
||||
|
||||
question, err := rq.newQuestion(ctx)
|
||||
@@ -242,14 +243,14 @@ func (h *handler) sendReport(ctx context.Context, report *waf.Report) {
|
||||
}
|
||||
|
||||
// postReport posts report to waf, and waits for its answer for up to
|
||||
// SWW_RPROXY_TIMEOUT_MS.
|
||||
// SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS.
|
||||
func (h *handler) postReport(ctx context.Context, report *waf.Report) error {
|
||||
body, err := json.Marshal(report)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode the report: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, h.config.Timeout)
|
||||
ctx, cancel := context.WithTimeout(ctx, h.config.WAFBackendTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext( //nolint:gosec // a setting's URL
|
||||
|
||||
Reference in new issue
Block a user