Keep the bans, the clients and GeoJS's answers in state files (closes #17)
check / check (push) Successful in 3m53s

smallwebwaf now copies its state to bans.json, clients.json and
lookups.json in SWWAF_STATE_DIR, as "Persistent state" in SPEC.md
describes, and reads them back at start, so a restart lifts no ban and
gives no client a fresh allowance. Each client gains a history, and a
ban's notes count the netblock's requests. bans.json is written
SWWAF_STATE_WRITE_DELAY after a ban, every file every
SWWAF_STATE_COUNTER_INTERVAL and at the stop, each through a synced
temporary file renamed over it. A file that does not parse, an unknown
version or an unwritable directory stops the start. The image gets
/var/lib/smallwebwaf, which the run script gives to the smallwebwaf user.

Deviation: no AS number or name, and no ban cause, reason or lifting yet.

Model: opus-5-5
This commit is contained in:
2026-10-06 04:14:27 +00:00
parent 73ca94f850
commit 06aa814216
27 changed files with 2444 additions and 242 deletions
+6 -4
View File
@@ -135,7 +135,7 @@ func TestCheckRefusesWhileTheBanLastsAndCountsTheRefusals(t *testing.T) {
ledger := bans.New(defaultRules())
netblock := netip.MustParsePrefix("203.0.113.9/32")
ban := ledger.BanForLimit(netblock, midnight(), bans.Notes{})
ban := ledger.BanForLimit(netblock, midnight(), bans.Notes{Requests: 5})
for range 3 {
got, banned := ledger.Check(netblock, ban.Expires.Add(-time.Nanosecond))
@@ -154,9 +154,11 @@ func TestCheckRefusesWhileTheBanLastsAndCountsTheRefusals(t *testing.T) {
t.Error("the ban did not end")
}
refused := ledger.Bans(netblock)[0].Notes.Refused
if refused != 3 {
t.Errorf("the notes count %d refused requests, want 3", refused)
// The netblock's requests went from 5 to 8 with the three refused.
notes := ledger.Bans(netblock)[0].Notes
if notes.Refused != 3 || notes.Requests != 8 {
t.Errorf("the notes count %d refused requests of %d, want 3 of 8",
notes.Refused, notes.Requests)
}
}