verify / verify (push) Successful in 2m55s
Export the reviewed application-neutral package set through the exact public allowlist. Development history and private application evidence remain outside this canonical source root. Developed with material AI assistance under maintainer review. Signed-off-by: Cole Speelman <crspeelman@gmail.com>
92 lines
3.3 KiB
Go
92 lines
3.3 KiB
Go
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package abuse
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/netip"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gamertan.com/web/requestmeta"
|
|
)
|
|
|
|
func TestEngineUsesApplicationClassifier(t *testing.T) {
|
|
now := time.Unix(100, 0)
|
|
resolver, _ := requestmeta.New(requestmeta.Config{TrustedProxies: []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8")}, Random: strings.NewReader(strings.Repeat("a", 64))})
|
|
engine := Engine{Store: NewMemoryStore(), Policy: Policy{Threshold: 2, Window: time.Minute, BlockDuration: time.Hour, Retention: time.Hour, MaxClients: 10}, Now: func() time.Time { return now }, Classify: func(request *http.Request) Signal {
|
|
if request.URL.Path == "/application-known-bad" {
|
|
return Signal{Severity: Strike, Reason: "application policy"}
|
|
}
|
|
return Signal{}
|
|
}}
|
|
called := 0
|
|
handler := resolver.Middleware(engine.Middleware(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { called++ })))
|
|
for index := 0; index < 2; index++ {
|
|
request := httptest.NewRequest(http.MethodGet, "http://example.test/application-known-bad", nil)
|
|
request.RemoteAddr = "127.0.0.1:1000"
|
|
request.Header.Set("X-Forwarded-For", "203.0.113.8")
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if index == 0 && response.Code != 200 {
|
|
t.Fatalf("first status=%d", response.Code)
|
|
}
|
|
if index == 1 && response.Code != 429 {
|
|
t.Fatalf("second status=%d", response.Code)
|
|
}
|
|
}
|
|
if called != 1 {
|
|
t.Fatalf("called=%d", called)
|
|
}
|
|
}
|
|
|
|
func TestImmediateBlockAndPardon(t *testing.T) {
|
|
store := NewMemoryStore()
|
|
now := time.Unix(100, 0)
|
|
policy := Policy{Threshold: 10, Window: time.Minute, BlockDuration: time.Hour, Retention: time.Hour, MaxClients: 10}
|
|
decision, err := store.Record(t.Context(), "192.0.2.1", Signal{Severity: ImmediateBlock, Reason: "credential probe"}, now, policy)
|
|
if err != nil || decision.Permanent || !decision.Blocked(now) {
|
|
t.Fatalf("decision=%+v err=%v", decision, err)
|
|
}
|
|
if err = store.Pardon(t.Context(), "192.0.2.1", now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decision, _ = store.Lookup(t.Context(), "192.0.2.1", now)
|
|
if decision.Blocked(now) {
|
|
t.Fatal("pardon did not clear block")
|
|
}
|
|
}
|
|
|
|
func TestPermanentCapacityFailsClosed(t *testing.T) {
|
|
store := NewMemoryStore()
|
|
now := time.Unix(100, 0)
|
|
policy := Policy{Threshold: 10, Window: time.Minute, BlockDuration: time.Hour, Retention: time.Hour, MaxClients: 1}
|
|
decision, err := store.Record(t.Context(), "192.0.2.1", Signal{Severity: PermanentBlock, Reason: "operator decision"}, now, policy)
|
|
if err != nil || !decision.Permanent {
|
|
t.Fatalf("decision=%+v err=%v", decision, err)
|
|
}
|
|
if _, err = store.Record(t.Context(), "192.0.2.2", Signal{Severity: Strike, Reason: "probe"}, now, policy); !errors.Is(err, ErrCapacity) {
|
|
t.Fatalf("err=%v", err)
|
|
}
|
|
if decision, _ = store.Lookup(t.Context(), "192.0.2.1", now); !decision.Permanent {
|
|
t.Fatal("capacity handling evicted permanent decision")
|
|
}
|
|
}
|
|
|
|
func TestLookupDoesNotAllocateUnseenClients(t *testing.T) {
|
|
store := NewMemoryStore()
|
|
now := time.Unix(100, 0)
|
|
for index := 0; index < 100; index++ {
|
|
if _, err := store.Lookup(t.Context(), "192.0.2."+strconv.Itoa(index), now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if len(store.clients) != 0 {
|
|
t.Fatalf("lookups allocated %d entries", len(store.clients))
|
|
}
|
|
}
|