From 34b1389cf45cebb0b11f9ab3582f1f09884395d4 Mon Sep 17 00:00:00 2001 From: Tamas Szabo Date: Sun, 30 Aug 2020 08:35:14 +0300 Subject: [PATCH] Handles "unknown" in the X-Forwarded-For header. Fixes muccg/django-iprestrict#53 References: * muccg/djang-iprestrict#54 * #1 --- docs/configuration.rst | 18 ++++++++++++++++ iprestrict/middleware.py | 13 ++++++++++++ tests/test_middleware.py | 44 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 71 insertions(+), 4 deletions(-) diff --git a/docs/configuration.rst b/docs/configuration.rst index c1360f4..48f7843 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -205,3 +205,21 @@ Use this setting when using a managed proxy with a dynamic IP (like when behind an AWS Load Balancer, or other cloud equivalent). When this setting is ``True``, Django IP Restrict will always check the HTTP ``X-Forwarded-For`` header to determine the true client IP address. + + +IPRESTRICT_USE_PROXY_IF_UNKNOWN +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Default: ``False`` + +Some proxies can be configured to hide the client IP behind them by using +the special value ``unknown`` in the ``X-Forwarded-For`` header. +By default, these requests will be blocked. + +Set this setting to ``True`` if you would like to use the proxy's IP address +instead when the proxy reports them as ``unknown``. +This will allow you to set up rules against the IP address of the proxy instead +of the client IP addresses it hides. + +Please note that in this special case the proxy is also checked against +``IPRESTRICT_TRUSTED_PROXIES`` if you are using that setting. diff --git a/iprestrict/middleware.py b/iprestrict/middleware.py index 9447e48..8b50801 100644 --- a/iprestrict/middleware.py +++ b/iprestrict/middleware.py @@ -10,6 +10,9 @@ logger = logging.getLogger(__name__) +UNKNOWN = "unknown" + + class IPRestrictMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -25,6 +28,9 @@ def __init__(self, get_response): self.trust_all_proxies = bool( get_setting("IPRESTRICT_TRUST_ALL_PROXIES", "TRUST_ALL_PROXIES", False) ) + self.use_proxy_if_unknown = bool( + getattr(settings, "IPRESTRICT_USE_PROXY_IF_UNKNOWN", False) + ) def __call__(self, request): if self.reload_rules: @@ -54,6 +60,13 @@ def extract_client_ip(self, request): def extract_client_ip_proxied_request(self, client_ip, forwarded_for): closest_proxy = client_ip client_ip = forwarded_for.pop(0) + + if client_ip == UNKNOWN: + if self.use_proxy_if_unknown: + client_ip = closest_proxy + else: + raise exceptions.PermissionDenied + if self.trust_all_proxies: return client_ip diff --git a/tests/test_middleware.py b/tests/test_middleware.py index bd6fe0d..1d8f6ae 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -69,7 +69,7 @@ def test_middleware_restricts_other_ip(self): response = self.client.get("", REMOTE_ADDR="10.1.1.1") self.assertEqual(response.status_code, 403) - @override_settings(IPRESTRICT_TRUSTED_PROXIES=(PROXY,), ALLOW_PROXIES=False) + @override_settings(IPRESTRICT_TRUSTED_PROXIES=(PROXY,)) def test_middleware_allows_if_proxy_is_trusted(self): response = self.client.get("", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR=LOCAL_IP) self.assertEqual(response.status_code, 404) @@ -78,6 +78,31 @@ def test_middleware_restricts_if_proxy_is_not_trusted(self): response = self.client.get("", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR=LOCAL_IP) self.assertEqual(response.status_code, 403) + @override_settings( + IPRESTRICT_TRUSTED_PROXIES=(LOCAL_IP,), IPRESTRICT_USE_PROXY_IF_UNKNOWN=True + ) + def test_middleware_allows_if_PROXY_is_used_and_allowed_when_IP_is_unknown(self): + response = self.client.get( + "", REMOTE_ADDR=LOCAL_IP, HTTP_X_FORWARDED_FOR="unknown" + ) + self.assertEqual(response.status_code, 404) + + @override_settings(IPRESTRICT_USE_PROXY_IF_UNKNOWN=True) + def test_middleware_restricts_if_PROXY_is_used_but_NOT_allowed_when_IP_is_unknown( + self, + ): + response = self.client.get( + "", REMOTE_ADDR=LOCAL_IP, HTTP_X_FORWARDED_FOR="unknown" + ) + self.assertEqual(response.status_code, 403) + + @override_settings(IPRESTRICT_TRUSTED_PROXIES=(PROXY,)) + def test_middleware_restricts_if_IP_is_unknown(self): + response = self.client.get( + "", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR="unknown" + ) + self.assertEqual(response.status_code, 403) + class ReloadRulesTest(TestCase): def setUp(self): @@ -149,9 +174,7 @@ def test_multiple_proxies_all_trusted(self): client_ip = self.middleware.extract_client_ip(request) self.assertEqual(client_ip, LOCAL_IP) - @override_settings( - IPRESTRICT_TRUSTED_PROXIES=(PROXY,), IPRESTRICT_TRUST_ALL_PROXIES=True - ) + @override_settings(IPRESTRICT_TRUST_ALL_PROXIES=True) def test_trust_all_proxies_on(self): self.middleware = IPRestrictMiddleware(dummy_get_response) proxies = ["1.1.1.1", "2.2.2.2", "3.3.3.3", "4.4.4.4"] @@ -161,3 +184,16 @@ def test_trust_all_proxies_on(self): client_ip = self.middleware.extract_client_ip(request) self.assertEqual(client_ip, LOCAL_IP) + + @override_settings( + IPRESTRICT_TRUST_ALL_PROXIES=True, IPRESTRICT_USE_PROXY_IF_UNKNOWN=True + ) + def test_use_proxy_if_unknown(self): + self.middleware = IPRestrictMiddleware(dummy_get_response) + proxies = ["1.1.1.1", "2.2.2.2", "3.3.3.3", "4.4.4.4"] + request = self.factory.get( + "", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR=", ".join(["unknown"] + proxies) + ) + + client_ip = self.middleware.extract_client_ip(request) + self.assertEqual(client_ip, PROXY)