diff options
| author | bndw <ben@bdw.to> | 2026-02-14 21:50:10 -0800 |
|---|---|---|
| committer | bndw <ben@bdw.to> | 2026-02-14 21:50:10 -0800 |
| commit | e647880669b79cd968231cf85dc037a18e8bfd9c (patch) | |
| tree | 4275603466ff264b680af75dc4386bf130d75997 /internal/ratelimit | |
| parent | 4dbd96ec697196d43ad41eca4fd43d53da46a081 (diff) | |
fix: prioritize proxy headers for rate limiting
Check X-Forwarded-For and X-Real-IP headers before peer info to correctly
identify clients behind reverse proxies. Previously, rate limiting would
apply globally when behind Caddy/nginx because all requests appeared to
come from the proxy's IP address.
This fix is critical for production deployments behind reverse proxies.
Diffstat (limited to 'internal/ratelimit')
| -rw-r--r-- | internal/ratelimit/interceptor.go | 17 |
1 files changed, 10 insertions, 7 deletions
diff --git a/internal/ratelimit/interceptor.go b/internal/ratelimit/interceptor.go index b27fe7e..57721a7 100644 --- a/internal/ratelimit/interceptor.go +++ b/internal/ratelimit/interceptor.go | |||
| @@ -59,14 +59,11 @@ func getIdentifier(ctx context.Context) string { | |||
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | // getIPAddress extracts the client IP address from the context. | 61 | // getIPAddress extracts the client IP address from the context. |
| 62 | // When behind a reverse proxy, this checks X-Forwarded-For and X-Real-IP headers first | ||
| 63 | // to get the real client IP, then falls back to peer info (which would be the proxy IP). | ||
| 62 | func getIPAddress(ctx context.Context) string { | 64 | func getIPAddress(ctx context.Context) string { |
| 63 | // Try to get from peer info | 65 | // Check proxy headers first (X-Forwarded-For, X-Real-IP) |
| 64 | p, ok := peer.FromContext(ctx) | 66 | // This is critical for rate limiting behind reverse proxies like Caddy/nginx |
| 65 | if ok && p.Addr != nil { | ||
| 66 | return p.Addr.String() | ||
| 67 | } | ||
| 68 | |||
| 69 | // Try to get from metadata (X-Forwarded-For header) | ||
| 70 | md, ok := metadata.FromIncomingContext(ctx) | 67 | md, ok := metadata.FromIncomingContext(ctx) |
| 71 | if ok { | 68 | if ok { |
| 72 | if xff := md.Get("x-forwarded-for"); len(xff) > 0 { | 69 | if xff := md.Get("x-forwarded-for"); len(xff) > 0 { |
| @@ -77,6 +74,12 @@ func getIPAddress(ctx context.Context) string { | |||
| 77 | } | 74 | } |
| 78 | } | 75 | } |
| 79 | 76 | ||
| 77 | // Fall back to peer info (direct connection or proxy IP) | ||
| 78 | p, ok := peer.FromContext(ctx) | ||
| 79 | if ok && p.Addr != nil { | ||
| 80 | return p.Addr.String() | ||
| 81 | } | ||
| 82 | |||
| 80 | return "unknown" | 83 | return "unknown" |
| 81 | } | 84 | } |
| 82 | 85 | ||
