Rename the program to sww and its deciding daemon to discriminator (closes #218)
check / check (push) Failing after 2m47s
check / check (push) Failing after 2m47s
The program is now sww, built from cmd/sww, with two modes: sww rproxy and sww discriminator. servewaf becomes discriminator everywhere it names that daemon: the package (internal/discriminator), its types, functions, files, the discriminator_line log field, and its docs and tests. smallwebwaf is left only where sneak's directive allows it: the documents' product name, the git.eeqj.de/sneak/smallwebwaf URLs, and the Go module path sneak.berlin/go/smallwebwaf. Everywhere else a name containing it takes the sww form: the package (internal/sww), metric names (sww_*), the X-Sww-* headers, the /_sww/ admin endpoints, the OS user, and the state and rules directories. The SWWAF_ environment variables take the SWW_ form. No logic changes. make check passes. Model: deepseek-v4-pro
This commit is contained in:
1 parent
2e2f6f7175
commit
4a030557f3
106 files changed
+3302
-3275
No files matched your search
@@ -0,0 +1,130 @@
|
||||
package discriminator_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/smallwebwaf/internal/bans"
|
||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||
)
|
||||
|
||||
// trapPaths is the setting's name, and trapPathList what the tests set it
|
||||
// to.
|
||||
const (
|
||||
trapPaths = "SWW_TRAP_PATHS"
|
||||
trapPathList = "/wp-login.php,/xmlrpc.php"
|
||||
)
|
||||
|
||||
func TestTrapPathBansAsABanRuleDoes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const allowed = "192.0.2.60" // in SWW_ALLOW_NETS
|
||||
|
||||
// A block rule for the same path: the trap path comes first.
|
||||
s, clk, server := startWithClock(t, "", map[string]string{
|
||||
trapPaths: trapPathList,
|
||||
rulesDir: writeRules(t, `wp path block ^/wp-login\.php$`),
|
||||
allowNets: allowed,
|
||||
banResponse: "429",
|
||||
})
|
||||
start := clk.Now()
|
||||
|
||||
// Only the path itself, the whole of it in the same case, is a trap
|
||||
// path.
|
||||
for _, path := range []string{"/wp-login.php/", "/WP-LOGIN.PHP", "/blog/xmlrpc.php"} {
|
||||
s.request(otherClient, path, http.StatusOK, requestlog.ActionForward)
|
||||
}
|
||||
|
||||
// A client in SWW_ALLOW_NETS is not checked.
|
||||
s.request(allowed, "/xmlrpc.php", http.StatusOK, requestlog.ActionForward)
|
||||
|
||||
// The query is not part of the path.
|
||||
line := s.request(client, "/wp-login.php?redirect_to=x", http.StatusTooManyRequests,
|
||||
requestlog.ActionBanned)
|
||||
wantRuleIDs(t, line)
|
||||
|
||||
if line.BanExpires != requestlog.FormatTime(start.Add(7*24*time.Hour)) {
|
||||
t.Errorf("log line has ban_expires %q, want seven days on", line.BanExpires)
|
||||
}
|
||||
|
||||
netblock := netip.MustParsePrefix(client + "/32")
|
||||
want := bans.Ban{
|
||||
Netblock: netblock,
|
||||
Start: start,
|
||||
Expires: start.Add(7 * 24 * time.Hour),
|
||||
Cause: bans.CauseAttack,
|
||||
Reason: "asked for the trap path /wp-login.php",
|
||||
Notes: bans.Notes{
|
||||
TrapPath: "/wp-login.php",
|
||||
Request: bans.Request{
|
||||
Time: start,
|
||||
Method: http.MethodGet,
|
||||
Host: appHost,
|
||||
Path: "/wp-login.php?redirect_to=x",
|
||||
Status: http.StatusTooManyRequests,
|
||||
UserAgent: userAgent,
|
||||
},
|
||||
Requests: 1,
|
||||
},
|
||||
}
|
||||
|
||||
got := server.Ledger.Bans(netblock)
|
||||
if len(got) != 1 || !reflect.DeepEqual(got[0], want) {
|
||||
t.Fatalf("bans\n%+v\nwant\n%+v", got, want)
|
||||
}
|
||||
|
||||
// The next request is refused under the ban, and makes it permanent.
|
||||
line = s.get(client, http.StatusTooManyRequests, requestlog.ActionBanned)
|
||||
if line.BanExpires != permanent {
|
||||
t.Errorf("log line has ban_expires %q, want permanent", line.BanExpires)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrapPathMatchesThePathDecodedOnce(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, _, server := startWithClock(t, "", map[string]string{trapPaths: trapPathList})
|
||||
|
||||
// %2577 decodes once to %77, not to w.
|
||||
s.request(otherClient, "/%2577p-login.php", http.StatusOK, requestlog.ActionForward)
|
||||
s.request(client, "/%77p-login.php", http.StatusForbidden, requestlog.ActionBanned)
|
||||
|
||||
// The ban's notes give the path as the client sent it.
|
||||
got := server.Ledger.Bans(netip.MustParsePrefix(client + "/32"))
|
||||
if len(got) != 1 || got[0].Notes.TrapPath != "/%77p-login.php" {
|
||||
t.Fatalf("bans %+v, want one whose notes give the trap path /%%77p-login.php", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrapPathsNeedNoRuleFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, _, _ := startWithClock(t, "", map[string]string{
|
||||
trapPaths: trapPathList,
|
||||
"SWW_RULES_ENABLED": "false",
|
||||
})
|
||||
|
||||
s.request(client, "/xmlrpc.php", http.StatusForbidden, requestlog.ActionBanned)
|
||||
}
|
||||
|
||||
func TestObserveModeLogsWhatATrapPathWouldDo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s, _, server := startWithClock(t, "", map[string]string{
|
||||
trapPaths: trapPathList,
|
||||
mode: observe,
|
||||
})
|
||||
|
||||
line := s.request(client, "/xmlrpc.php", http.StatusOK, requestlog.ActionForward)
|
||||
wantWouldAction(t, line, requestlog.ActionBanned)
|
||||
|
||||
// No ban was made.
|
||||
s.get(client, http.StatusOK, requestlog.ActionForward)
|
||||
|
||||
if got := server.Ledger.Snapshot(); len(got) != 0 {
|
||||
t.Errorf("bans %+v, want none", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user