Skip to content

Commit d193c1f

Browse files
author
Dmytro Trotsko
committed
Updated get real ip function
1 parent da4623f commit d193c1f

File tree

1 file changed

+29
-6
lines changed

1 file changed

+29
-6
lines changed

src/epiportal/middleware.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Any
1010

1111
from django.utils.deprecation import MiddlewareMixin
12+
from django.conf import settings
1213

1314
logger = logging.getLogger("epiportal.requests")
1415

@@ -43,13 +44,35 @@ def _should_log_request(request) -> bool:
4344
return not any(pattern in path for pattern in LOG_EXCLUDE_PATH_PATTERNS)
4445

4546

46-
def _get_client_ip(request) -> str:
47+
def _get_client_ip(req) -> str:
4748
"""Extract client IP, respecting X-Forwarded-For when behind proxies."""
48-
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
49-
if x_forwarded_for:
50-
# Take the leftmost (original client) IP
51-
return x_forwarded_for
52-
return request.META.get("REMOTE_ADDR", "")
49+
if settings.REVERSE_PROXY_DEPTH:
50+
# we only expect/trust (up to) "REVERSE_PROXY_DEPTH" number of proxies between this server and the outside world.
51+
# a REVERSE_PROXY_DEPTH of 0 means not proxied, i.e. server is globally directly reachable.
52+
# a negative proxy depth is a special case to trust the whole chain -- not generally recommended unless the
53+
# most-external proxy is configured to disregard "X-Forwarded-For" from outside.
54+
# really, ONLY trust the following headers if reverse proxied!!!
55+
x_forwarded_for = req.META.get("HTTP_X_FORWARDED_FOR")
56+
57+
if x_forwarded_for:
58+
full_proxy_chain = x_forwarded_for.split(",")
59+
# eliminate any extra addresses at the front of this list, as they could be spoofed.
60+
if settings.REVERSE_PROXY_DEPTH > 0:
61+
depth = settings.REVERSE_PROXY_DEPTH
62+
else:
63+
# special case for -1/negative: setting `depth` to 0 will not strip any items from the chain
64+
depth = 0
65+
trusted_proxy_chain = full_proxy_chain[-depth:]
66+
# accept the first (or only) address in the remaining trusted part of the chain as the actual remote address
67+
return trusted_proxy_chain[0].strip()
68+
69+
# fall back to "X-Real-Ip" if "X-Forwarded-For" isnt present
70+
x_real_ip = req.META.get("HTTP_X_REAL_IP")
71+
if x_real_ip:
72+
return x_real_ip
73+
74+
# if we are not proxied (or we are proxied but the headers werent present and we fell through to here), just use the remote ip addr as the true client address
75+
return req.META.get("REMOTE_ADDR")
5376

5477

5578
def _sanitize_headers(meta: dict) -> dict[str, str]:

0 commit comments

Comments
 (0)