Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09eac675dc |
+23
-10
@@ -160,20 +160,32 @@ func (l *Ledger) Check(client netip.Addr, now time.Time) (Ban, bool) {
|
||||
continue
|
||||
}
|
||||
|
||||
// A ban is made only once the one before has ended, so only the
|
||||
// last can be active.
|
||||
last := &(*bans)[len(*bans)-1]
|
||||
if last.ActiveAt(now) {
|
||||
last.Notes.Requests++
|
||||
last.Notes.Refused++
|
||||
ban := activeBan(*bans, now)
|
||||
if ban != nil {
|
||||
ban.Notes.Requests++
|
||||
ban.Notes.Refused++
|
||||
|
||||
return *last, true
|
||||
return *ban, true
|
||||
}
|
||||
}
|
||||
|
||||
return Ban{}, false
|
||||
}
|
||||
|
||||
// activeBan returns the ban in bans, a netblock's bans oldest first, that
|
||||
// is active at now, or nil when none is. If several are, it returns the
|
||||
// one that started last. Every ban is looked at, since a ban an admin adds
|
||||
// to bans.json can start before the netblock's others and outlast them.
|
||||
func activeBan(bans []Ban, now time.Time) *Ban {
|
||||
for i := len(bans) - 1; i >= 0; i-- {
|
||||
if bans[i].ActiveAt(now) {
|
||||
return &bans[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BanForLimit bans netblock at now for a broken limit, with notes, and
|
||||
// returns the ban. A first ban lasts LimitBanDuration. A ban made within
|
||||
// LimitBanRepeatWindow after the netblock's last ban ended lasts
|
||||
@@ -190,11 +202,12 @@ func (l *Ledger) BanForLimit(netblock netip.Prefix, now time.Time, notes Notes)
|
||||
|
||||
bans, found := l.netblocks.Get(netblock)
|
||||
if found {
|
||||
last = &(*bans)[len(*bans)-1]
|
||||
if last.ActiveAt(now) {
|
||||
return *last
|
||||
active := activeBan(*bans, now)
|
||||
if active != nil {
|
||||
return *active
|
||||
}
|
||||
|
||||
last = &(*bans)[len(*bans)-1]
|
||||
notes.EarlierBans = last.Notes.EarlierBans + 1
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,38 @@ func TestLoadedBanRefusesEveryClientInItsNetblock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermanentBanStartedBeforeAnEndedOneRefuses(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// As when an admin adds a permanent ban to bans.json with a start
|
||||
// before that of the netblock's ban that has ended.
|
||||
netblock := netip.MustParsePrefix("203.0.113.0/24")
|
||||
permanent := bans.Ban{Netblock: netblock, Start: midnight().Add(-time.Hour)}
|
||||
ended := bans.Ban{
|
||||
Netblock: netblock,
|
||||
Start: midnight(),
|
||||
Expires: midnight().Add(time.Hour),
|
||||
}
|
||||
|
||||
ledger := bans.New(defaultRules())
|
||||
ledger.Load([]bans.Ban{permanent, ended})
|
||||
|
||||
now := midnight().Add(2 * time.Hour)
|
||||
|
||||
ban, banned := ledger.Check(netip.MustParseAddr("203.0.113.9"), now)
|
||||
if !banned || !ban.Permanent() {
|
||||
t.Errorf("the client is refused: %t, under %+v, want under the permanent ban",
|
||||
banned, ban)
|
||||
}
|
||||
|
||||
// A limit broken now makes no shorter ban over the permanent one.
|
||||
ban = ledger.BanForLimit(netblock, now, bans.Notes{})
|
||||
if !ban.Permanent() || len(ledger.Bans(netblock)) != 2 {
|
||||
t.Errorf("a broken limit returned %+v and left the netblock %d bans, "+
|
||||
"want the permanent ban and 2", ban, len(ledger.Bans(netblock)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadKeepsAtMostMaxBansDroppingTheEarliest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -154,8 +186,10 @@ func TestLoadKeepsAtMostMaxBansDroppingTheEarliest(t *testing.T) {
|
||||
func TestLoadReplacesTheBansHeld(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Room for three bans, so that the second load, were it added to the
|
||||
// two bans held, would drop none of them to make room.
|
||||
rules := defaultRules()
|
||||
rules.MaxBans = 2
|
||||
rules.MaxBans = 3
|
||||
ledger := bans.New(rules)
|
||||
kept := bans.Ban{Netblock: netip.MustParsePrefix("2001:db8::/64"), Start: midnight()}
|
||||
ledger.Load([]bans.Ban{
|
||||
@@ -164,18 +198,22 @@ func TestLoadReplacesTheBansHeld(t *testing.T) {
|
||||
})
|
||||
|
||||
// Loaded again without the first ban, as when an admin's edit of
|
||||
// bans.json is taken in: that ban is lifted, and the ledger, holding
|
||||
// one ban, makes another without dropping any.
|
||||
// bans.json is taken in, that ban is lifted.
|
||||
ledger.Load([]bans.Ban{kept})
|
||||
made := ledger.BanForLimit(netip.MustParsePrefix("198.51.100.7/32"), midnight(),
|
||||
bans.Notes{})
|
||||
|
||||
_, banned := ledger.Check(netip.MustParseAddr("203.0.113.9"), midnight())
|
||||
if banned {
|
||||
t.Error("a ban left out of the second load still refuses")
|
||||
}
|
||||
|
||||
if got, want := ledger.Snapshot(), []bans.Ban{made, kept}; !slices.Equal(got, want) {
|
||||
// The ledger holds one ban, so it makes two more without dropping any.
|
||||
first := ledger.BanForLimit(netip.MustParsePrefix("198.51.100.7/32"), midnight(),
|
||||
bans.Notes{})
|
||||
second := ledger.BanForLimit(netip.MustParsePrefix("198.51.100.8/32"), midnight(),
|
||||
bans.Notes{})
|
||||
|
||||
want := []bans.Ban{first, second, kept}
|
||||
if got := ledger.Snapshot(); !slices.Equal(got, want) {
|
||||
t.Errorf("the ledger holds %+v, want %+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,9 +378,11 @@ func (f *Files) writeFile(name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// The file holds data from here on, even if the directory sync fails,
|
||||
// so that its next read does not take it for an admin's edit.
|
||||
f.sums[name] = sha256.Sum256(data)
|
||||
|
||||
return nil
|
||||
return syncDirectory(f.params.Dir)
|
||||
}
|
||||
|
||||
// encode returns the state file name as smallwebwaf writes it, from a
|
||||
@@ -620,7 +622,7 @@ func position(data []byte, err error) string {
|
||||
// write writes data to the file name in dir so that a crash at any
|
||||
// moment leaves either the old file or the new one, whole: data goes to a
|
||||
// temporary file in the same directory, which is synced and renamed over
|
||||
// name, and then the directory is synced, so that the rename lasts.
|
||||
// name. syncDirectory must follow, so that the rename lasts.
|
||||
func write(dir, name string, data []byte) error {
|
||||
path := filepath.Join(dir, name)
|
||||
temporary := path + ".tmp"
|
||||
@@ -632,10 +634,13 @@ func write(dir, name string, data []byte) error {
|
||||
|
||||
if err != nil {
|
||||
_ = os.Remove(temporary)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// syncDirectory syncs dir to the disk, so that a rename in it lasts.
|
||||
func syncDirectory(dir string) error {
|
||||
directory, err := os.Open(dir) //nolint:gosec // SWWAF_STATE_DIR itself
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user