Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handles "unknown" in the X-Forwarded-For header. #3

Merged
merged 1 commit into from
Aug 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
13 changes: 13 additions & 0 deletions iprestrict/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
logger = logging.getLogger(__name__)


UNKNOWN = "unknown"


class IPRestrictMiddleware:
def __init__(self, get_response):
self.get_response = get_response
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down
44 changes: 40 additions & 4 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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):
Expand Down Expand Up @@ -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"]
Expand All @@ -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)