Files
Victor Lyuboslavsky 8f24773d2e Added per-IP rate limiting and response caching (#41516)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #37092 

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added per‑IP rate limiting to IdP metadata and SSO endpoints.
* Implemented TTL-backed in‑memory caching for IdP metadata responses to
reduce backend load.

* **Tests**
* Added tests covering metadata caching behavior, cache miss/error
handling, and content type preservation.
* Added tests validating rate limiting behavior across clients, bursts,
and proxy scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-03-13 15:21:16 -05:00

50 lines
1.6 KiB
Go

package ratelimit
import (
"fmt"
"net/http"
"github.com/realclientip/realclientip-go"
"github.com/throttled/throttled/v2"
)
// clientIPVaryBy implements throttled.VaryBy using the real client IP extracted via a configured
// realclientip strategy. This correctly identifies clients behind load balancers/reverse proxies
// instead of rate-limiting by the proxy's IP address.
type clientIPVaryBy struct {
strategy realclientip.Strategy
}
func (v *clientIPVaryBy) Key(r *http.Request) string {
ip := v.strategy.ClientIP(r.Header, r.RemoteAddr)
if ip == "" {
return r.RemoteAddr
}
return ip
}
// DefaultHTTPRateQuota returns a reasonable default for HTTP-level rate limiting:
// 10 requests per minute per IP with burst of 9 (allows 10 initial requests before throttling).
func DefaultHTTPRateQuota() throttled.RateQuota {
return throttled.RateQuota{
MaxRate: throttled.PerMin(10),
MaxBurst: 9,
}
}
// NewHTTPRateLimiter creates an HTTP-level rate limiter that varies by real client IP.
// The ipStrategy determines how to extract the real client IP from requests (e.g. from
// X-Forwarded-For headers when behind a load balancer). Use endpointer.NewClientIPStrategy
// to create a strategy from the server's trusted_proxies configuration.
func NewHTTPRateLimiter(store throttled.GCRAStore, quota throttled.RateQuota, ipStrategy realclientip.Strategy) (*throttled.HTTPRateLimiter, error) {
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
if err != nil {
return nil, fmt.Errorf("create rate limiter: %w", err)
}
return &throttled.HTTPRateLimiter{
RateLimiter: rateLimiter,
VaryBy: &clientIPVaryBy{strategy: ipStrategy},
}, nil
}