servewaf always answers rproxy well within its time limit (closes #182)
check / check (push) Failing after 15m18s
check / check (push) Failing after 15m18s
A question waits on GeoJS at most 800ms, the new default of SWWAF_LOOKUP_TIMEOUT. In enforce mode it does not wait at all when it is over the header limits, on a fetched blocklist with deny, on the CrowdSec decision list, or over a whole rate limit. Not waiting never lowers a client's limit. 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,62 @@ 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 request is one that does not wait
|
||||
// for GeoJS, since the checks will refuse it 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. In observe mode, which refuses nothing, none is.
|
||||
// rproxy lets a request through when servewaf answers too late.
|
||||
func (rq *request) refusedWhateverTheAnswer(now time.Time) bool {
|
||||
if rq.h.config.Observe {
|
||||
return false
|
||||
}
|
||||
|
||||
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 being one unless it is empty or only spaces and tabs. 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. It trims only spaces and control
|
||||
// characters, of which a header value can hold only tabs, so a part of Unicode
|
||||
// spaces is a cookie to it.
|
||||
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.Trim(part, " \t") != "" {
|
||||
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