Blocklists and an AS percentage file fetched by URL (closes #29)
check / check (push) Waiting to run

SWWAF_BLOCKLIST_URLS names lists of addresses and netblocks, fetched every
SWWAF_BLOCKLIST_REFRESH (24h, never under 1h); an IPv4-mapped line stands
for its IPv4 address or netblock. reputation.json keeps each list's last
try, failed or not, even one cut off by a stop, which a restart waits on
as a running instance does, and its last good copy, whole, used while a
fetch fails. SWWAF_BLOCKLIST_ACTION denies, limits or only logs a listed
client; the log line names the lists, each raises reputation_hit, and a
failed fetch raises source_failure. SWWAF_ASN_LIMIT_PERCENT_URL is fetched
the same way and counts as SWWAF_ASN_LIMIT_PERCENT does, the lower winning.

Judgement call: a failed fetch is retried after the refresh, not sooner.
Not done: ban notes do not name the lists yet.

Model: opus-5-5
This commit was merged in pull request #108.
This commit is contained in:
2026-10-07 19:09:41 +02:00
parent 82e20e0cb5
commit 2b8c98ba1f
19 changed files with 2588 additions and 322 deletions
+17 -5
View File
@@ -18,6 +18,7 @@ import (
"sneak.berlin/go/smallwebwaf/internal/lookup"
"sneak.berlin/go/smallwebwaf/internal/metrics"
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
"sneak.berlin/go/smallwebwaf/internal/reputation"
"sneak.berlin/go/smallwebwaf/internal/requestlog"
"sneak.berlin/go/smallwebwaf/internal/rules"
)
@@ -69,14 +70,16 @@ type Params struct {
// against.
Rules *rules.Files
// Alerts receive the alert for each ban the proxy makes or makes
// permanent, for each count over an anomaly threshold, and for GeoJS
// failing.
// permanent, for each count over an anomaly threshold, for each request
// whose client a blocklist lists, and for GeoJS failing or a fetch of a
// list failing.
Alerts *alerts.Queue
}
// Server is the server smallwebwaf runs, with the parts of the proxy
// whose state the state files keep, the lookup database, nil unless
// SWWAF_LOOKUP_SOURCE is file, and the metrics.
// SWWAF_LOOKUP_SOURCE is file, the lists fetched from URLs, which its Run
// fetches, and the metrics.
type Server struct {
*http.Server
@@ -85,6 +88,7 @@ type Server struct {
GeoJS *lookup.GeoJS
Anomalies *anomaly.Counters
LookupFile *lookup.File
Lists *reputation.Lists
Metrics *metrics.Metrics
}
@@ -132,8 +136,13 @@ func New(params Params) *Server {
Alerts: params.Alerts,
}),
lookupFile: params.LookupFile,
rules: params.Rules,
alerts: params.Alerts,
lists: reputation.New(reputation.Params{
BlocklistURLs: params.Config.BlocklistURLs, Refresh: params.Config.BlocklistRefresh,
ASNLimitPercentURL: params.Config.ASNLimitPercentURL, Now: params.Now,
ProcessLog: params.ProcessLog, Alerts: params.Alerts,
}),
rules: params.Rules,
alerts: params.Alerts,
}
h.geojs = lookup.New(lookup.Params{
URL: params.GeoJSURL,
@@ -151,6 +160,7 @@ func New(params Params) *Server {
})
m.AddBansAndClients(h.ledger, h.limiter, params.Now)
m.AddRules(params.Rules)
m.AddReputation(h.lists)
return &Server{
Server: &http.Server{
@@ -170,6 +180,7 @@ func New(params Params) *Server {
GeoJS: h.geojs,
Anomalies: h.anomalies,
LookupFile: h.lookupFile,
Lists: h.lists,
Metrics: m,
}
}
@@ -189,6 +200,7 @@ type handler struct {
geojs *lookup.GeoJS
anomalies *anomaly.Counters
lookupFile *lookup.File
lists *reputation.Lists
rules *rules.Files
alerts *alerts.Queue
}