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:
@@ -44,6 +44,9 @@ type Config struct {
|
||||
SessionCacheMax int
|
||||
// PushHeartbeatInterval is the idle keep-alive cadence on a client live stream.
|
||||
PushHeartbeatInterval time.Duration
|
||||
// MaxBodyBytes caps one inbound request body on the public listener and one
|
||||
// Connect message read; oversized requests are refused without buffering.
|
||||
MaxBodyBytes int
|
||||
// RateLimit configures the in-memory anti-abuse limiter.
|
||||
RateLimit RateLimitConfig
|
||||
// Telemetry configures the OpenTelemetry providers (shared bootstrap).
|
||||
@@ -77,6 +80,11 @@ const (
|
||||
defaultServiceName = "scrabble-gateway"
|
||||
)
|
||||
|
||||
// DefaultMaxBodyBytes is the default request-body cap (GATEWAY_MAX_BODY_BYTES):
|
||||
// 1 MiB — far above any legitimate edge payload (drafts and chat are a few KB)
|
||||
// yet small enough to stop a cheap memory-amplification upload (R3).
|
||||
const DefaultMaxBodyBytes = 1 << 20
|
||||
|
||||
// supportedLanguages is the set of game languages a service may declare for the
|
||||
// New Game variant gating; defaultSupportedLanguages is the non-platform fallback.
|
||||
var (
|
||||
@@ -130,6 +138,9 @@ func Load() (Config, error) {
|
||||
if c.PushHeartbeatInterval, err = envDuration("GATEWAY_PUSH_HEARTBEAT_INTERVAL", defaultPushHeartbeatInterval); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
if c.MaxBodyBytes, err = envInt("GATEWAY_MAX_BODY_BYTES", DefaultMaxBodyBytes); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
if c.DefaultSupportedLanguages, err = envLanguages("GATEWAY_DEFAULT_SUPPORTED_LANGUAGES", defaultSupportedLanguages); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
@@ -161,6 +172,9 @@ func (c Config) validate() error {
|
||||
if c.BackendGRPCAddr == "" {
|
||||
return fmt.Errorf("config: GATEWAY_BACKEND_GRPC_ADDR must not be empty")
|
||||
}
|
||||
if c.MaxBodyBytes <= 0 {
|
||||
return fmt.Errorf("config: GATEWAY_MAX_BODY_BYTES must be positive")
|
||||
}
|
||||
if err := c.Telemetry.Validate(); err != nil {
|
||||
return fmt.Errorf("config: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user