This commit is contained in:
parent
d4408f3d5d
commit
329c041224
|
|
@ -80,6 +80,26 @@ The hostname displayed in the user interface identifying the system on which Net
|
|||
|
||||
---
|
||||
|
||||
## HTTP_CLIENT_IP_HEADERS
|
||||
|
||||
!!! info "This parameter was introduced in NetBox v4.6.1."
|
||||
|
||||
Default:
|
||||
|
||||
```python
|
||||
(
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'REMOTE_ADDR',
|
||||
)
|
||||
```
|
||||
|
||||
An ordered list of HTTP request headers inspected to determine the source IP address of a client request. The first header in the list which is present on the request is used; if none are found, the client IP cannot be determined. This is most commonly required when NetBox is deployed behind a reverse proxy which injects a proprietary client IP header (e.g. `HTTP_CF_CONNECTING_IP` for Cloudflare).
|
||||
|
||||
The client IP is used for source-address restrictions on API tokens and for logging failed login attempts.
|
||||
|
||||
---
|
||||
|
||||
## HTTP_PROXIES
|
||||
|
||||
Default: `None`
|
||||
|
|
|
|||
|
|
@ -132,6 +132,11 @@ GRAPHQL_DEFAULT_VERSION = getattr(configuration, 'GRAPHQL_DEFAULT_VERSION', 1)
|
|||
GRAPHQL_MAX_ALIASES = getattr(configuration, 'GRAPHQL_MAX_ALIASES', 10)
|
||||
GRAPHQL_MAX_QUERY_DEPTH = getattr(configuration, 'GRAPHQL_MAX_QUERY_DEPTH', None)
|
||||
HOSTNAME = getattr(configuration, 'HOSTNAME', platform.node())
|
||||
HTTP_CLIENT_IP_HEADERS = getattr(configuration, 'HTTP_CLIENT_IP_HEADERS', (
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'REMOTE_ADDR',
|
||||
))
|
||||
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', {})
|
||||
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
|
||||
ISOLATED_DEPLOYMENT = getattr(configuration, 'ISOLATED_DEPLOYMENT', False)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import warnings
|
|||
from contextlib import ExitStack, contextmanager
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from netaddr import AddrFormatError, IPAddress
|
||||
|
|
@ -71,15 +72,14 @@ def copy_safe_request(request, include_files=True):
|
|||
|
||||
def get_client_ip(request, additional_headers=()):
|
||||
"""
|
||||
Return the client (source) IP address of the given request.
|
||||
Return the client (source) IP address of the given request. Accepts an optional list of headers to inspect in
|
||||
addition to those configured under HTTP_CLIENT_IP_HEADERS.
|
||||
"""
|
||||
HTTP_HEADERS = (
|
||||
'HTTP_X_REAL_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'REMOTE_ADDR',
|
||||
*additional_headers
|
||||
headers = (
|
||||
*settings.HTTP_CLIENT_IP_HEADERS,
|
||||
*additional_headers,
|
||||
)
|
||||
for header in HTTP_HEADERS:
|
||||
for header in headers:
|
||||
if header in request.META:
|
||||
ip = request.META[header].split(',')[0].strip()
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.test import RequestFactory, TestCase, override_settings
|
||||
from netaddr import IPAddress
|
||||
|
||||
from utilities.request import copy_safe_request, get_client_ip
|
||||
|
|
@ -61,3 +61,31 @@ class GetClientIPTestCase(TestCase):
|
|||
request = self.factory.get('/', HTTP_X_FORWARDED_FOR='invalid_ip')
|
||||
with self.assertRaises(ValueError):
|
||||
get_client_ip(request)
|
||||
|
||||
def test_no_matching_header(self):
|
||||
request = self.factory.get('/')
|
||||
request.META.pop('REMOTE_ADDR', None)
|
||||
self.assertIsNone(get_client_ip(request))
|
||||
|
||||
def test_additional_headers_argument(self):
|
||||
"""Headers passed via `additional_headers` are checked after the configured defaults."""
|
||||
request = self.factory.get('/', HTTP_CF_CONNECTING_IP='10.0.0.1')
|
||||
request.META.pop('REMOTE_ADDR', None)
|
||||
self.assertEqual(
|
||||
get_client_ip(request, additional_headers=('HTTP_CF_CONNECTING_IP',)),
|
||||
IPAddress('10.0.0.1'),
|
||||
)
|
||||
|
||||
def test_default_headers_precede_additional_headers(self):
|
||||
"""Headers from HTTP_CLIENT_IP_HEADERS take precedence over `additional_headers`."""
|
||||
request = self.factory.get('/', HTTP_X_FORWARDED_FOR='192.168.1.1', HTTP_CF_CONNECTING_IP='10.0.0.1')
|
||||
self.assertEqual(
|
||||
get_client_ip(request, additional_headers=('HTTP_CF_CONNECTING_IP',)),
|
||||
IPAddress('192.168.1.1'),
|
||||
)
|
||||
|
||||
@override_settings(HTTP_CLIENT_IP_HEADERS=('HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'))
|
||||
def test_custom_configured_headers(self):
|
||||
"""get_client_ip() should honor the HTTP_CLIENT_IP_HEADERS setting."""
|
||||
request = self.factory.get('/', HTTP_X_FORWARDED_FOR='192.168.1.1', HTTP_CF_CONNECTING_IP='10.0.0.1')
|
||||
self.assertEqual(get_client_ip(request), IPAddress('10.0.0.1'))
|
||||
|
|
|
|||
Loading…
Reference in New Issue