Observe mode: log what would be refused, refuse nothing (closes #78)
check / check (push) Successful in 3m54s
check / check (push) Successful in 3m54s
SWWAF_MODE (default enforce) takes enforce or observe. In observe mode a request that SWWAF_DENY_NETS, a ban, the country lists or a rate limit would refuse is passed to the app, and its log line names that refusal in would_action. The size and time limits and the 401 still apply. A broken limit makes no ban; bans read from bans.json are kept but refuse nothing, and Ledger.Find reads them without counting a refusal in their notes. Judgement call: in observe mode a broken limit does not reset the client's counters, since the reset comes with the ban. Judgement call: a request a ban would refuse keeps ban_expires. Model: opus-5-5
This commit was merged in pull request #81.
This commit is contained in:
+50
-28
@@ -109,38 +109,24 @@ func (h *handler) newRequest(w http.ResponseWriter, r *http.Request) *request {
|
||||
|
||||
// check is the one place where a request can be refused once its client
|
||||
// is known, before its body is read or anything reaches the app. It
|
||||
// returns nil to let the request through. A client in SWWAF_ALLOW_NETS
|
||||
// skips every check but the size limit. For any other client,
|
||||
// SWWAF_DENY_NETS comes first, then a ban on its netblock, so that a
|
||||
// client either refuses is not looked up, and then the country lists; a
|
||||
// request any of them refuses is not counted for the rate limits. Then
|
||||
// come the rate limits, unless the client is in
|
||||
// SWWAF_RATE_LIMIT_EXEMPT_NETS, so that every other request is counted,
|
||||
// one refused for its size too. Every refusal but the size limit's is
|
||||
// answered with SWWAF_BAN_RESPONSE. ctx is the request's own context.
|
||||
// returns nil to let the request through. The checks of checkClient come
|
||||
// first, answered with SWWAF_BAN_RESPONSE, and then the size limit, so
|
||||
// that a request the rate limits count is counted even when it is
|
||||
// refused for its size. In observe mode a request checkClient refuses
|
||||
// goes on to the size limit like any other. ctx is the request's own
|
||||
// context.
|
||||
func (rq *request) check(ctx context.Context) *refusal {
|
||||
cfg := rq.h.config
|
||||
allowed := isInside(rq.client, cfg.AllowNets)
|
||||
exempt := isInside(rq.client, cfg.RateLimitExemptNets)
|
||||
now := rq.h.now()
|
||||
action := rq.checkClient(ctx)
|
||||
if action != "" {
|
||||
if !rq.h.config.Observe {
|
||||
return rq.banResponse(action)
|
||||
}
|
||||
|
||||
if !allowed && isInside(rq.client, cfg.DenyNets) {
|
||||
return rq.banResponse(requestlog.ActionDenied)
|
||||
// The log line names what enforce mode would have done.
|
||||
rq.line.WouldAction = action
|
||||
}
|
||||
|
||||
if !allowed && rq.banned(now) {
|
||||
return rq.banResponse(requestlog.ActionBanned)
|
||||
}
|
||||
|
||||
if !allowed && rq.countryDenied(ctx) {
|
||||
return rq.banResponse(requestlog.ActionCountryDenied)
|
||||
}
|
||||
|
||||
if !allowed && !exempt && rq.limitBroken(now) {
|
||||
return rq.banResponse(requestlog.ActionRateLimited)
|
||||
}
|
||||
|
||||
maxBytes := cfg.RequestMaxBytes
|
||||
maxBytes := rq.h.config.RequestMaxBytes
|
||||
if maxBytes > 0 && rq.in.ContentLength > maxBytes {
|
||||
return &refusal{
|
||||
status: http.StatusRequestEntityTooLarge,
|
||||
@@ -152,6 +138,42 @@ func (rq *request) check(ctx context.Context) *refusal {
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkClient runs the checks on the request's client, and returns the
|
||||
// action of the first that refuses the request, or "" when none does. A
|
||||
// client in SWWAF_ALLOW_NETS skips them. For any other client,
|
||||
// SWWAF_DENY_NETS comes first, then a ban on its netblock, so that a
|
||||
// client either refuses is not looked up, and then the country lists; a
|
||||
// request any of them refuses is not counted for the rate limits. Then
|
||||
// come the rate limits, unless the client is in
|
||||
// SWWAF_RATE_LIMIT_EXEMPT_NETS, so that every other request is counted.
|
||||
// ctx is the request's own context.
|
||||
func (rq *request) checkClient(ctx context.Context) string {
|
||||
cfg := rq.h.config
|
||||
if isInside(rq.client, cfg.AllowNets) {
|
||||
return ""
|
||||
}
|
||||
|
||||
now := rq.h.now()
|
||||
|
||||
if isInside(rq.client, cfg.DenyNets) {
|
||||
return requestlog.ActionDenied
|
||||
}
|
||||
|
||||
if rq.banned(now) {
|
||||
return requestlog.ActionBanned
|
||||
}
|
||||
|
||||
if rq.countryDenied(ctx) {
|
||||
return requestlog.ActionCountryDenied
|
||||
}
|
||||
|
||||
if !isInside(rq.client, cfg.RateLimitExemptNets) && rq.limitBroken(now) {
|
||||
return requestlog.ActionRateLimited
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// forward passes the request to the app and the app's answer back. ctx
|
||||
// is the request's own context.
|
||||
func (rq *request) forward(ctx context.Context) {
|
||||
|
||||
Reference in New Issue
Block a user