Add proxy precendence and count configuration

Fixes #286
This commit is contained in:
Aleksi Häkli 2018-04-11 21:13:48 +03:00 committed by Aleksi Häkli
parent 521b6adb97
commit 512452e580
No known key found for this signature in database
GPG key ID: 3E7146964D726BBE
7 changed files with 75 additions and 30 deletions

View file

@ -4,18 +4,16 @@ from hashlib import md5
from django.contrib.auth import get_user_model
from django.utils import timezone
from ipware.ip2 import get_client_ip
from axes.conf import settings
from axes.models import AccessAttempt
from axes.utils import get_axes_cache
from axes.utils import get_axes_cache, get_client_ip
def _query_user_attempts(request):
"""Returns access attempt record if it exists.
Otherwise return None.
"""
ip, _ = get_client_ip(request)
ip = get_client_ip(request)
username = request.POST.get(settings.AXES_USERNAME_FORM_FIELD, None)
if settings.AXES_ONLY_USER_FAILURES:
@ -60,7 +58,7 @@ def get_cache_key(request_or_obj):
un = request_or_obj.username
ua = request_or_obj.user_agent
else:
ip, _ = get_client_ip(request_or_obj)
ip = get_client_ip(request_or_obj)
un = request_or_obj.POST.get(settings.AXES_USERNAME_FORM_FIELD, None)
ua = request_or_obj.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
@ -176,7 +174,7 @@ def is_user_lockable(request):
def is_already_locked(request):
ip, _ = get_client_ip(request)
ip = get_client_ip(request)
if (
settings.AXES_ONLY_USER_FAILURES or

View file

@ -53,3 +53,23 @@ class MyAppConf(AppConf):
# message to show when locked out and have cooloff disabled
PERMALOCK_MESSAGE = 'Account locked: too many login attempts. Contact an admin to unlock your account.'
# if your deployment is using reverse proxies, set this value to 'left-most' or 'right-most' per your configuration
PROXY_ORDER = 'left-most'
# if your deployment is using reverse proxies, set this value to the number of proxies in front of Django
PROXY_COUNT = None
# if your deployment is using reverse proxies, set to your trusted proxy IP addresses prefixes if needed
PROXY_TRUSTED_IPS = None
# set to the names of request.META attributes that should be checked for the IP address of the client
# if your deployment is using reverse proxies, ensure that the header attributes are securely set by the proxy
# ensure that the client can not spoof the headers by setting them and sending them through the proxy
META_PRECEDENCE_ORDER = getattr(
settings, 'AXES_META_PRECEDENCE_ORDER', getattr(
settings, 'IPWARE_META_PRECEDENCE_ORDER', (
'REMOTE_ADDR',
)
)
)

View file

@ -8,8 +8,6 @@ from django.dispatch import receiver
from django.dispatch import Signal
from django.utils import timezone
from ipware.ip import get_ip
from axes.conf import settings
from axes.attempts import get_cache_key
from axes.attempts import get_cache_timeout
@ -19,7 +17,7 @@ from axes.attempts import ip_in_whitelist
from axes.models import AccessLog, AccessAttempt
from axes.utils import get_client_str
from axes.utils import query2str
from axes.utils import get_axes_cache
from axes.utils import get_axes_cache, get_client_ip
log = logging.getLogger(settings.AXES_LOGGER)
@ -36,7 +34,7 @@ def log_user_login_failed(sender, credentials, request, **kwargs):
log.error('Attempt to authenticate with a custom backend failed.')
return
ip_address = get_ip(request)
ip_address = get_client_ip(request)
username = credentials[settings.AXES_USERNAME_FORM_FIELD]
user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
@ -128,7 +126,7 @@ def log_user_logged_in(sender, request, user, **kwargs):
""" When a user logs in, update the access log
"""
username = user.get_username()
ip_address = get_ip(request)
ip_address = get_client_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]

View file

@ -189,7 +189,7 @@ class AccessAttemptTest(TestCase):
# Make a login attempt again
self.test_valid_login()
@patch('ipware.ip.get_ip', return_value='127.0.0.1')
@patch('axes.utils.get_client_ip', return_value='127.0.0.1')
def test_get_cache_key(self, get_ip_mock):
""" Test the cache key format"""
# Getting cache key from request

View file

@ -7,6 +7,8 @@ from socket import inet_pton, AF_INET6, error
from django.core.cache import cache, caches
from django.utils import six
import ipware.ip2
from axes.conf import settings
from axes.models import AccessAttempt
@ -49,6 +51,21 @@ def get_client_str(username, ip_address, user_agent=None, path_info=None):
return client
def get_client_ip(request):
client_ip_attribute = 'axes_client_ip'
if not hasattr(request, client_ip_attribute):
client_ip, _ = ipware.ip2.get_client_ip(
request,
proxy_order=settings.AXES_PROXY_ORDER,
proxy_count=settings.AXES_PROXY_COUNT,
proxy_trusted_ips=settings.AXES_PROXY_TRUSTED_IPS,
request_header_order=settings.AXES_META_PRECEDENCE_ORDER,
)
setattr(request, client_ip_attribute, client_ip)
return getattr(request, client_ip_attribute)
def is_ipv6(ip):
try:
inet_pton(AF_INET6, ip)

View file

@ -3,7 +3,7 @@
Configuration
=============
Add `axes` to your ``INSTALLED_APPS``::
Add ``axes`` to your ``INSTALLED_APPS``::
INSTALLED_APPS = (
'django.contrib.admin',
@ -27,25 +27,15 @@ Add ``axes.backends.AxesModelBackend`` to the top of ``AUTHENTICATION_BACKENDS``
Run ``python manage.py migrate`` to sync the database.
Configure `django-ipware <https://github.com/un33k/django-ipware/>`_ to your liking. Pay close attention to the `IPWARE_META_PRECEDENCE_ORDER <https://github.com/un33k/django-ipware#precedence-order>`_ setting. Please note that this configuration is required for functional security in your project. A good starting point for a project running without a reverse proxy could be::
IPWARE_META_PRECEDENCE_ORDER = (
'REMOTE_ADDR',
)
Things to you might need to change in your code, especially if you get a ``AxesModelBackend.RequestParameterRequired``:
- make sure any calls to ``django.contrib.auth.authenticate`` pass the request.
- make sure any auth libraries you use that call the authentication middleware stack pass request. Notably Django Rest
Framework (DRF) ``BasicAuthentication`` does not pass request. `Here is an example workaround for DRF`_.
.. _Here is an example workaround for DRF: https://gist.github.com/markddavidoff/7e442b1ea2a2e68d390e76731c35afe7
Known configuration problems
----------------------------
Axes has a few configuration issues with external packages and specific cache backends
due to their internal implementations.
Cache problems
~~~~~~~~~~~~~~
If you are running Axes on a deployment with in-memory Django cache,
the ``axes_reset`` functionality might not work predictably.
@ -80,6 +70,28 @@ to your ``settings.py`` file::
There are no known problems in other cache backends such as
``DummyCache``, ``FileBasedCache``, or ``MemcachedCache`` backends.
Authentication backend problems
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you get ``AxesModelBackend.RequestParameterRequired`` exceptions,
make sure any auth libraries and middleware you use pass the request object to authenticate.
Notably Django Rest Framework (DRF) ``BasicAuthentication`` does not pass request.
`Here is an example workaround for DRF <https://gist.github.com/markddavidoff/7e442b1ea2a2e68d390e76731c35afe7>`_.
Reverse proxy configuration
---------------------------
Django Axes makes use of ``django-ipware`` package to detect the IP address of the client
and uses some conservative configuration parameters by default for security.
If you are using reverse proxies, you will need to configure one or more of the
following settings to suit your set up to correctly resolve client IP addresses:
* ``AXES_PROXY_COUNT``: The number of reverse proxies in front of Django as an integer. Default: ``None``
* ``AXES_META_PRECEDENCE_ORDER``: The names of ``request.META`` attributes as a tuple of strings
to check to get the client IP address. Check the Django documentation for header naming conventions.
Default: ``IPWARE_META_PRECEDENCE_ORDER`` setting if set, else ``('REMOTE_ADDR', )``
Customizing Axes
----------------

View file

@ -23,7 +23,7 @@ setup(
install_requires=[
'pytz',
'django-appconf',
'django-ipware',
'django-ipware>=2.0.2',
'win_inet_pton ; python_version < "3.4" and sys_platform == "win32"'
],
include_package_data=True,