[Django]-Django ALLOWED_HOSTS IPs range

48πŸ‘

βœ…

I posted a ticket on Django however I was shown this could be achieved by doing the following

from socket import gethostname, gethostbyname 
ALLOWED_HOSTS = [ gethostname(), gethostbyname(gethostname()), ] 

Update. If you are using docker the following code is better as gethostbyname doesn’t get the correct information.

from socket import gethostname, gethostbyname, gethostbyname_ex
ALLOWED_HOSTS = [ gethostname(), ] + list(set(gethostbyname_ex(gethostname())[2]))

The reason it gets converted to a set is that fact that gethostbyname_ex can return duplicates.

The link to the ticket on django website is.

https://code.djangoproject.com/ticket/27485

πŸ‘€Thomas Turner

62πŸ‘

No, this is not currently possible. According to the docs, the following syntax is supported:

['www.example.com']  # Fully qualified domain
['.example.com']  # Subdomain wildcard, matches example.com and www.example.com 
['*']  # Matches anything

If you look at the implementation of the validate_host method, you can see that using '*' by itself is allowed, but using * as a wildcard as part of a string (e.g. '172.17.*.*') is not supported.

πŸ‘€Alasdair

31πŸ‘

Mozilla have released a Python package called django-allow-cidr which is designed to solve exactly this problem.

The announcement blog post explains that it’s useful for things like health checks that don’t have a Host header and just use an IP address.

You would have to change your IP address '172.17.*.*' slightly to be a CIDR range like 172.17.0.0/16

πŸ‘€alexmuller

26πŸ‘

Here is a quick and dirty solution.

ALLOWED_HOSTS += ['172.17.{}.{}'.format(i,j) for i in range(256) for j in range(256)]
πŸ‘€Ivandir

6πŸ‘

If we take a look into how Django validates hosts, we can gain insight into how we can make more flexible ALLOWED_HOSTS entries:

def validate_host(host, allowed_hosts):
    """
    Validate the given host for this site.

    Check that the host looks valid and matches a host or host pattern in the
    given list of ``allowed_hosts``. Any pattern beginning with a period
    matches a domain and all its subdomains (e.g. ``.example.com`` matches
    ``example.com`` and any subdomain), ``*`` matches anything, and anything
    else must match exactly.

    Note: This function assumes that the given host is lowercased and has
    already had the port, if any, stripped off.

    Return ``True`` for a valid host, ``False`` otherwise.
    """
    return any(pattern == '*' or is_same_domain(host, pattern) for pattern in allowed_hosts)

. . .

def is_same_domain(host, pattern):
    """
    Return ``True`` if the host is either an exact match or a match
    to the wildcard pattern.

    Any pattern beginning with a period matches a domain and all of its
    subdomains. (e.g. ``.example.com`` matches ``example.com`` and
    ``foo.example.com``). Anything else is an exact string match.
    """
    if not pattern:
        return False

    pattern = pattern.lower()
    return (
        pattern[0] == '.' and (host.endswith(pattern) or host == pattern[1:]) or
        pattern == host
    )

Here is a RegexHost utility which can make it through this validation.

class RegexHost(str):
    def lower(self):
        return self

    def __init__(self, pattern):
        super().__init__()
        self.regex = re.compile(pattern)

    def __eq__(self, other):
        # override the equality operation to use regex matching
        # instead of str.__eq__(self, other) 
        return self.regex.match(other)

Which can be used like so:

# this matches '172.17.*.*' and also many impossible IPs
host = RegexHost(r'172\.17\.[0-9]{1,3}\.[0-9]{1,3}')

# Un-comment the below assertions to prove to yourself that this host
# validation works. Do not leave these assertions active in 
# production code for startup performance considerations.

# assert all(host == f'172.17.{i}.{j}' for i in range(256) for j in range(256))
# assert not any(host == f'172.18.{i}.{j}' for i in range(256) for j in range(256))
ALLOWED_HOSTS = [host]
πŸ‘€DragonBobZ

3πŸ‘

I’ve found such solution for filtering range of IPs:

https://stackoverflow.com/a/36222755/3766751

Using this approach we can filter IPs by any means (f.e. with regex).

from django.http import HttpResponseForbidden

class FilterHostMiddleware(object):

    def process_request(self, request):

        allowed_hosts = ['127.0.0.1', 'localhost']  # specify complete host names here
        host = request.META.get('HTTP_HOST')

        if host[len(host)-10:] == 'dyndns.org':  # if the host ends with dyndns.org then add to the allowed hosts
            allowed_hosts.append(host)
        elif host[:7] == '192.168':  # if the host starts with 192.168 then add to the allowed hosts
            allowed_hosts.append(host)

        if host not in allowed_hosts:
            raise HttpResponseForbidden

        return None

Thanks for @Zorgmorduk

πŸ‘€Alex T

2πŸ‘

This solution works for me:

  • Add django-allow-cidr==0.5.0 to your requirements.txt
  • Add to your setting.py:

MIDDLEWARE = [ 'allow_cidr.middleware.AllowCIDRMiddleware', ... ]

ALLOWED_CIDR_NETS = ['172.17.0.0/16']

Like this:

ALLOWED_HOSTS = ['127.0.0.1', 'localhost', '.domain.com']
ALLOWED_CIDR_NETS = ['172.17.0.0/16']

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'allow_cidr.middleware.AllowCIDRMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
πŸ‘€Miguel Conde

Leave a comment