package bans_test import ( "net/netip" "reflect" "testing" "time" "sneak.berlin/go/smallwebwaf/internal/bans" ) // scenario is the scenario of the tests' CrowdSec decisions. const scenario = "crowdsecurity/ssh-bf" func TestCrowdSecBanLastsUntilTheDecisionEnds(t *testing.T) { t.Parallel() ledger := bans.New(defaultRules()) netblock := netip.MustParsePrefix("203.0.113.9/32") expires := midnight().Add(4 * time.Hour) // The ban that would be made is not made. would, wouldBan := ledger.WouldBanForCrowdSec(netblock, midnight(), expires, scenario, bans.Notes{}) if !wouldBan || len(ledger.Bans(netblock)) != 0 { t.Errorf("would ban %t, and the ledger holds %+v, want true and nothing", wouldBan, ledger.Bans(netblock)) } const reason = "CrowdSec's decision for " + scenario ban, made := ledger.BanForCrowdSec(netblock, midnight(), expires, scenario, bans.Notes{}) if !made || !reflect.DeepEqual(ban, would) || ban.Cause != bans.CauseCrowdSec || !ban.Expires.Equal(expires) || ban.Reason != reason || ledger.Made(bans.CauseCrowdSec) != 1 { t.Errorf("made %t the ban %+v, want the one that would be made, %+v, for "+ "crowdsec until %s", made, ban, would, expires) } // A second decision on the netblock while the ban lasts makes no other. again, made := ledger.BanForCrowdSec(netblock, midnight().Add(time.Hour), expires.Add(time.Hour), scenario, bans.Notes{}) if made || !again.Expires.Equal(expires) || ledger.Made(bans.CauseCrowdSec) != 1 { t.Errorf("made %t the ban %+v while the first lasts, want none", made, again) } } func TestCrowdSecBanIsNeverMadePermanent(t *testing.T) { t.Parallel() ledger := bans.New(defaultRules()) netblock := netip.MustParsePrefix("203.0.113.9/32") expires := midnight().Add(4 * time.Hour) ledger.BanForCrowdSec(netblock, midnight(), expires, scenario, bans.Notes{}) // A request as the ban ends is refused, and leaves it as it is. last := expires.Add(-time.Nanosecond) held, banned, madePermanent := ledger.Check(netblock.Addr(), last) if !banned || madePermanent || !held.Expires.Equal(expires) || ledger.WouldBePermanent(netblock, last, bans.CauseCrowdSec) { t.Errorf("as the ban ends, banned %t with %+v, made permanent %t, want "+ "refused under the ban as it was", banned, held, madePermanent) } if _, banned, _ := ledger.Check(netblock.Addr(), expires); banned { t.Error("the ban refuses a request once the decision has ended") } } func TestCrowdSecBanIsCountedAndDoesNotLengthenTheNextBanForALimit(t *testing.T) { t.Parallel() ledger := bans.New(defaultRules()) netblock := netip.MustParsePrefix("203.0.113.9/32") // Three times the three days would be permanent; a limit broken as the // ban for CrowdSec's decision ends bans for an hour, as a first broken // limit does. crowdSec, _ := ledger.BanForCrowdSec(netblock, midnight(), midnight().Add(3*day), scenario, bans.Notes{}) limit, _ := ledger.BanForLimit(netblock, crowdSec.Expires, bans.Notes{}) if limit.Expires.Sub(limit.Start) != time.Hour || limit.Notes.EarlierBans != (bans.EarlierBans{CrowdSec: 1}) { t.Errorf("the ban for a limit is %+v, want one of an hour after one for crowdsec", limit) } }