Skip to content

fix(settings): trust reverse proxy for client IP in rate limiting#1813

Open
miaulalala wants to merge 1 commit into
masterfrom
fix/login-ratelimit-trusted-proxy
Open

fix(settings): trust reverse proxy for client IP in rate limiting#1813
miaulalala wants to merge 1 commit into
masterfrom
fix/login-ratelimit-trusted-proxy

Conversation

@miaulalala

Copy link
Copy Markdown
Contributor

Summary

  • mod_wsgi logs show every request's REMOTE_ADDR as 127.0.0.1 — there's a reverse proxy in front of Apache that isn't being accounted for by the app.
  • django-allauth's rate limiter (backing ACCOUNT_RATE_LIMITS["login_failed"]) resolves the client IP via allauth.core.internal.httpkit.get_client_ip(), which only trusts X-Forwarded-For when ALLAUTH_TRUSTED_PROXY_COUNT > 0. That setting wasn't set anywhere, so it fell back to REMOTE_ADDR — i.e. the proxy's own address for every visitor.
  • Net effect: the per-IP login_failed: "10/h/ip" limit collapsed into a single site-wide bucket of 10 failed password attempts per hour, shared by every user. Once exhausted (typos, scanners, credential stuffing — trivial to trigger), every regular username/password login got rejected with 429 until the hour rolled over, while GitHub OAuth — a separate code path with no rate limiter — kept working. This matches reports of "GitHub login works, regular login doesn't."
  • Confirmed with sysadmins: there's exactly one reverse-proxy hop in front of Apache, and X-Forwarded-For is reliably populated with the real client IP in this deployment (an existing ops-side logging filter for bad_requests.log/fail2ban already depends on it).
  • Fix: set ALLAUTH_TRUSTED_PROXY_COUNT = 1 so allauth resolves the real client IP from the trusted hop of X-Forwarded-For instead of REMOTE_ADDR, restoring per-visitor rate limiting.

Test plan

  • python -c "... from allauth import app_settings; print(app_settings.TRUSTED_PROXY_COUNT)" → confirms 1 is picked up from settings.
  • Post-deploy: verify a burst of failed logins from one real client IP no longer blocks unrelated users' logins.

🤖 Generated with Claude Code

Apache/mod_wsgi logs show every request's remote address as 127.0.0.1,
meaning a reverse proxy sits in front of it. allauth's rate limiter
(used by ACCOUNT_RATE_LIMITS["login_failed"]) only reads the real
client IP from X-Forwarded-For when ALLAUTH_TRUSTED_PROXY_COUNT > 0;
left unset, it fell back to REMOTE_ADDR, i.e. the proxy's own address
for every visitor. That collapsed the per-IP login_failed limit into a
single site-wide bucket of 10 failed attempts per hour shared by every
user, so once exhausted (typos, scanners, credential stuffing), regular
username/password login was rejected for everyone until the hour
rolled over -- while GitHub OAuth, a separate code path with no rate
limiter, kept working. This matches reports of 'GitHub login works but
regular login doesn't'.

Set ALLAUTH_TRUSTED_PROXY_COUNT = 1 so allauth resolves the real client
IP from the last hop of X-Forwarded-For instead of REMOTE_ADDR,
restoring per-visitor rate limiting.

Pending sysadmin confirmation that there is exactly one reverse proxy
hop in front of Apache, that it sets X-Forwarded-For itself (rather
than forwarding a client-supplied value), and that Apache is never
reachable bypassing that proxy -- required for this setting to be safe.

Signed-off-by: Anna Larch <anna@nextcloud.com>

@marcelklehr marcelklehr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants