R3: gateway edge hardening — body cap, h2c sizing, rate-limit observability
- GATEWAY_MAX_BODY_BYTES (1 MiB): connect WithReadMaxBytes + http.MaxBytesReader
on the public mux; explicit http2.Server MaxConcurrentStreams/IdleTimeout and
an http.Server ReadHeaderTimeout (R2 report follow-up).
- gateway_rate_limited_total{class} counter, Debug per rejection, a rejection
tracker drained every 30 s into a Warn summary per key and a report POST to
/api/v1/internal/ratelimit/report (feeds the admin view + auto-flag).
- The dead AdminPerMinute/AdminBurst policy now guards the /_gm mount (429),
ahead of its Basic-Auth.
- resolve() logs the cause of infra session-resolve failures at Warn (the
transient unauthenticated dips from the R2 run); unknown tokens stay silent.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package ratelimit_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"scrabble/gateway/internal/ratelimit"
|
||||
)
|
||||
|
||||
// TestTrackerDrain verifies rejections aggregate per (class, key) and that a
|
||||
// drain resets the tracker.
|
||||
func TestTrackerDrain(t *testing.T) {
|
||||
tr := ratelimit.NewTracker()
|
||||
if got := tr.Drain(); got != nil {
|
||||
t.Fatalf("empty tracker drained %v, want nil", got)
|
||||
}
|
||||
|
||||
tr.Add("user", "u-1")
|
||||
tr.Add("user", "u-1")
|
||||
tr.Add("public", "10.0.0.1")
|
||||
|
||||
got := map[string]ratelimit.Rejection{}
|
||||
for _, r := range tr.Drain() {
|
||||
got[r.Class+"/"+r.Key] = r
|
||||
}
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("drained %d entries, want 2", len(got))
|
||||
}
|
||||
if r := got["user/u-1"]; r.Rejected != 2 {
|
||||
t.Errorf("user/u-1 rejected = %d, want 2", r.Rejected)
|
||||
}
|
||||
if r := got["public/10.0.0.1"]; r.Rejected != 1 {
|
||||
t.Errorf("public/10.0.0.1 rejected = %d, want 1", r.Rejected)
|
||||
}
|
||||
|
||||
if got := tr.Drain(); got != nil {
|
||||
t.Fatalf("second drain = %v, want nil", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user