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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// A ban is made only once the one before has ended, so only the
|
ban := activeBan(*bans, now)
|
||||||
// last can be active.
|
if ban != nil {
|
||||||
last := &(*bans)[len(*bans)-1]
|
ban.Notes.Requests++
|
||||||
if last.ActiveAt(now) {
|
ban.Notes.Refused++
|
||||||
last.Notes.Requests++
|
|
||||||
last.Notes.Refused++
|
|
||||||
|
|
||||||
return *last, true
|
return *ban, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ban{}, false
|
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
|
// BanForLimit bans netblock at now for a broken limit, with notes, and
|
||||||
// returns the ban. A first ban lasts LimitBanDuration. A ban made within
|
// returns the ban. A first ban lasts LimitBanDuration. A ban made within
|
||||||
// LimitBanRepeatWindow after the netblock's last ban ended lasts
|
// 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)
|
bans, found := l.netblocks.Get(netblock)
|
||||||
if found {
|
if found {
|
||||||
last = &(*bans)[len(*bans)-1]
|
active := activeBan(*bans, now)
|
||||||
if last.ActiveAt(now) {
|
if active != nil {
|
||||||
return *last
|
return *active
|
||||||
}
|
}
|
||||||
|
|
||||||
|
last = &(*bans)[len(*bans)-1]
|
||||||
notes.EarlierBans = last.Notes.EarlierBans + 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) {
|
func TestLoadKeepsAtMostMaxBansDroppingTheEarliest(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -154,8 +186,10 @@ func TestLoadKeepsAtMostMaxBansDroppingTheEarliest(t *testing.T) {
|
|||||||
func TestLoadReplacesTheBansHeld(t *testing.T) {
|
func TestLoadReplacesTheBansHeld(t *testing.T) {
|
||||||
t.Parallel()
|
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 := defaultRules()
|
||||||
rules.MaxBans = 2
|
rules.MaxBans = 3
|
||||||
ledger := bans.New(rules)
|
ledger := bans.New(rules)
|
||||||
kept := bans.Ban{Netblock: netip.MustParsePrefix("2001:db8::/64"), Start: midnight()}
|
kept := bans.Ban{Netblock: netip.MustParsePrefix("2001:db8::/64"), Start: midnight()}
|
||||||
ledger.Load([]bans.Ban{
|
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
|
// 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
|
// bans.json is taken in, that ban is lifted.
|
||||||
// one ban, makes another without dropping any.
|
|
||||||
ledger.Load([]bans.Ban{kept})
|
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())
|
_, banned := ledger.Check(netip.MustParseAddr("203.0.113.9"), midnight())
|
||||||
if banned {
|
if banned {
|
||||||
t.Error("a ban left out of the second load still refuses")
|
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)
|
t.Errorf("the ledger holds %+v, want %+v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -378,9 +378,11 @@ func (f *Files) writeFile(name string) error {
|
|||||||
return err
|
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)
|
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
|
// 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
|
// 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
|
// 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
|
// 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 {
|
func write(dir, name string, data []byte) error {
|
||||||
path := filepath.Join(dir, name)
|
path := filepath.Join(dir, name)
|
||||||
temporary := path + ".tmp"
|
temporary := path + ".tmp"
|
||||||
@@ -632,10 +634,13 @@ func write(dir, name string, data []byte) error {
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = os.Remove(temporary)
|
_ = os.Remove(temporary)
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
directory, err := os.Open(dir) //nolint:gosec // SWWAF_STATE_DIR itself
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user