Name request log actions so they say what they mean (closes #229)
check / check (push) Successful in 6m30s
check / check (push) Successful in 6m30s
The request log's `action` values now say what they mean, as sneak named them: `request_header_too_large`, `client_is_rate_limited`, `blocked_by_block_rule` and `client_denied`, with no aliases. `banned` splits into `temporarily_banned` and `permanently_banned`, by whether the ban runs out. When a permanent ban also covers a client, that ban is the one reported. Trips a reader: a request over a rate limit stays `client_is_rate_limited` even when the ban it makes is permanent. Disclosure: the offence key `rule_blocked` is renamed in `clients.json` too, so an existing file holding it stops the start and must be removed. Its `version` stays 1. Model: opus-5-5
This commit was merged in pull request #233.
This commit is contained in:
1 parent
6850cb0e9f
commit
aede434f6a
48 files changed
+522
-300
No files matched your search
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||
"sneak.berlin/go/smallwebwaf/internal/bans"
|
||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||
)
|
||||
@@ -14,6 +15,93 @@ import (
|
||||
// observe is the value of SWW_MODE for observe mode.
|
||||
const observe = "observe"
|
||||
|
||||
func TestBanActionSaysWhetherTheBanRunsOut(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const trapPath = "/wp-login.php"
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
cause string
|
||||
// endsIn is how long after the request the ban ends: negative for a
|
||||
// ban that has ended, and zero for a permanent ban.
|
||||
endsIn time.Duration
|
||||
path string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
"temporary ban", bans.CauseAdmin, time.Hour, "/",
|
||||
requestlog.ActionTemporarilyBanned,
|
||||
},
|
||||
{"permanent ban", bans.CauseAdmin, 0, "/", requestlog.ActionPermanentlyBanned},
|
||||
{
|
||||
"request that makes a ban permanent", bans.CauseAttack, time.Hour, "/",
|
||||
requestlog.ActionPermanentlyBanned,
|
||||
},
|
||||
{
|
||||
"trap path after an attack ban ended", bans.CauseAttack, -time.Hour,
|
||||
trapPath, requestlog.ActionPermanentlyBanned,
|
||||
},
|
||||
{
|
||||
"first trap path after another ban ended", bans.CauseAdmin, -time.Hour,
|
||||
trapPath, requestlog.ActionTemporarilyBanned,
|
||||
},
|
||||
} {
|
||||
for _, modeValue := range []string{"enforce", observe} {
|
||||
t.Run(tc.name+" in "+modeValue, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, clk, server := startWithClock(t, "", map[string]string{
|
||||
trapPaths: trapPathList, mode: modeValue,
|
||||
})
|
||||
|
||||
ban := bans.Ban{
|
||||
Netblock: netip.MustParsePrefix(client + "/32"),
|
||||
Start: clk.Now().Add(-2 * time.Hour),
|
||||
Cause: tc.cause,
|
||||
}
|
||||
if tc.endsIn != 0 {
|
||||
ban.Expires = clk.Now().Add(tc.endsIn)
|
||||
}
|
||||
|
||||
server.Ledger.Load([]bans.Ban{ban})
|
||||
|
||||
if modeValue == observe {
|
||||
line := s.request(client, tc.path, http.StatusOK, requestlog.ActionForward)
|
||||
wantWouldAction(t, line, tc.want)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
s.request(client, tc.path, http.StatusForbidden, tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestObserveModeNamesAPermanentBanWhoseAlertWouldBeSent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, clk, server, queue := startWithAlerts(t, map[string]string{
|
||||
trapPaths: trapPathList, mode: observe,
|
||||
})
|
||||
|
||||
server.Ledger.Load([]bans.Ban{{
|
||||
Netblock: netip.MustParsePrefix(client + "/32"),
|
||||
Start: clk.Now().Add(-2 * time.Hour),
|
||||
Expires: clk.Now().Add(-time.Hour),
|
||||
Cause: bans.CauseAttack,
|
||||
}})
|
||||
|
||||
line := s.request(client, "/wp-login.php", http.StatusOK, requestlog.ActionForward)
|
||||
wantWouldAction(t, line, requestlog.ActionPermanentlyBanned)
|
||||
|
||||
waiting := queue.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||
if len(waiting) != 1 || waiting[0].Event != alerts.EventPermanentBan {
|
||||
t.Errorf("alerts waiting are %+v, want one %s", waiting, alerts.EventPermanentBan)
|
||||
}
|
||||
}
|
||||
|
||||
func TestObserveModeNamesTheHeuristicThatWouldHaveRefused(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -22,7 +110,7 @@ func TestObserveModeNamesTheHeuristicThatWouldHaveRefused(t *testing.T) {
|
||||
})
|
||||
|
||||
line := s.get(client, http.StatusOK, requestlog.ActionForward)
|
||||
wantWouldAction(t, line, requestlog.ActionDenied)
|
||||
wantWouldAction(t, line, requestlog.ActionClientDenied)
|
||||
wantHeuristic(t, line, "deny_nets", `{"netblock": "`+client+`/32"}`)
|
||||
}
|
||||
|
||||
@@ -69,10 +157,10 @@ func TestObserveModeForwardsWhatEnforceModeRefuses(t *testing.T) {
|
||||
s.get(fromDE, http.StatusOK, requestlog.ActionForward)
|
||||
|
||||
for _, sent := range []struct{ from, refusal string }{
|
||||
{denied, requestlog.ActionDenied},
|
||||
{banned, requestlog.ActionBanned},
|
||||
{fromKP, requestlog.ActionDenied},
|
||||
{fromDE, requestlog.ActionRateLimited},
|
||||
{denied, requestlog.ActionClientDenied},
|
||||
{banned, requestlog.ActionTemporarilyBanned},
|
||||
{fromKP, requestlog.ActionClientDenied},
|
||||
{fromDE, requestlog.ActionClientIsRateLimited},
|
||||
} {
|
||||
if !tc.observe {
|
||||
line := s.get(sent.from, http.StatusForbidden, sent.refusal)
|
||||
@@ -110,7 +198,7 @@ func TestObserveModeMakesNoBanAndKeepsTheBansItHas(t *testing.T) {
|
||||
|
||||
for range 2 {
|
||||
line := s.get(client, http.StatusOK, requestlog.ActionForward)
|
||||
wantWouldAction(t, line, requestlog.ActionRateLimited)
|
||||
wantWouldAction(t, line, requestlog.ActionClientIsRateLimited)
|
||||
|
||||
if line.LimitHit != minute || line.Offence != requestlog.OffenceLimit ||
|
||||
line.BanExpires != "" {
|
||||
@@ -123,7 +211,7 @@ func TestObserveModeMakesNoBanAndKeepsTheBansItHas(t *testing.T) {
|
||||
// The ban read from bans.json refuses nothing, and so counts no
|
||||
// refusal in its notes, but is kept.
|
||||
line := s.get(otherClient, http.StatusOK, requestlog.ActionForward)
|
||||
wantWouldAction(t, line, requestlog.ActionBanned)
|
||||
wantWouldAction(t, line, requestlog.ActionTemporarilyBanned)
|
||||
|
||||
if line.BanExpires != requestlog.FormatTime(kept.Expires) {
|
||||
t.Errorf("log line has ban_expires %q, want %s", line.BanExpires,
|
||||
|
||||
Reference in new issue
Block a user