sneak ruled to remove it. Gone: the heuristic crowdsec, the ban cause crowdsec and its earlier_bans count, the tag crowdsec, the settings SWW_WAF_CROWDSEC_LAPI_URL and SWW_WAF_CROWDSEC_LAPI_KEY, the decision list fetch and parse in internal/reputation, their tests, and every mention in the README, SPEC.md and docs/. EVALUATION.md keeps its comparison of tools. The ban ledger's internal ban function loses its expires parameter, which only CrowdSec bans used. Model: opus-5-5
This commit is contained in:
1 parent
50c50bfcae
commit
478c3affad
27 files changed
+125
-1851
No files matched your search
+35
-108
@@ -1,10 +1,8 @@
|
||||
// Package bans is the ban ledger: the bans sww makes on the
|
||||
// netblocks of clients that break a rate limit, a byte limit or the refusal
|
||||
// limit, show a clear sign of attack or are listed by the CrowdSec
|
||||
// decision list, and
|
||||
// those an admin makes, with their notes, as the "Bans" section of SPEC.md
|
||||
// describes. The bans are kept in memory, and written to bans.json and
|
||||
// read from it by the state package.
|
||||
// limit or show a clear sign of attack, and those an admin makes, with their
|
||||
// notes, as the "Bans" section of SPEC.md describes. The bans are kept in
|
||||
// memory, and written to bans.json and read from it by the state package.
|
||||
package bans
|
||||
|
||||
import (
|
||||
@@ -28,9 +26,6 @@ const (
|
||||
// CauseAdmin is a ban an admin made, or one sww made that an
|
||||
// admin keeps. It is never dropped.
|
||||
CauseAdmin = "admin"
|
||||
// CauseCrowdSec is a ban sww made for a client the CrowdSec
|
||||
// decision list lists. It ends when CrowdSec's decision does.
|
||||
CauseCrowdSec = "crowdsec"
|
||||
)
|
||||
|
||||
// repeatFactor is how many times as long as the netblock's last ban a ban
|
||||
@@ -45,8 +40,8 @@ type Params struct {
|
||||
// LimitBanDuration is how long a first ban for a broken limit lasts.
|
||||
LimitBanDuration time.Duration
|
||||
// LimitBanRepeatWindow is how soon after the end of the netblock's
|
||||
// ban that ended last, other than one for a clear sign of attack or for
|
||||
// CrowdSec's decision, a broken limit counts as a repeat, which bans for
|
||||
// ban that ended last, other than one for a clear sign of attack, a
|
||||
// broken limit counts as a repeat, which bans for
|
||||
// repeatFactor times as long as that ban.
|
||||
LimitBanRepeatWindow time.Duration
|
||||
// A ban for a broken limit that would be longer than
|
||||
@@ -68,11 +63,10 @@ type Ban struct {
|
||||
Start time.Time
|
||||
// Expires is when the ban ends, zero for a permanent ban.
|
||||
Expires time.Time
|
||||
// Cause is CauseLimit, CauseAttack, CauseAdmin or CauseCrowdSec.
|
||||
// Cause is CauseLimit, CauseAttack or CauseAdmin.
|
||||
Cause string
|
||||
// Reason is a short text: for a ban sww made, the limit broken,
|
||||
// the rule that matched or the scenario of CrowdSec's decision; for an
|
||||
// admin's, what the admin wrote.
|
||||
// Reason is a short text: for a ban sww made, the limit broken or
|
||||
// the rule that matched; for an admin's, what the admin wrote.
|
||||
Reason string
|
||||
// Lifted is when an admin lifted the ban, zero while no admin has. A
|
||||
// lifted ban refuses nothing, and does not make the netblock's next
|
||||
@@ -133,8 +127,7 @@ type Notes struct {
|
||||
// log's reputation names them. It is left out when none did.
|
||||
Reputation []ReputationHit `json:"reputation,omitempty"`
|
||||
// Request is the request that broke the limit, or whose bytes broke
|
||||
// it, that was the clear sign of attack, or that came from a client the
|
||||
// CrowdSec decision list lists.
|
||||
// it, or that was the clear sign of attack.
|
||||
Request Request `json:"request"`
|
||||
// Requests is how many requests the netblock has sent since it was
|
||||
// first seen, and Refused how many of them the ban has refused so
|
||||
@@ -148,9 +141,8 @@ type Notes struct {
|
||||
|
||||
// ReputationHit is a reputation source that listed a client, as a
|
||||
// reputation_hit alert's detail gives it: Source is the URL of the
|
||||
// blocklist or of the CrowdSec decision list, the DNSBL zone with its key
|
||||
// masked, or "abuseipdb", and Score, for AbuseIPDB alone, its score of the
|
||||
// client.
|
||||
// blocklist, the DNSBL zone with its key masked, or "abuseipdb", and Score,
|
||||
// for AbuseIPDB alone, its score of the client.
|
||||
type ReputationHit struct {
|
||||
Source string `json:"source"`
|
||||
Score *int64 `json:"score,omitempty"`
|
||||
@@ -158,10 +150,9 @@ type ReputationHit struct {
|
||||
|
||||
// EarlierBans counts a netblock's bans before a ban, by cause.
|
||||
type EarlierBans struct {
|
||||
Limit int `json:"limit"`
|
||||
Attack int `json:"attack"`
|
||||
Admin int `json:"admin"`
|
||||
CrowdSec int `json:"crowdsec"`
|
||||
Limit int `json:"limit"`
|
||||
Attack int `json:"attack"`
|
||||
Admin int `json:"admin"`
|
||||
}
|
||||
|
||||
// Request is a request in a ban's notes. Each text is cut to 256 bytes.
|
||||
@@ -303,8 +294,7 @@ func activeBan(bans []Ban, now time.Time) *Ban {
|
||||
// BanForLimit bans netblock at now for a broken limit, with notes, and
|
||||
// returns the ban, and true. A first ban lasts LimitBanDuration. A ban
|
||||
// made within LimitBanRepeatWindow after the netblock's ban that ended
|
||||
// last, other than one for a clear sign of attack or for CrowdSec's
|
||||
// decision, or a lifted one, lasts
|
||||
// last, other than one for a clear sign of attack or a lifted one, lasts
|
||||
// repeatFactor times as long as that one. A ban that would be longer
|
||||
// than LimitBanPermanentAbove is permanent instead. If a ban on netblock is still
|
||||
// active, as when two of its requests break a limit at once, that ban is
|
||||
@@ -318,7 +308,6 @@ func (l *Ledger) BanForLimit(
|
||||
return l.ban(
|
||||
netblock,
|
||||
now,
|
||||
time.Time{},
|
||||
CauseLimit,
|
||||
limitReason(notes),
|
||||
notes,
|
||||
@@ -334,7 +323,6 @@ func (l *Ledger) WouldBanForLimit(
|
||||
return l.ban(
|
||||
netblock,
|
||||
now,
|
||||
time.Time{},
|
||||
CauseLimit,
|
||||
limitReason(notes),
|
||||
notes,
|
||||
@@ -353,7 +341,6 @@ func (l *Ledger) BanForAttack(
|
||||
return l.ban(
|
||||
netblock,
|
||||
now,
|
||||
time.Time{},
|
||||
CauseAttack,
|
||||
attackReason(notes),
|
||||
notes,
|
||||
@@ -369,7 +356,6 @@ func (l *Ledger) WouldBanForAttack(
|
||||
return l.ban(
|
||||
netblock,
|
||||
now,
|
||||
time.Time{},
|
||||
CauseAttack,
|
||||
attackReason(notes),
|
||||
notes,
|
||||
@@ -377,51 +363,9 @@ func (l *Ledger) WouldBanForAttack(
|
||||
)
|
||||
}
|
||||
|
||||
// BanForCrowdSec bans netblock at now until expires, when CrowdSec's
|
||||
// decision on the client ends, with notes, and returns the ban, and
|
||||
// whether it made it, as BanForLimit does. Its reason is "CrowdSec's
|
||||
// decision for <scenario>", the scenario that made the decision.
|
||||
func (l *Ledger) BanForCrowdSec(
|
||||
netblock netip.Prefix,
|
||||
now, expires time.Time,
|
||||
scenario string,
|
||||
notes Notes,
|
||||
) (Ban, bool) {
|
||||
return l.ban(
|
||||
netblock,
|
||||
now,
|
||||
expires,
|
||||
CauseCrowdSec,
|
||||
crowdSecReason(scenario),
|
||||
notes,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// WouldBanForCrowdSec returns what BanForCrowdSec would, without making
|
||||
// the ban: what observe mode would have done.
|
||||
func (l *Ledger) WouldBanForCrowdSec(
|
||||
netblock netip.Prefix,
|
||||
now, expires time.Time,
|
||||
scenario string,
|
||||
notes Notes,
|
||||
) (Ban, bool) {
|
||||
return l.ban(
|
||||
netblock,
|
||||
now,
|
||||
expires,
|
||||
CauseCrowdSec,
|
||||
crowdSecReason(scenario),
|
||||
notes,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// WouldBePermanent reports whether a ban on netblock for cause, CauseLimit,
|
||||
// CauseAttack or CauseCrowdSec, made at now would be permanent, as
|
||||
// BanForLimit, BanForAttack or BanForCrowdSec would make it. It works out
|
||||
// nothing else of the ban. A ban for CrowdSec's decision is never
|
||||
// permanent: it ends with the decision.
|
||||
// WouldBePermanent reports whether a ban on netblock for cause, CauseLimit
|
||||
// or CauseAttack, made at now would be permanent, as BanForLimit or
|
||||
// BanForAttack would make it. It works out nothing else of the ban.
|
||||
func (l *Ledger) WouldBePermanent(
|
||||
netblock netip.Prefix, now time.Time, cause string,
|
||||
) bool {
|
||||
@@ -433,14 +377,11 @@ func (l *Ledger) WouldBePermanent(
|
||||
held = *bans
|
||||
}
|
||||
|
||||
switch cause {
|
||||
case CauseAttack:
|
||||
if cause == CauseAttack {
|
||||
return l.attackExpiry(held, now).IsZero()
|
||||
case CauseLimit:
|
||||
return l.limitExpiry(held, now).IsZero()
|
||||
default: // CauseCrowdSec
|
||||
return false
|
||||
}
|
||||
|
||||
return l.limitExpiry(held, now).IsZero()
|
||||
}
|
||||
|
||||
// limitReason is the reason of a ban for a broken limit, with notes.
|
||||
@@ -459,12 +400,6 @@ func attackReason(notes Notes) string {
|
||||
return "matched the rule " + notes.RuleID
|
||||
}
|
||||
|
||||
// crowdSecReason is the reason of a ban for CrowdSec's decision, which
|
||||
// scenario made.
|
||||
func crowdSecReason(scenario string) string {
|
||||
return "CrowdSec's decision for " + scenario
|
||||
}
|
||||
|
||||
// BanForAdmin bans netblock at now for an admin, with reason, until
|
||||
// expires, or for good when expires is zero, and returns the ban, whose
|
||||
// cause is CauseAdmin. Unlike BanForLimit and BanForAttack, it makes the
|
||||
@@ -704,15 +639,13 @@ func (l *Ledger) holds(netblock netip.Prefix, start time.Time) bool {
|
||||
})
|
||||
}
|
||||
|
||||
// ban bans netblock at now for cause, with reason and notes, as
|
||||
// BanForLimit, BanForAttack and BanForCrowdSec describe, and returns the
|
||||
// ban, and whether it made it. expires is when a ban for CauseCrowdSec
|
||||
// ends, and zero for the others, whose end the ledger works out. Unless
|
||||
// keep is true, the ban is not made, only returned: it is the ban that
|
||||
// would have been made.
|
||||
// ban bans netblock at now for cause, CauseLimit or CauseAttack, with
|
||||
// reason and notes, as BanForLimit and BanForAttack describe, and returns
|
||||
// the ban, and whether it made it. Unless keep is true, the ban is not
|
||||
// made, only returned: it is the ban that would have been made.
|
||||
func (l *Ledger) ban(
|
||||
netblock netip.Prefix,
|
||||
now, expires time.Time,
|
||||
now time.Time,
|
||||
cause, reason string,
|
||||
notes Notes,
|
||||
keep bool,
|
||||
@@ -743,13 +676,10 @@ func (l *Ledger) ban(
|
||||
Notes: notes,
|
||||
}
|
||||
|
||||
switch cause {
|
||||
case CauseAttack:
|
||||
if cause == CauseAttack {
|
||||
ban.Expires = l.attackExpiry(held, now)
|
||||
case CauseLimit:
|
||||
} else {
|
||||
ban.Expires = l.limitExpiry(held, now)
|
||||
default: // CauseCrowdSec
|
||||
ban.Expires = expires
|
||||
}
|
||||
|
||||
if !keep {
|
||||
@@ -784,16 +714,13 @@ func earlierBans(held []Ban) EarlierBans {
|
||||
fromHere.Attack++
|
||||
case CauseAdmin:
|
||||
fromHere.Admin++
|
||||
case CauseCrowdSec:
|
||||
fromHere.CrowdSec++
|
||||
}
|
||||
|
||||
notes := ban.Notes.EarlierBans
|
||||
count := EarlierBans{
|
||||
Limit: notes.Limit + fromHere.Limit,
|
||||
Attack: notes.Attack + fromHere.Attack,
|
||||
Admin: notes.Admin + fromHere.Admin,
|
||||
CrowdSec: notes.CrowdSec + fromHere.CrowdSec,
|
||||
Limit: notes.Limit + fromHere.Limit,
|
||||
Attack: notes.Attack + fromHere.Attack,
|
||||
Admin: notes.Admin + fromHere.Admin,
|
||||
}
|
||||
|
||||
// Of counts that name as many bans, the earlier ban's is taken: it
|
||||
@@ -808,7 +735,7 @@ func earlierBans(held []Ban) EarlierBans {
|
||||
|
||||
// total returns how many bans e counts, of all causes.
|
||||
func (e EarlierBans) total() int {
|
||||
return e.Limit + e.Attack + e.Admin + e.CrowdSec
|
||||
return e.Limit + e.Attack + e.Admin
|
||||
}
|
||||
|
||||
// markChanged has Changed receive a value, unless one is waiting already.
|
||||
@@ -913,9 +840,9 @@ func (l *Ledger) add(ban Ban) {
|
||||
// limitExpiry returns when a ban for a broken limit made at now ends, or
|
||||
// zero when it is permanent. held are the netblock's bans, none of them
|
||||
// active, of which the one that ended last, other than a ban for a clear
|
||||
// sign of attack or for CrowdSec's decision, or a lifted one, can make the
|
||||
// new ban longer. A ban an admin adds to bans.json can start after
|
||||
// another and end before it, so that one is looked for among them all.
|
||||
// sign of attack or a lifted one, can make the new ban longer. A ban an
|
||||
// admin adds to bans.json can start after another and end before it, so
|
||||
// that one is looked for among them all.
|
||||
func (l *Ledger) limitExpiry(held []Ban, now time.Time) time.Time {
|
||||
length := l.params.LimitBanDuration
|
||||
|
||||
|
||||
Reference in new issue
Block a user