How to use IP ranges for Django's INTERNAL_IPS setting

INTERNAL_IPS is a Django setting that works as a "security" filter, allowing Django to know whether it is OK (or not) to disclose sensitive information within its requests and Debug information output. It is also used by other modules, most notably Django Debug Toolbar to know if it is OK to show up.
 
It is a great feature, but it is very annoying to work with it. Sometimes you need a lot of addresses to be considered internal, and sometimes those addresses change a lot, and it is just impractical to keep track of the changes and keep updating INTERNAL_IPS accordingly.
 
To the rescue: Python 3.3 and its ipaddress module. We can now use IP ranges as our INTERNAL_IPS setting and be done with it.
 
To do so use an instance of the following class as the value of INTERNAL_IPS, instantiated with the address that you want to allow.
 
import ipaddress
import logging

class IpNetworks():
    """
    A Class that contains a list of IPvXNetwork objects.

    Credits to https://djangosnippets.org/snippets/1862/
    """

    networks = []

    def __init__(self, addresses):
        """Create a new IpNetwork object for each address provided."""
        for address in addresses:
            self.networks.append(ipaddress.ip_network(address))

    def __contains__(self, address):
        """Check if the given address is contained in any of our Networks."""
        logger = logging.getLogger(__name__)
        logger.debug('Checking address: "%s".', address)
        for network in self.networks:
            if ipaddress.ip_address(address) in network:
                return True
        return False

Now, when Django checks whether an IP is "in" INTERNAL_IPS, our object will check if the address is within any of our ranges.

This is an  example of how to use it, passing the allowed addresses as an environmental variable:

# settings.py

import ipaddress
import logging

# Some other settings are skipped for brevity.

class IpNetworks():
    """
    A Class that contains a list of IPvXNetwork objects.

    Credits to https://djangosnippets.org/snippets/1862/
    """

    networks = []

    def __init__(self, addresses):
        """Create a new IpNetwork object for each address provided."""
        for address in addresses:
            self.networks.append(ipaddress.ip_network(address))

    def __contains__(self, address):
        """Check if the given address is contained in any of our Networks."""
        logger = logging.getLogger(__name__)
        logger.debug('Checking address: "%s".', address)
        for network in self.networks:
            if ipaddress.ip_address(address) in network:
                return True
        return False


INTERNAL_IPS = IpNetworks(os.getenv('INTERNAL_ADDRESSES').split(' '))

Since I use Docker on a Mac for development, the value of my INTERNAL_ADDRESSES environmental variables is usually:

INTERNAL_ADDRESSES=127.0.0.1 172.0.0.0/255.0.0.0

With this value any IP within the 172.0.X.X range is considered internal, as well as 127.0.0.1.

If you want to allow 127.0.0.1 and any IP from the 192.168.1.X range to be considered internal:

INTERNAL_ADDRESSES=127.0.0.1 192.168.1.0/255.255.255.0

Comments, improvements and suggestions are appreciated.

June 3, 2018

Comments