Rule files, and bans for a clear sign of attack (closes #24)
check / check (push) Successful in 3m13s
check / check (push) Successful in 3m13s
Every *.rules file in SWWAF_RULES_DIR is read at start and on each change, and each request is checked against the rules after the rate limits: log notes a match, block refuses with 403, ban refuses and bans the netblock for SWWAF_ATTACK_BAN_DURATION, made permanent by its next request or clear sign of attack. path, query and uri are matched as the request line sent them. bans.json gains each ban's cause, and ban notes count earlier bans by cause. The image ships 00-default.rules. Judgement call: a header sent twice is matched with its values joined by ", ". Judgement call: SWWAF_MAX_BAN_DURATION does not cap a ban for an attack. Not in this unit: offences for rule matches, with the error burst. Model: opus-5-5
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||
"sneak.berlin/go/smallwebwaf/internal/proxy"
|
||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||
"sneak.berlin/go/smallwebwaf/internal/rules"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -67,6 +68,8 @@ const (
|
||||
maxBanDuration = "SWWAF_MAX_BAN_DURATION"
|
||||
maxBans = "SWWAF_MAX_BANS"
|
||||
banScopeV4Prefix = "SWWAF_BAN_SCOPE_V4_PREFIX"
|
||||
attackBanDuration = "SWWAF_ATTACK_BAN_DURATION"
|
||||
rulesDir = "SWWAF_RULES_DIR"
|
||||
)
|
||||
|
||||
// output collects what smallwebwaf writes on stdout.
|
||||
@@ -197,14 +200,15 @@ func startProxyWithGeoJS(
|
||||
}
|
||||
|
||||
// startProxyWithClock is startProxyWithGeoJS with requests counted and
|
||||
// bans made by the time now tells, and returns the server as well.
|
||||
// bans made by the time now tells, and returns the server as well. Unless
|
||||
// env sets SWWAF_RULES_DIR, it is an empty directory, of no rules.
|
||||
func startProxyWithClock(
|
||||
t *testing.T, appURL, geojsURL string, now func() time.Time,
|
||||
env map[string]string,
|
||||
) (string, *output, *proxy.Server) {
|
||||
t.Helper()
|
||||
|
||||
settings := map[string]string{"SWWAF_UPSTREAM_URL": appURL}
|
||||
settings := map[string]string{"SWWAF_UPSTREAM_URL": appURL, rulesDir: t.TempDir()}
|
||||
maps.Copy(settings, env)
|
||||
|
||||
cfg, err := config.FromEnvironment(func(name string) (string, bool) {
|
||||
@@ -217,12 +221,22 @@ func startProxyWithClock(
|
||||
}
|
||||
|
||||
out := &output{}
|
||||
processLog := requestlog.NewProcessLogger(out)
|
||||
|
||||
ruleFiles, err := rules.Load(rules.Params{
|
||||
Dir: cfg.RulesDir, Enabled: cfg.RulesEnabled, ProcessLog: processLog,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("rule files: %v", err)
|
||||
}
|
||||
|
||||
server := proxy.New(proxy.Params{
|
||||
Config: cfg,
|
||||
RequestLog: out,
|
||||
ProcessLog: requestlog.NewProcessLogger(out),
|
||||
ProcessLog: processLog,
|
||||
GeoJSURL: geojsURL,
|
||||
Now: now,
|
||||
Rules: ruleFiles,
|
||||
})
|
||||
|
||||
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||
|
||||
Reference in New Issue
Block a user