check / check (push) Successful in 4m12s
GET /_smallwebwaf/metrics answers in the Prometheus text format for a request carrying SWWAF_METRICS_TOKEN, 401 without it and 404 while it is unset. Every request under /_smallwebwaf/ but the health check now goes through the checks and is answered where it would be forwarded, 404 for any path but the metrics, so none reaches the app. SWWAF_METRICS_TOP_N bounds the series by country, the rest counted as other. Judgement call: a request answered at smallwebwaf's own endpoints is neither forwarded nor refused in the client's history. Deviation: go.mod and go.sum written by hand from the module proxy and sum.golang.org, as go runs only through make. Deviation: no metrics yet for state files read again after an edit or edits set aside; that work is not merged. Model: opus-5-5
120 lines
2.9 KiB
Go
120 lines
2.9 KiB
Go
package ratelimit_test
|
|
|
|
import (
|
|
"net/netip"
|
|
"testing"
|
|
"time"
|
|
|
|
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
|
)
|
|
|
|
func TestHistoryKeepsEveryRequest(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
limiter := ratelimit.New(ratelimit.Limits{})
|
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
|
start := midnight()
|
|
|
|
for i, r := range []ratelimit.Request{
|
|
{Country: "DE", Forwarded: true, Status: 200, RequestBytes: 10, ResponseBytes: 100},
|
|
{Forwarded: true, Status: 101},
|
|
{Forwarded: true, Status: 304, RequestBytes: 5},
|
|
{Country: "FR", Refused: true, Status: 403, ResponseBytes: 10, BrokeLimit: true},
|
|
{Forwarded: true, Status: 502, ResponseBytes: 12},
|
|
// Closed without an answer: refused, and no response.
|
|
{Refused: true, Status: 0},
|
|
// Answered at smallwebwaf's own endpoints: neither forwarded nor
|
|
// refused.
|
|
{Status: 404},
|
|
} {
|
|
limiter.AddToHistory(client, start.Add(time.Duration(i)*time.Minute), r)
|
|
}
|
|
|
|
want := ratelimit.History{
|
|
FirstSeen: start,
|
|
LastSeen: start.Add(6 * time.Minute),
|
|
Country: "FR",
|
|
LookedUp: start.Add(3 * time.Minute),
|
|
Requests: 7,
|
|
Forwarded: 4,
|
|
Refused: 2,
|
|
RequestBytes: 15,
|
|
ResponseBytes: 122,
|
|
Responses: ratelimit.Responses{
|
|
Status1xx: 1, Status2xx: 1, Status3xx: 1, Status4xx: 2, Status5xx: 1,
|
|
},
|
|
Offences: ratelimit.Offences{Limit: 1},
|
|
}
|
|
|
|
got := historyOf(t, limiter, client)
|
|
if got != want {
|
|
t.Errorf("history\n%+v\nwant\n%+v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestResetKeepsTheHistory(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: limit})
|
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
|
start := midnight()
|
|
|
|
for range limit {
|
|
wantCount(t, limiter, client, start, "")
|
|
limiter.AddToHistory(client, start, ratelimit.Request{Forwarded: true})
|
|
}
|
|
|
|
limiter.Reset(client)
|
|
|
|
if got := historyOf(t, limiter, client).Requests; got != limit {
|
|
t.Errorf("the history counts %d requests, want %d", got, limit)
|
|
}
|
|
}
|
|
|
|
func TestRequestsAddsUpTheClientsInsideTheNetblock(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
limiter := ratelimit.New(ratelimit.Limits{})
|
|
|
|
for client, requests := range map[string]int{
|
|
"198.51.100.9/32": 2,
|
|
"198.51.100.10/32": 3,
|
|
"192.0.2.1/32": 5,
|
|
"2001:db8:5::/64": 7,
|
|
} {
|
|
for range requests {
|
|
limiter.AddToHistory(netip.MustParsePrefix(client), midnight(),
|
|
ratelimit.Request{})
|
|
}
|
|
}
|
|
|
|
for netblock, want := range map[string]int64{
|
|
"198.51.100.9/32": 2,
|
|
"198.51.100.0/24": 5,
|
|
"2001:db8:5::/64": 7,
|
|
"203.0.113.0/24": 0,
|
|
} {
|
|
got := limiter.Requests(netip.MustParsePrefix(netblock))
|
|
if got != want {
|
|
t.Errorf("%s has sent %d requests, want %d", netblock, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// historyOf returns client's history.
|
|
func historyOf(
|
|
t *testing.T, limiter *ratelimit.Limiter, client netip.Prefix,
|
|
) ratelimit.History {
|
|
t.Helper()
|
|
|
|
for _, c := range limiter.Snapshot() {
|
|
if c.Client == client {
|
|
return c.History
|
|
}
|
|
}
|
|
|
|
t.Fatalf("%s is not in the table", client)
|
|
|
|
return ratelimit.History{}
|
|
}
|