Format Go with gofmt, then golines -m 77 (closes #215)
check / check (push) Waiting to run

script/fmt now runs golines -m 77 after gofmt, repeating golines until
it changes nothing, since one pass can leave a line the next splits.
script/fmt-check fails when either would change a file.
script/bootstrap installs golines v0.13.0 with go install, pinned to
its commit, and checks the installed version with go version -m.

The tree is reformatted. The longer lines this makes are fixed by
hand: nolint directives moved onto their own line above the statement,
one string split, and 20 functions over funlen's 80 lines shortened or
split, RunWAF and limitPercentages among them.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-11 15:14:58 +00:00
1 parent 63e58e326b
commit 7f8a59de04
118 files changed
+9447 -2670

No files matched your search

+21 -5
View File
@@ -31,7 +31,9 @@ var errHealthEndpoint = errors.New("the health endpoint answered")
// args are the arguments after `healthcheck`; it takes none, and given
// one it names it on stderr and returns 1 without checking anything.
func HealthCheck(
ctx context.Context, args []string, lookupEnv func(string) (string, bool),
ctx context.Context,
args []string,
lookupEnv func(string) (string, bool),
stderr io.Writer,
) int {
if len(args) > 0 {
@@ -85,7 +87,9 @@ func checkWAF(
func checkRProxyAndApp(
ctx context.Context, lookupEnv func(string) (string, bool),
) error {
listenAddr, upstreamURL, err := config.RProxyListenAddrAndUpstreamURL(lookupEnv)
listenAddr, upstreamURL, err := config.RProxyListenAddrAndUpstreamURL(
lookupEnv,
)
if err != nil {
return fmt.Errorf("invalid setting: %w", err)
}
@@ -95,7 +99,11 @@ func checkRProxyAndApp(
return fmt.Errorf("ask rproxy: %w", err)
}
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", appAddress(upstreamURL))
conn, err := (&net.Dialer{}).DialContext(
ctx,
"tcp",
appAddress(upstreamURL),
)
if err != nil {
return fmt.Errorf("connect to the app: %w", err)
}
@@ -109,9 +117,17 @@ func checkRProxyAndApp(
// listenAddr, which the settings have checked has one.
func askHealthEndpoint(ctx context.Context, listenAddr string) error {
_, port, _ := net.SplitHostPort(listenAddr)
health := "http://" + net.JoinHostPort("127.0.0.1", port) + waf.HealthPath
health := "http://" + net.JoinHostPort(
"127.0.0.1",
port,
) + waf.HealthPath
req, err := http.NewRequestWithContext(ctx, http.MethodGet, health, http.NoBody)
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
health,
http.NoBody,
)
if err != nil {
return fmt.Errorf("make the request: %w", err)
}
+31 -9
View File
@@ -94,7 +94,10 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
site, _ := startRProxy(t, "http://127.0.0.1:9", app.URL)
_, port, _ := net.SplitHostPort(strings.TrimPrefix(site, "http://"))
settings := func(more ...string) map[string]string {
env := map[string]string{rproxyListenAddr: ":" + port, rproxyUpstreamURL: app.URL}
env := map[string]string{
rproxyListenAddr: ":" + port,
rproxyUpstreamURL: app.URL,
}
for i := 0; i < len(more); i += 2 {
env[more[i]] = more[i+1]
}
@@ -107,8 +110,12 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
wantHealthCheck(t, settings(commandSetting, "rproxy"), 0, "")
// SWW_COMMAND cannot be given as a file.
wantHealthCheck(t, settings(commandSetting+"_FILE", writeFile(t, "waf\n")),
0, "")
wantHealthCheck(
t,
settings(commandSetting+"_FILE", writeFile(t, "waf\n")),
0,
"",
)
// Another setting does not fail it, even an invalid one, and neither does
// SWRP_WAF_BACKEND being unset.
@@ -121,7 +128,12 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
// Listening on port 0 takes any free port, so nothing listens on port 0
// itself.
wantHealthCheck(t, settings(rproxyListenAddr, ":0"), 1, "unhealthy: ask rproxy: ")
wantHealthCheck(
t,
settings(rproxyListenAddr, ":0"),
1,
"unhealthy: ask rproxy: ",
)
wantHealthCheck(t, settings(rproxyListenAddr, "8080"), 1,
"unhealthy: invalid setting: SWRP_LISTEN_ADDR: ")
@@ -161,16 +173,26 @@ func TestHealthCheckRefusesAnArgument(t *testing.T) {
// wantHealthCheck runs the health check with the settings in env, and
// checks its exit status and the start of what it writes to stderr,
// which is nothing when message is empty.
func wantHealthCheck(t *testing.T, env map[string]string, status int, message string) {
func wantHealthCheck(
t *testing.T,
env map[string]string,
status int,
message string,
) {
t.Helper()
var stderr bytes.Buffer
got := sww.HealthCheck(t.Context(), nil, func(name string) (string, bool) {
value, ok := env[name]
got := sww.HealthCheck(
t.Context(),
nil,
func(name string) (string, bool) {
value, ok := env[name]
return value, ok
}, &stderr)
return value, ok
},
&stderr,
)
wrote := stderr.String()
if got != status || !strings.HasPrefix(wrote, message) ||
+10 -2
View File
@@ -21,7 +21,11 @@ import (
// more severe: rproxy has no setting for either.
func RunRProxy(ctx context.Context, params Params) int {
hostname, _ := os.Hostname() // "" when the host has no name to give
processLog := requestlog.NewProcessLogger(params.Stdout, hostname, slog.LevelInfo)
processLog := requestlog.NewProcessLogger(
params.Stdout,
hostname,
slog.LevelInfo,
)
cfg, err := config.RProxyFromEnvironment(params.LookupEnv)
if err != nil {
@@ -38,7 +42,11 @@ func RunRProxy(ctx context.Context, params Params) int {
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
if err != nil {
processLog.Error("cannot listen on SWRP_LISTEN_ADDR", "error", err.Error())
processLog.Error(
"cannot listen on SWRP_LISTEN_ADDR",
"error",
err.Error(),
)
return 1
}
+91 -22
View File
@@ -79,7 +79,11 @@ func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
select {
case status := <-exited:
if status != 0 {
t.Errorf("rproxy's exit status %d, want 0; output:\n%s", status, out.text())
t.Errorf(
"rproxy's exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Error("rproxy still running after being told to stop")
@@ -94,7 +98,10 @@ func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
// startWAF runs waf with the settings in env, and returns
// its address and URL, what it writes, and what stops it, which checks that it
// stopped in order.
func startWAF(t *testing.T, env map[string]string) (string, *output, func()) {
func startWAF(
t *testing.T,
env map[string]string,
) (string, *output, func()) {
t.Helper()
ctx, stop := context.WithCancel(t.Context())
@@ -113,7 +120,11 @@ func startWAF(t *testing.T, env map[string]string) (string, *output, func()) {
select {
case status := <-exited:
if status != 0 {
t.Fatalf("waf's exit status %d, want 0; output:\n%s", status, out.text())
t.Fatalf(
"waf's exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Fatal("waf still running after being told to stop")
@@ -341,7 +352,12 @@ func pipe(dst, src net.Conn) {
// answersNormally reports whether a request to url from the client at from, as
// X-Forwarded-For names it, gets the app's answer.
func answersNormally(ctx context.Context, url, from string) bool {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
url,
http.NoBody,
)
if err != nil {
return false
}
@@ -359,7 +375,8 @@ func answersNormally(ctx context.Context, url, from string) bool {
body, err := io.ReadAll(res.Body)
_ = res.Body.Close()
return err == nil && res.StatusCode == http.StatusOK && string(body) == greeting
return err == nil && res.StatusCode == http.StatusOK &&
string(body) == greeting
}
// sendRaw sends request, as it is, to rproxy at site, and returns the status of
@@ -412,9 +429,16 @@ func TestAChunkedBodyStaysABodyAtWAF(t *testing.T) {
waf.ClientHeader + ": " + otherClient + "\r\n" +
"X-Request-ID: " + smuggledID + "\r\n\r\n"
status := sendRaw(t, site, "POST /upload HTTP/1.1\r\nHost: site.example\r\n"+
"X-Forwarded-For: "+placed+"\r\nTransfer-Encoding: chunked\r\n\r\n"+
strconv.FormatInt(int64(len(smuggled)), 16)+"\r\n"+smuggled+"\r\n0\r\n\r\n")
status := sendRaw(
t,
site,
"POST /upload HTTP/1.1\r\nHost: site.example\r\n"+
"X-Forwarded-For: "+placed+"\r\nTransfer-Encoding: chunked\r\n\r\n"+
strconv.FormatInt(
int64(len(smuggled)),
16,
)+"\r\n"+smuggled+"\r\n0\r\n\r\n",
)
if status != http.StatusOK {
t.Errorf("status %d, want the app's 200", status)
}
@@ -443,8 +467,12 @@ func TestATargetThatCannotGoToWAFIsRefusedForABannedClient(t *testing.T) {
wantStatus(t, site+bannedProbe, placed, http.StatusForbidden)
status := sendRaw(t, site, "GET x:admin HTTP/1.1\r\nHost: site.example\r\n"+
"X-Forwarded-For: "+placed+"\r\n\r\n")
status := sendRaw(
t,
site,
"GET x:admin HTTP/1.1\r\nHost: site.example\r\n"+
"X-Forwarded-For: "+placed+"\r\n\r\n",
)
if status != http.StatusBadRequest {
t.Errorf("status %d, want 400", status)
}
@@ -470,7 +498,8 @@ func TestAClientWithAZoneIsTheClientWithout(t *testing.T) {
wantStatus(t, site+"/", "2001:db8::1", http.StatusForbidden)
line := out.line(t, "path", bannedProbe)
if line["client_ip"] != "2001:db8::1" || line["default_disposition"] != nil {
if line["client_ip"] != "2001:db8::1" ||
line["default_disposition"] != nil {
t.Errorf("rproxy's line is %v", line)
}
}
@@ -495,7 +524,10 @@ func TestAStatusWAFDoesNotTakeIsReportedAs599(t *testing.T) {
line := out.line(t, "path", "/")
if line["status"] != float64(status) {
t.Errorf("rproxy's line is %v, want the status the visitor got", line)
t.Errorf(
"rproxy's line is %v, want the status the visitor got",
line,
)
}
id, _ := line["request_id"].(string)
@@ -519,7 +551,8 @@ func startRProxyProcess(t *testing.T, env ...string) (string, func()) {
}
out := &output{}
cmd := exec.CommandContext(t.Context(), binary) //nolint:gosec // this test binary
//nolint:gosec // this test binary
cmd := exec.CommandContext(t.Context(), binary)
cmd.Stdout = out
cmd.Env = append(os.Environ(),
@@ -540,7 +573,11 @@ func startRProxyProcess(t *testing.T, env ...string) (string, func()) {
select {
case status := <-exited:
if status != 0 {
t.Errorf("exit status %d, want 0; output:\n%s", status, out.text())
t.Errorf(
"exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Error("still running after being told to stop")
@@ -587,7 +624,11 @@ func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
"Connection": "Connection: anything\r\n",
"a WebSocket upgrade": "Connection: Upgrade\r\nUpgrade: websocket\r\n",
} {
status := sendRaw(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n"+headers+"\r\n")
status := sendRaw(
t,
addr,
"GET / HTTP/1.1\r\nHost: site.example\r\n"+headers+"\r\n",
)
if status != http.StatusForbidden {
t.Errorf("with %s: status %d, want waf's 403", name, status)
}
@@ -595,7 +636,11 @@ func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
select {
case protocol := <-protocols:
if protocol != "HTTP/1.1" {
t.Errorf("with %s: asked over %s, want HTTP/1.1", name, protocol)
t.Errorf(
"with %s: asked over %s, want HTTP/1.1",
name,
protocol,
)
}
case <-time.After(waitLimit):
t.Errorf("with %s: waf was not asked", name)
@@ -616,7 +661,11 @@ func TestRProxyProcessLinesNameTheHostAndComeAtInfo(t *testing.T) {
line := out.line(t, "msg", "starting")
if line["instance"] != hostname || line["level"] != "INFO" {
t.Errorf("rproxy's line is %v, want instance %s at INFO", line, hostname)
t.Errorf(
"rproxy's line is %v, want instance %s at INFO",
line,
hostname,
)
}
}
@@ -681,7 +730,11 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
select {
case status := <-exited:
if status != 0 {
t.Errorf("rproxy's exit status %d, want 0; output:\n%s", status, out.text())
t.Errorf(
"rproxy's exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Error("rproxy still running, the report having been answered")
@@ -755,7 +808,11 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) {
}()
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
conn := sendAndKeepOpen(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
conn := sendAndKeepOpen(
t,
addr,
"GET / HTTP/1.1\r\nHost: site.example\r\n\r\n",
)
select {
case <-asked:
@@ -779,7 +836,11 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) {
select {
case status := <-exited:
if status != 0 {
t.Errorf("rproxy's exit status %d, want 0; output:\n%s", status, out.text())
t.Errorf(
"rproxy's exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Error("rproxy still running after the cut-off")
@@ -814,7 +875,11 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
message, _ := line["error"].(string)
if !strings.HasPrefix(message, tc.setting+": ") {
t.Errorf("start refused with %v, want it to name %s", line, tc.setting)
t.Errorf(
"start refused with %v, want it to name %s",
line,
tc.setting,
)
}
})
}
@@ -823,7 +888,11 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
func TestRProxyAddressInUseStopsTheStart(t *testing.T) {
t.Parallel()
taken, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
taken, err := (&net.ListenConfig{}).Listen(
t.Context(),
"tcp",
localhost+":0",
)
if err != nil {
t.Fatalf("listen: %v", err)
}
+80 -25
View File
@@ -79,7 +79,11 @@ func Main(version string) int {
syscall.SIGTERM, os.Interrupt)
defer stop()
params := Params{Version: version, LookupEnv: os.LookupEnv, Stdout: os.Stdout}
params := Params{
Version: version,
LookupEnv: os.LookupEnv,
Stdout: os.Stdout,
}
switch args[0] {
case "rproxy":
@@ -111,9 +115,7 @@ func RunWAF(ctx context.Context, params Params) int {
cfg, err := config.FromEnvironment(params.LookupEnv)
if err != nil {
processLog.Error("invalid setting", "error", err.Error())
return 1
return cannotStart(processLog, "invalid setting", err)
}
// While SWW_LOG_REMOTE_URL is set, every line on stdout from here on
@@ -127,13 +129,38 @@ func RunWAF(ctx context.Context, params Params) int {
stdout = io.MultiWriter(params.Stdout, remote)
}
processLog = requestlog.NewProcessLogger(stdout, cfg.InstanceName, cfg.LogLevel)
processLog = requestlog.NewProcessLogger(
stdout,
cfg.InstanceName,
cfg.LogLevel,
)
if remote != nil {
stopSending := startSending(ctx, remote, processLog)
defer stopSending()
}
return runWAFWithSettings(
ctx,
params.Version,
cfg,
stdout,
remote,
processLog,
)
}
// runWAFWithSettings is the rest of RunWAF once the settings are read and
// the process log goes where they say. remote is nil unless
// SWW_LOG_REMOTE_URL is set.
func runWAFWithSettings(
ctx context.Context,
version string,
cfg *config.Config,
stdout io.Writer,
remote *remotelog.Sender,
processLog *slog.Logger,
) int {
// The state files and the alerts give times in UTC.
now := func() time.Time { return time.Now().UTC() }
@@ -146,23 +173,25 @@ func RunWAF(ctx context.Context, params Params) int {
Alerts: alertQueue,
})
if err != nil {
processLog.Error("cannot use the rule files", "error", err.Error())
return 1
return cannotStart(processLog, "cannot use the rule files", err)
}
crs, err := waf.NewCoreRuleSet(cfg)
if err != nil {
processLog.Error("cannot use the Core Rule Set", "error", err.Error())
return 1
return cannotStart(processLog, "cannot use the Core Rule Set", err)
}
server, err := newServer(cfg, stdout, processLog, now, ruleFiles, alertQueue, crs)
server, err := newServer(
cfg,
stdout,
processLog,
now,
ruleFiles,
alertQueue,
crs,
)
if err != nil {
processLog.Error("cannot use the lookup database", "error", err.Error())
return 1
return cannotStart(processLog, "cannot use the lookup database", err)
}
if remote != nil {
@@ -171,24 +200,43 @@ func RunWAF(ctx context.Context, params Params) int {
files, err := loadStateFiles(cfg, server, alertQueue, now, processLog)
if err != nil {
processLog.Error("cannot use the state and cache files", "error", err.Error())
return 1
return cannotStart(
processLog,
"cannot use the state and cache files",
err,
)
}
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
if err != nil {
processLog.Error("cannot listen on SWW_LISTEN_ADDR", "error", err.Error())
return 1
return cannotStart(
processLog,
"cannot listen on SWW_LISTEN_ADDR",
err,
)
}
processLog.Info("starting",
"version", params.Version,
"version", version,
"address", listener.Addr().String(),
"settings", cfg)
return serve(ctx, server, listener, files, ruleFiles, alertQueue, processLog)
return serve(
ctx,
server,
listener,
files,
ruleFiles,
alertQueue,
processLog,
)
}
// cannotStart logs why waf cannot start, and returns its exit status.
func cannotStart(processLog *slog.Logger, message string, err error) int {
processLog.Error(message, "error", err.Error())
return 1
}
// newServer returns the server waf runs, with the metrics of the alerts,
@@ -230,7 +278,10 @@ func newServer(
server.Metrics.AddAlerts(alertQueue)
if lookupFile != nil {
server.Metrics.AddLookupFile(lookupFile.LastRead, lookupFile.ReadFailures)
server.Metrics.AddLookupFile(
lookupFile.LastRead,
lookupFile.ReadFailures,
)
}
return server, nil
@@ -395,7 +446,11 @@ func serve(
err = files.WriteAll()
if err != nil {
processLog.Error("writing the state and cache files failed", "error", err.Error())
processLog.Error(
"writing the state and cache files failed",
"error",
err.Error(),
)
return 1
}
+8 -2
View File
@@ -22,7 +22,10 @@ func TestStopWaitsForTheSenderToFinish(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
took := stopHeldSender(t, time.Second)
if took != time.Second {
t.Errorf("the stop took %s, want the second the sender took", took)
t.Errorf(
"the stop took %s, want the second the sender took",
took,
)
}
})
}
@@ -59,7 +62,10 @@ func stopHeldSender(t *testing.T, release time.Duration) time.Duration {
sender := remotelog.New(remotelog.Params{
// No port is 65536, so each attempt to connect fails at once,
// before it reaches the network.
URL: &url.URL{Scheme: remotelog.SchemeTCP, Host: "127.0.0.1:65536"},
URL: &url.URL{
Scheme: remotelog.SchemeTCP,
Host: "127.0.0.1:65536",
},
Buffer: 1,
})
+194 -63
View File
@@ -111,8 +111,12 @@ func (o *output) lineBeforeExit(
select {
case status := <-exited:
t.Fatalf("exit status %d while waiting for a line with msg %q; output:\n%s",
status, msg, o.text())
t.Fatalf(
"exit status %d while waiting for a line with msg %q; output:\n%s",
status,
msg,
o.text(),
)
case <-time.After(pollInterval):
}
}
@@ -227,7 +231,11 @@ func TestShortTokenStopsTheStartUnshown(t *testing.T) {
func TestAddressInUseStopsTheStart(t *testing.T) {
t.Parallel()
taken, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
taken, err := (&net.ListenConfig{}).Listen(
t.Context(),
"tcp",
localhost+":0",
)
if err != nil {
t.Fatalf("listen: %v", err)
}
@@ -325,7 +333,11 @@ func TestLogLevelHoldsBackTheLessSevereProcessLines(t *testing.T) {
select {
case status := <-exited:
if status != 0 {
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
t.Fatalf(
"exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Fatal("still running after being told to stop")
@@ -479,7 +491,14 @@ func TestBanAddedAndLiftedByEditingBansJSON(t *testing.T) {
runUntilStopped(t, env, func(url string) {
path := filepath.Join(dir, "bans.json")
saveUntilAnswered(t, path, oneBan, url, "203.0.113.9", http.StatusForbidden)
saveUntilAnswered(
t,
path,
oneBan,
url,
"203.0.113.9",
http.StatusForbidden,
)
wantStatus(t, url, "198.51.100.7", http.StatusOK)
saveUntilAnswered(t, path, noBan, url, "203.0.113.9", http.StatusOK)
})
@@ -568,12 +587,20 @@ func TestRuleFileThatDoesNotParseStopsTheStart(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "00-default.rules")
err := os.WriteFile(path, []byte("# probes\nenv-file path bann ^/\\.env$\n"), 0o600)
err := os.WriteFile(
path,
[]byte("# probes\nenv-file path bann ^/\\.env$\n"),
0o600,
)
if err != nil {
t.Fatalf("write the rule file: %v", err)
}
wantRulesRefused(t, dir, path+`, line 2: the action "bann" is not log, block or ban`)
wantRulesRefused(
t,
dir,
path+`, line 2: the action "bann" is not log, block or ban`,
)
}
func TestRulesDirThatDoesNotExistStopsTheStart(t *testing.T) {
@@ -585,7 +612,9 @@ func TestRulesDirThatDoesNotExistStopsTheStart(t *testing.T) {
": no such file or directory")
}
func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(t *testing.T) {
func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(
t *testing.T,
) {
t.Parallel()
path := filepath.Join(t.TempDir(), "crs-exclusions.conf")
@@ -686,23 +715,38 @@ func TestLookupDatabaseReplacedWhileRunningTakesEffect(t *testing.T) {
}
// One that cannot be read leaves the file read before in use.
err := os.WriteFile(replacement, []byte("not a lookup database\n"), 0o600)
err := os.WriteFile(
replacement,
[]byte("not a lookup database\n"),
0o600,
)
if err != nil {
t.Fatalf("write %s: %v", replacement, err)
}
rename(t, replacement, path)
metrics := metricsWith(t, url+"_sww/metrics", token,
`sww_lookup_database_read_failures_total{instance="fsn1app1/blog"} 1`)
lastRead = seriesValue(t, metrics,
`sww_lookup_database_last_read_timestamp_seconds{instance="fsn1app1/blog"}`)
metrics := metricsWith(
t,
url+"_sww/metrics",
token,
`sww_lookup_database_read_failures_total{instance="fsn1app1/blog"} 1`,
)
lastRead = seriesValue(
t,
metrics,
`sww_lookup_database_last_read_timestamp_seconds{instance="fsn1app1/blog"}`,
)
wantStatus(t, url, placed, http.StatusOK)
})
if lastRead < float64(began.Unix()) || lastRead > float64(time.Now().Unix()) {
t.Errorf("the lookup database was last read at %v, want a time in the test", lastRead)
if lastRead < float64(began.Unix()) ||
lastRead > float64(time.Now().Unix()) {
t.Errorf(
"the lookup database was last read at %v, want a time in the test",
lastRead,
)
}
}
@@ -776,8 +820,8 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
})
wantDeniedByList(t, out.line(t, "action", "client_denied"), dropURL)
if fetches := torFetches.Load(); fetches != 1 {
t.Errorf("tor.txt fetched %d times, want once, before the restart", fetches)
if n := torFetches.Load(); n != 1 {
t.Errorf("tor.txt fetched %d times before the restart, want 1", n)
}
// After another restart, with the server failing, the copy of drop.txt
@@ -792,10 +836,10 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
path := filepath.Join(cache, "lists.json")
kept, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
//nolint:gosec // a file in the test's directory
kept, err := os.ReadFile(path)
if err != nil || !strings.Contains(string(kept), `"`+copyright+`"`) {
t.Errorf("lists.json holds\n%s\nwant the copy with %q (%v)", kept, copyright,
err)
t.Errorf("lists.json holds\n%s\nwant %q (%v)", kept, copyright, err)
}
}
@@ -815,9 +859,12 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
case down.Load():
w.WriteHeader(http.StatusServiceUnavailable)
default:
_, _ = io.WriteString(w, `[{"duration": "4h0m0s", "origin": "crowdsec", `+
`"scenario": "crowdsecurity/http-probing", "scope": "Range", `+
`"type": "ban", "value": "203.0.113.0/24"}]`)
_, _ = io.WriteString(
w,
`[{"duration": "4h0m0s", "origin": "crowdsec", `+
`"scenario": "crowdsecurity/http-probing", "scope": "Range", `+
`"type": "ban", "value": "203.0.113.0/24"}]`,
)
}
}))
t.Cleanup(engine.Close)
@@ -842,7 +889,11 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
ban := onlyBan(t, dir)
if ban["netblock"] != placed+"/32" || ban["cause"] != "crowdsec" ||
ban["reason"] != "CrowdSec's decision for crowdsecurity/http-probing" {
t.Errorf("bans.json holds %v, want the ban for crowdsec on %s", ban, placed)
t.Errorf(
"bans.json holds %v, want the ban for crowdsec on %s",
ban,
placed,
)
}
// Restarted with the engine down, the copy kept in lists.json bans
@@ -871,7 +922,10 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
}
if all := strings.Join(shown, "\n"); strings.Contains(all, bouncerKey) {
t.Errorf("the key is shown in the output or the state and cache files:\n%s", all)
t.Errorf(
"the key is shown in the output or the state and cache files:\n%s",
all,
)
}
}
@@ -883,7 +937,11 @@ func wantDeniedByList(t *testing.T, line map[string]any, listURL string) {
reputation, _ := line["reputation"].([]any)
if line["action"] != "client_denied" || len(reputation) != 1 ||
reputation[0] != listURL {
t.Errorf("request log line %v, want one denied for %s", line, listURL)
t.Errorf(
"request log line %v, want one denied for %s",
line,
listURL,
)
}
}
@@ -918,7 +976,11 @@ func TestLookupDatabaseThatCannotBeReadStopsTheStart(t *testing.T) {
func TestEveryLineIsAlsoSentToTheRemoteLogEndpoint(t *testing.T) {
t.Parallel()
endpoint, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
endpoint, err := (&net.ListenConfig{}).Listen(
t.Context(),
"tcp",
localhost+":0",
)
if err != nil {
t.Fatalf("listen: %v", err)
}
@@ -973,7 +1035,11 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
// The endpoint takes connections and never answers, so the TLS
// handshake of each waits on it, and no line is ever sent.
endpoint, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
endpoint, err := (&net.ListenConfig{}).Listen(
t.Context(),
"tcp",
localhost+":0",
)
if err != nil {
t.Fatalf("listen: %v", err)
}
@@ -1009,7 +1075,8 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
const dropped = "\nsww_remote_log_lines_dropped_total" +
`{instance="fsn1app1/blog"} `
if strings.Contains(metrics, dropped+"0\n") || !strings.Contains(metrics, dropped) {
if strings.Contains(metrics, dropped+"0\n") ||
!strings.Contains(metrics, dropped) {
t.Errorf("no line dropped in the metrics:\n%s", metrics)
}
@@ -1035,7 +1102,11 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
// too.
line := out.line(t, "msg", "starting")
if line["instance"] != instance {
t.Errorf("start logged with instance %v, want %s", line["instance"], instance)
t.Errorf(
"start logged with instance %v, want %s",
line["instance"],
instance,
)
}
}
@@ -1066,10 +1137,14 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
wantRefused(t, url+".env")
post := webhook.waitFor(t, "ban", true)
if post.alert["client"] != localhost || post.alert["netblock"] != localhost+"/32" ||
if post.alert["client"] != localhost ||
post.alert["netblock"] != localhost+"/32" ||
post.authorization != "Bearer "+adminSecret {
t.Errorf("the webhook was sent %v, with Authorization %q", post.alert,
post.authorization)
t.Errorf(
"the webhook was sent %v, with Authorization %q",
post.alert,
post.authorization,
)
}
// The webhook fails, so the alert for the ban made permanent by the
@@ -1081,24 +1156,7 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
// alerts.json keeps it as sww stops, and once started again,
// sww sends it.
var file struct {
Waiting map[string][]struct {
Event string `json:"event"`
} `json:"waiting"`
}
path := filepath.Join(dir, "alerts.json")
data, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
if err == nil {
err = json.Unmarshal(data, &file)
}
waiting := file.Waiting["webhook"]
if err != nil || len(waiting) != 1 || waiting[0].Event != "permanent_ban" {
t.Fatalf("alerts.json holds %s (%v), want the permanent_ban alert waiting for "+
"the webhook", data, err)
}
wantPermanentBanAlertWaiting(t, dir)
// It counts the alert sent in the metrics, read here from a client the
// ban does not cover.
@@ -1130,6 +1188,37 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
})
}
// wantPermanentBanAlertWaiting checks that alerts.json in dir holds one
// alert waiting for the webhook, the permanent_ban alert.
func wantPermanentBanAlertWaiting(t *testing.T, dir string) {
t.Helper()
var file struct {
Waiting map[string][]struct {
Event string `json:"event"`
} `json:"waiting"`
}
path := filepath.Join(dir, "alerts.json")
//nolint:gosec // a file in the test's directory
data, err := os.ReadFile(path)
if err == nil {
err = json.Unmarshal(data, &file)
}
waiting := file.Waiting["webhook"]
if err != nil || len(waiting) != 1 ||
waiting[0].Event != "permanent_ban" {
t.Fatalf(
"alerts.json holds %s (%v), want the permanent_ban alert waiting for "+
"the webhook",
data,
err,
)
}
}
func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
t.Parallel()
@@ -1167,7 +1256,8 @@ func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
slackPost := slack.firstPost(t)
err := json.Unmarshal([]byte(slackPost.body), &message)
if err != nil || !strings.HasPrefix(message.Text, "*fsn1app1/blog: ban*\n") ||
if err != nil ||
!strings.HasPrefix(message.Text, "*fsn1app1/blog: ban*\n") ||
!strings.Contains(message.Text, "\nclient: "+client+"\n") {
t.Errorf("Slack was sent %s", slackPost.body)
}
@@ -1202,14 +1292,22 @@ func TestStateFileThatDoesNotParseStopsTheStart(t *testing.T) {
dir := t.TempDir()
err := os.WriteFile(filepath.Join(dir, "bans.json"), []byte("{\n"), 0o600)
err := os.WriteFile(
filepath.Join(dir, "bans.json"),
[]byte("{\n"),
0o600,
)
if err != nil {
t.Fatalf("write bans.json: %v", err)
}
// The file ends at the newline that is the second byte of its first
// line.
wantStartRefused(t, dir, filepath.Join(dir, "bans.json")+", line 1, column 2: ")
wantStartRefused(
t,
dir,
filepath.Join(dir, "bans.json")+", line 1, column 2: ",
)
}
func TestUnwritableStateDirStopsTheStart(t *testing.T) {
@@ -1264,7 +1362,11 @@ func wantStartRefused(t *testing.T, dir, want string) {
message, _ := line["error"].(string)
if !strings.HasPrefix(message, want) {
t.Errorf("start refused with %q, want an error starting %q", message, want)
t.Errorf(
"start refused with %q, want an error starting %q",
message,
want,
)
}
}
@@ -1329,7 +1431,11 @@ func runUntilStopped(
select {
case status := <-exited:
if status != 0 {
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
t.Fatalf(
"exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Fatal("still running after being told to stop")
@@ -1371,7 +1477,12 @@ func wantStartingLine(t *testing.T, line map[string]any, dir string) {
for name, value := range want {
if settings[name] != value {
t.Errorf("starting line gives %s=%v, want %v", name, settings[name], value)
t.Errorf(
"starting line gives %s=%v, want %v",
name,
settings[name],
value,
)
}
}
@@ -1411,7 +1522,11 @@ func messages(t *testing.T, received string) []string {
_, message, ok := strings.Cut(record, header)
if !ok || !strings.HasPrefix(record, "<134>1 ") {
t.Fatalf("record %q, want priority <134> and header %q", record, header)
t.Fatalf(
"record %q, want priority <134> and header %q",
record,
header,
)
}
found = append(found, message+"\n")
@@ -1476,7 +1591,10 @@ func seriesValue(t *testing.T, metrics, series string) float64 {
t.Helper()
for line := range strings.Lines(metrics) {
value, found := strings.CutPrefix(strings.TrimSpace(line), series+" ")
value, found := strings.CutPrefix(
strings.TrimSpace(line),
series+" ",
)
if !found {
continue
}
@@ -1510,7 +1628,11 @@ func writeLookupDatabase(t *testing.T, path, country string) {
t.Helper()
lookuptest.Write(t, path, map[string]lookuptest.Network{
placed + "/32": {ASN: "AS64496", ASName: "Example Net", Country: country},
placed + "/32": {
ASN: "AS64496",
ASName: "Example Net",
Country: country,
},
})
}
@@ -1548,7 +1670,8 @@ func onlyBan(t *testing.T, dir string) map[string]any {
path := filepath.Join(dir, "bans.json")
data, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
//nolint:gosec // a file in the test's directory
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read bans.json: %v", err)
}
@@ -1590,7 +1713,11 @@ func wantStatus(t *testing.T, url, from string, status int) {
// request, since sww may not watch its directory yet when it is
// first written. It waits as long as that takes, so that a slow test
// process cannot fail the test.
func saveUntilAnswered(t *testing.T, path, content, url, from string, status int) {
func saveUntilAnswered(
t *testing.T,
path, content, url, from string,
status int,
) {
t.Helper()
for {
@@ -1722,7 +1849,11 @@ func startWebhook(t *testing.T) *webhook {
// waitFor waits until the webhook has been sent an alert for event that
// it took, or, unless answered, failed, and returns it. It waits as long
// as that takes, so that a slow test process cannot fail the test.
func (w *webhook) waitFor(t *testing.T, event string, answered bool) webhookPost {
func (w *webhook) waitFor(
t *testing.T,
event string,
answered bool,
) webhookPost {
t.Helper()
for {
+27 -7
View File
@@ -14,6 +14,7 @@ import (
"syscall"
"testing"
"time"
// The time zone database, so that the process TestMainRunsWAF starts
// finds the zone TZ names on any host.
_ "time/tzdata"
@@ -85,7 +86,8 @@ func TestMainRunsWAF(t *testing.T) {
dir := t.TempDir()
out := &output{}
cmd := exec.CommandContext(t.Context(), binary) //nolint:gosec // this test binary
//nolint:gosec // this test binary
cmd := exec.CommandContext(t.Context(), binary)
cmd.Stdout = out
cmd.Env = append(os.Environ(),
@@ -112,7 +114,11 @@ func TestMainRunsWAF(t *testing.T) {
select {
case status := <-exited:
if status != 0 {
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
t.Fatalf(
"exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Fatal("still running after being told to stop")
@@ -123,7 +129,8 @@ func TestMainRunsWAF(t *testing.T) {
path := filepath.Join(dir, "clients.json")
clients, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
//nolint:gosec // a file in the test's directory
clients, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read clients.json: %v", err)
}
@@ -181,7 +188,11 @@ func TestMainWithoutACommandItKnowsPrintsTheCommands(t *testing.T) {
for _, name := range []string{"rproxy", wafCommand, "healthcheck"} {
if !strings.Contains(stderr.String(), name) {
t.Errorf("stderr %q does not name %s", stderr.String(), name)
t.Errorf(
"stderr %q does not name %s",
stderr.String(),
name,
)
}
}
@@ -219,8 +230,13 @@ func ask(t *testing.T, addr, path string, status int, disposition string) {
if res.StatusCode != status ||
res.Header.Get(waf.DispositionHeader) != disposition {
t.Errorf("answered %d with disposition %q, want %d with %q", res.StatusCode,
res.Header.Get(waf.DispositionHeader), status, disposition)
t.Errorf(
"answered %d with disposition %q, want %d with %q",
res.StatusCode,
res.Header.Get(waf.DispositionHeader),
status,
disposition,
)
}
}
@@ -245,7 +261,11 @@ func runWAF(t *testing.T, env map[string]string, use func(addr string)) {
select {
case status := <-exited:
if status != 0 {
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
t.Fatalf(
"exit status %d, want 0; output:\n%s",
status,
out.text(),
)
}
case <-time.After(waitLimit):
t.Fatal("still running after being told to stop")