fix(backend): rate-limit and cap report ingest, drop wildcard CORS (closes #20)
check / check (push) Successful in 58s
check / check (push) Successful in 58s
POST /api/v1/reports stays unauthenticated but is bounded. Each client address, as the trusted-proxy logic resolves it, may send REPORTS_PER_MINUTE reports a minute (default 60, all at once if it likes), using golang.org/x/time/rate; past that it gets 429 with Retry-After. Buckets that have refilled are dropped once a minute, so idle addresses do not pile up. reportbuf refuses a report that would take the report files past DATA_DIR_MAX_BYTES (default 1 GiB) with ErrFull, answered with 507; the count starts from the files already in DATA_DIR, and reports not yet written count at their uncompressed size. CORS adds nothing unless CORS_ALLOWED_ORIGINS lists origins. A limit that is not a positive number stops the server from starting. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// TestAddressLimiterDropsOnlyFullBuckets checks the sweep in allow:
|
||||
// a minute after the last one, it drops an address whose bucket has
|
||||
// filled up again, and keeps one still short of tokens, whose limit
|
||||
// would otherwise start over.
|
||||
func TestAddressLimiterDropsOnlyFullBuckets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const (
|
||||
refilled = "198.51.100.1"
|
||||
drained = "198.51.100.2"
|
||||
)
|
||||
|
||||
// Two a minute: one token back every 30 seconds.
|
||||
limiter := &addressLimiter{
|
||||
burst: 2,
|
||||
byAddr: make(map[string]*rate.Limiter),
|
||||
limit: rate.Every(30 * time.Second),
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
// The first call sweeps the empty map and takes one of two tokens.
|
||||
limiter.allow(refilled, start)
|
||||
limiter.allow(drained, start.Add(59*time.Second))
|
||||
limiter.allow(drained, start.Add(59*time.Second))
|
||||
|
||||
// A minute after the first sweep, this call sweeps again.
|
||||
limiter.allow("198.51.100.3", start.Add(time.Minute))
|
||||
|
||||
if _, ok := limiter.byAddr[refilled]; ok {
|
||||
t.Error("address with a full bucket was kept")
|
||||
}
|
||||
|
||||
if _, ok := limiter.byAddr[drained]; !ok {
|
||||
t.Error("address short of tokens was dropped")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user