servewaf always answers rproxy well within its time limit (closes #182)
check / check (push) Successful in 6m23s
check / check (push) Successful in 6m23s
A question waits on GeoJS at most 800ms, the new default of SWWAF_LOOKUP_TIMEOUT. It does not wait at all when the checks will refuse it whatever GeoJS answers. Just before the Core Rule Set, servewaf refuses more than 50 cookies or 100 header fields with 431. Shared default changed: SWWAF_LOOKUP_TIMEOUT also applies to smallwebwaf with no command. Judgement call: SWWAF_REPUTATION_TIMEOUT stays 2s, since no request waits on the DNS blocklists or AbuseIPDB. Model: opus-5-5
This commit is contained in:
@@ -53,6 +53,13 @@ const ReportPath = "/_smallwebwaf/report"
|
||||
// QuestionBodyMaxBytes is the most of a visitor's body a question carries.
|
||||
const QuestionBodyMaxBytes = 64 << 10
|
||||
|
||||
// The header limits: the most cookies and header fields a request may have
|
||||
// when the checks reach the Core Rule Set. Real clients never reach them.
|
||||
const (
|
||||
maxCookies = 50
|
||||
maxHeaderFields = 100
|
||||
)
|
||||
|
||||
const (
|
||||
// maxAwaitingReport is the most answered requests held until their
|
||||
// reports come. Past it, the request answered longest ago is dropped.
|
||||
@@ -146,6 +153,56 @@ func (h *handler) serveWAF(w http.ResponseWriter, r *http.Request) {
|
||||
h.answerOwnEndpoint(w, r)
|
||||
}
|
||||
|
||||
// answersQuestions reports whether h is servewaf, which alone holds requests
|
||||
// for their reports.
|
||||
func (h *handler) answersQuestions() bool {
|
||||
return h.awaitingReport != nil
|
||||
}
|
||||
|
||||
// refusedWhateverTheAnswer reports whether the checks will refuse the request
|
||||
// at now whatever GeoJS answers about its client: it is over the header limits,
|
||||
// a blocklist lists its client while SWWAF_BLOCKLIST_ACTION is deny, the
|
||||
// CrowdSec decision list lists it, or it is over a whole rate limit. rproxy
|
||||
// lets a request through when servewaf answers too late, so such a request
|
||||
// does not wait for GeoJS.
|
||||
func (rq *request) refusedWhateverTheAnswer(now time.Time) bool {
|
||||
lists := rq.h.lists
|
||||
_, decided := lists.CrowdSecDecision(rq.client, now)
|
||||
|
||||
return decided || rq.overHeaderLimits() ||
|
||||
(rq.h.config.BlocklistAction == deny && len(lists.ListedBy(rq.client)) > 0) ||
|
||||
(rq.countedByRateLimits() &&
|
||||
rq.h.limiter.OverARateLimit(rq.h.clientGroup(rq.client), now))
|
||||
}
|
||||
|
||||
// overHeaderLimits reports whether servewaf refuses the request for its
|
||||
// headers: it has more than maxHeaderFields fields, each value of a header sent
|
||||
// twice being one, or more than maxCookies cookies, each part of a Cookie header
|
||||
// between semicolons that is not empty being one. The Core Rule Set runs each of
|
||||
// its rules on each cookie, so that thousands of small cookies keep it busy for
|
||||
// over a second.
|
||||
func (rq *request) overHeaderLimits() bool {
|
||||
if !rq.h.answersQuestions() {
|
||||
return false
|
||||
}
|
||||
|
||||
fields, cookies := 0, 0
|
||||
|
||||
for _, values := range rq.in.Header {
|
||||
fields += len(values)
|
||||
}
|
||||
|
||||
for _, value := range rq.in.Header.Values("Cookie") {
|
||||
for part := range strings.SplitSeq(value, ";") {
|
||||
if strings.TrimSpace(part) != "" {
|
||||
cookies++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields > maxHeaderFields || cookies > maxCookies
|
||||
}
|
||||
|
||||
// answerQuestion runs the checks on the visitor's request a question
|
||||
// carries, and answers rproxy what to do with it. A denied request's counting
|
||||
// is finished here, and an allowed one's by its report.
|
||||
|
||||
Reference in New Issue
Block a user