AbuseIPDB scores for clients that committed an offence, within a daily budget (closes #105)
check / check (push) Waiting to run
check / check (push) Waiting to run
With SWWAF_ABUSEIPDB_KEY set, a client whose history counts an offence (a broken limit, a ban rule's match or a block rule's refusal, counted by kind) is checked in the background, at most SWWAF_ABUSEIPDB_DAILY_BUDGET checks a day, the count kept in reputation.json. A client, an IPv4 address or an IPv6 /64, is checked by the address it sent from, and its score serves all its addresses. A score at or over SWWAF_ABUSEIPDB_MIN_SCORE is a hit for SWWAF_REPUTATION_ACTION, logged as abuseipdb and alerted with its score. A failure or the used-up budget gives no score and raises source_failure. The key goes only in the Key header. Judgement call: the budget's day is UTC; AbuseIPDB documents no reset time. Judgement call: each check sent spends budget; a minute's pause after a failure. Model: opus-5-5
This commit is contained in:
@@ -63,6 +63,9 @@ const (
|
||||
blocklistAction = "SWWAF_BLOCKLIST_ACTION"
|
||||
dnsblZones = "SWWAF_DNSBL_ZONES"
|
||||
dnsblResolver = "SWWAF_DNSBL_RESOLVER"
|
||||
abuseIPDBKey = "SWWAF_ABUSEIPDB_KEY"
|
||||
abuseIPDBMinScore = "SWWAF_ABUSEIPDB_MIN_SCORE"
|
||||
abuseIPDBDailyBudget = "SWWAF_ABUSEIPDB_DAILY_BUDGET"
|
||||
reputationAction = "SWWAF_REPUTATION_ACTION"
|
||||
reputationCacheTTL = "SWWAF_REPUTATION_CACHE_TTL"
|
||||
reputationTimeout = "SWWAF_REPUTATION_TIMEOUT"
|
||||
@@ -1526,6 +1529,76 @@ func TestDNSBLZoneKeyIsLoggedMaskedAndNeverShown(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbuseIPDBSettingsAsSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := fromEnvironment(t, environment{})
|
||||
if cfg.AbuseIPDBKey != "" || cfg.AbuseIPDBMinScore != 75 ||
|
||||
cfg.AbuseIPDBDailyBudget != 900 {
|
||||
t.Errorf("by default, the key %q, the minimum score %d and the daily budget %d, "+
|
||||
"want none, 75 and 900", cfg.AbuseIPDBKey, cfg.AbuseIPDBMinScore,
|
||||
cfg.AbuseIPDBDailyBudget)
|
||||
}
|
||||
|
||||
cfg = fromEnvironment(t, environment{
|
||||
abuseIPDBKey: token, abuseIPDBMinScore: "0", abuseIPDBDailyBudget: "1",
|
||||
})
|
||||
if cfg.AbuseIPDBKey != token || cfg.AbuseIPDBMinScore != 0 ||
|
||||
cfg.AbuseIPDBDailyBudget != 1 {
|
||||
t.Errorf("set, the key %q, the minimum score %d and the daily budget %d, "+
|
||||
"want %s, 0 and 1", cfg.AbuseIPDBKey, cfg.AbuseIPDBMinScore,
|
||||
cfg.AbuseIPDBDailyBudget, token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidAbuseIPDBSettingStopsTheStartSayingWhatIsWrong(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
notScore = " is not a percentage, a whole number from 0 to 100"
|
||||
notBudget = " is not a whole number above zero, such as 5000"
|
||||
)
|
||||
|
||||
for _, tc := range []struct{ name, value, want string }{
|
||||
{abuseIPDBMinScore, "101", `"101"` + notScore},
|
||||
{abuseIPDBMinScore, off, `"off"` + notScore},
|
||||
{abuseIPDBDailyBudget, "0", `"0"` + notBudget},
|
||||
{abuseIPDBDailyBudget, off, `"off"` + notBudget},
|
||||
// The key itself is never shown.
|
||||
{
|
||||
abuseIPDBKey, token + "\r",
|
||||
"holds a control character, such as the carriage return of a Windows line end",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name+"="+tc.value, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := config.FromEnvironment(environment{tc.name: tc.value}.lookupEnv)
|
||||
|
||||
want := tc.name + ": " + tc.want
|
||||
if err == nil || err.Error() != want {
|
||||
t.Errorf("error %v, want %s", err, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbuseIPDBKeyIsLoggedMasked(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := fromEnvironment(t, environment{abuseIPDBKey: token})
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
slog.New(slog.NewJSONHandler(&out, nil)).Info("starting", "settings", cfg)
|
||||
|
||||
logged := out.String()
|
||||
if strings.Contains(logged, token) ||
|
||||
!strings.Contains(logged, `"`+abuseIPDBKey+`":"********"`) {
|
||||
t.Errorf("the key is not logged masked: %s", logged)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSizesAndOff(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1956,6 +2029,9 @@ func TestLogsEachSettingWithItsValue(t *testing.T) {
|
||||
blocklistAction: actionDeny,
|
||||
dnsblZones: "",
|
||||
dnsblResolver: "",
|
||||
abuseIPDBKey: "",
|
||||
abuseIPDBMinScore: "75",
|
||||
abuseIPDBDailyBudget: "900",
|
||||
reputationAction: "limit:25",
|
||||
reputationCacheTTL: defaultReputationCacheTTL,
|
||||
reputationTimeout: "2s",
|
||||
|
||||
Reference in New Issue
Block a user