Admin endpoints for bans and clients on the single listener (closes #27)
check / check (push) Successful in 4m26s
check / check (push) Successful in 4m26s
SWWAF_ADMIN_TOKEN, or its _FILE form, opens GET and POST /_smallwebwaf/bans, DELETE /_smallwebwaf/bans/<client> and GET /_smallwebwaf/clients/<ip>. Unset, they answer 404; a missing or wrong token gets 401, in observe mode too. They go through every check, as the metrics do. POST takes a netblock, not IPv4-mapped and without a zone, or a client's address, a duration or permanent, and a reason, and makes an admin ban even while another lasts. DELETE lifts every active ban covering the address, kept and marked lifted. Bans come back as bans.json entries; a client as clients.json holds it, with its bans. Judgement call: answers leave out bans.json's version field. Judgement call: DELETE takes an address, not a netblock. Rule suppressed: gosec G304 on a test reading bans.json. Model: opus-5-5
This commit was merged in pull request #92.
This commit is contained in:
@@ -184,3 +184,103 @@ func TestLoadEditCountsTheBansAnAdminMade(t *testing.T) {
|
||||
ledger.Made(bans.CauseAdmin), ledger.Made(bans.CauseLimit))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminsBanIsMadeWhileAnotherLasts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
netblock := netip.MustParsePrefix("203.0.113.0/24")
|
||||
ledger := bans.New(defaultRules())
|
||||
|
||||
// An hour's ban for a broken limit.
|
||||
ledger.BanForLimit(netblock, midnight(), bans.Notes{})
|
||||
wantChanged(t, ledger, true)
|
||||
|
||||
// A minute later an admin bans the netblock for good, named by an
|
||||
// address in it: that ban is made, and counts the other among the
|
||||
// earlier bans.
|
||||
now := midnight().Add(time.Minute)
|
||||
want := bans.Ban{
|
||||
Netblock: netblock,
|
||||
Start: now,
|
||||
Cause: bans.CauseAdmin,
|
||||
Reason: "probes for logins",
|
||||
Notes: bans.Notes{EarlierBans: bans.EarlierBans{Limit: 1}},
|
||||
}
|
||||
|
||||
got := ledger.BanForAdmin(netip.MustParsePrefix("203.0.113.9/24"), now, time.Time{},
|
||||
"probes for logins")
|
||||
if got != want {
|
||||
t.Errorf("the admin's ban is\n%+v\nwant\n%+v", got, want)
|
||||
}
|
||||
|
||||
wantChanged(t, ledger, true)
|
||||
|
||||
if made := ledger.Made(bans.CauseAdmin); made != 1 {
|
||||
t.Errorf("%d bans made by an admin, want 1", made)
|
||||
}
|
||||
|
||||
// It refuses once the ban for the limit has ended.
|
||||
ban, banned := ledger.Find(netblock.Addr(), midnight().Add(2*time.Hour))
|
||||
if !banned || ban != want {
|
||||
t.Errorf("after the limit's ban the netblock is under %+v (%t), want %+v",
|
||||
ban, banned, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiftLiftsEveryActiveBanCoveringTheClient(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client := netip.MustParseAddr("203.0.113.9")
|
||||
own := netip.MustParsePrefix("203.0.113.9/32")
|
||||
wide := netip.MustParsePrefix("203.0.113.0/24")
|
||||
other := netip.MustParsePrefix("203.0.113.10/32")
|
||||
|
||||
ledger := bans.New(defaultRules())
|
||||
ledger.Load([]bans.Ban{
|
||||
// Ended an hour ago.
|
||||
{
|
||||
Netblock: own, Start: midnight().Add(-2 * time.Hour),
|
||||
Expires: midnight().Add(-time.Hour), Cause: bans.CauseLimit,
|
||||
},
|
||||
// Active, on the client's address and on its /24.
|
||||
{
|
||||
Netblock: own, Start: midnight(), Expires: midnight().Add(time.Hour),
|
||||
Cause: bans.CauseLimit,
|
||||
},
|
||||
{Netblock: wide, Start: midnight(), Cause: bans.CauseAdmin},
|
||||
// Another client's.
|
||||
{Netblock: other, Start: midnight(), Cause: bans.CauseAdmin},
|
||||
})
|
||||
|
||||
now := midnight().Add(time.Minute)
|
||||
|
||||
lifted := ledger.Lift(client, now)
|
||||
if len(lifted) != 2 || lifted[0].Lifted != now || lifted[1].Lifted != now {
|
||||
t.Errorf("lifted %+v, want the two active bans covering the client", lifted)
|
||||
}
|
||||
|
||||
wantChanged(t, ledger, true)
|
||||
|
||||
if _, banned := ledger.Check(client, now); banned {
|
||||
t.Error("the client is still banned")
|
||||
}
|
||||
|
||||
if _, banned := ledger.Check(other.Addr(), now); !banned {
|
||||
t.Error("the other client's ban was lifted")
|
||||
}
|
||||
|
||||
// The lifted bans are kept, and the one that had ended is not lifted.
|
||||
covering := ledger.Covering(client)
|
||||
if len(covering) != 3 || covering[0].Netblock != wide ||
|
||||
!covering[1].Lifted.IsZero() || covering[2].Lifted != now {
|
||||
t.Errorf("the bans covering the client are %+v, want the /24's and both "+
|
||||
"of its own, the earlier not lifted", covering)
|
||||
}
|
||||
|
||||
// With none active, nothing is lifted or changed.
|
||||
if lifted = ledger.Lift(client, now); len(lifted) != 0 {
|
||||
t.Errorf("lifted %+v again", lifted)
|
||||
}
|
||||
|
||||
wantChanged(t, ledger, false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user