Django ALLOWED_HOSTS IP范围

53

有没有一种方法在Django中设置允许的主机IP范围?

就像这样:

ALLOWED_HOSTS = ['172.17.*.*']

2
我本来要回答“是的”,但进行了一些谷歌搜索,没有找到有人这样做的具体示例。根据文档,您可以在列表中使用一个孤立的通配符,例如ALLOWED_HOSTS = ['*'](出于安全原因不建议这样做),但我还没有看到像您的示例那样的东西。我仍然倾向于“是的”,但在看到明确说明的参考资料之前,我仍然不会被说服。这可能是一个愚蠢的问题,但您是否尝试过它,以查看它是否会在django中引发任何错误? - Ian
最好将此任务转移到专门高效处理的Web服务器上。或者甚至可以设置防火墙规则,越高层次的流量过滤得越好。 - serg
@Nez,是的,我有。请看下面我的答案。我找到了解决这个问题的方法。 - Alex T
不错。我知道它是可以完成的,但我不知道我在寻找什么样的解决方案。中间件很有道理。@serg建议应该在堆栈的更高层处理,这很好,但对于相对低流量的构建来说,这应该没问题,对吧? - Ian
@serg的建议对于高负载项目来说肯定比中间件更合适。但我正在寻找Django级别的解决方案。 - Alex T
7个回答

62

不,目前这是不可能的。根据文档,仅支持以下语法:

['www.example.com']  # Fully qualified domain
['.example.com']  # Subdomain wildcard, matches example.com and www.example.com 
['*']  # Matches anything
如果您查看validate_host方法的实现,您会发现仅使用'*'是被允许的,但是将*作为字符串的通配符(例如'172.17.*.*')是不支持的。

48

我在 Django 上发布了一个工单,然后被告知可以通过以下方式实现:

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

更新。如果您正在使用Docker,则以下代码更好,因为gethostbyname无法获取正确的信息。

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

将它转换为集合的原因是gethostbyname_ex可能会返回重复项。

Django网站上的链接是:

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


1
这不安全吗? - Karl Zillner
@KarlZillner 我看不出来为什么这是不安全的。 - Thomas Turner
1
@KarlZillner 不对,这不是真的。它只会添加您机器的IP地址,没有域名将起作用,否则会出现“无效的HTTP_HOST”错误。而 "*" 将允许所有IP地址和所有域名。 - Thomas Turner
2
不幸的是,这只允许添加机器的IP和主机名,而不允许添加我们信任但不知道静态IP地址的任何其他服务器,例如Amazon负载均衡器。 如果可以添加通配符,我可以允许子网范围,但现在我必须使用nginx重写主机来解决问题。 - Michel Rugenbrink
对于那些使用Kubernetes的人,这个答案需要进行一些小的修改才能适用于我: ALLOWED_HOSTS = [ gethostname(), gethostbyname(gethostname()), '.example.com'] - Kurt
显示剩余4条评论

30

Mozilla发布了一个名为django-allow-cidr的Python包,旨在解决这个问题。

公告博客文章解释了它对于像健康检查这样没有Host标题只使用IP地址的事情非常有用。

您需要略微更改您的IP地址'172.17.*.*',使其成为像172.17.0.0/16这样的CIDR范围


3
请注意,这会允许像 172.17.malicious.host.com 这样恶意的东西。 - Cole
这对我有用;我遇到了健康检查失败的问题,因为源主机位于内部子网上。将 10.0.0.0/16 添加到 ALLOWED_CIDR_NETS 中完美解决了问题。ALLOWED_CIDR_NETS = ['10.0.0.0/16'] - Goran
2
@Cole 不会的。 看源代码 https://github.com/mozmeao/django-allow-cidr/blob/master/allow_cidr/middleware.py 你可以看到它只会匹配特定的IP。 self.allowed_cidr_nets = [IPNetwork(net) for net in allowed_cidr_nets] for net in self.allowed_cidr_nets:你可以在 https://netaddr.readthedocs.io/en/latest/tutorial_01.html#slicing 中了解更多关于 IPNetwork 的信息,其中“使用生成器确保与大型 IP 子网的处理效率高。” - sanchaz

26

这是一个快速而简单的解决方案。

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

2
['172.17.{}.{}'.format(i,j) for i in range(256) for j in range(256)] 使用 format 的相同代码。我喜欢它。 - Nomen Nescio
"['192.168.1.{}'.format(i) for i in range(256)]" 可以生成一个包含 256 个元素的列表,每个元素都是类似于 "192.168.1.XXX" 的字符串。这个代码非常实用,可以生成大量的 IP 地址。感谢,我只想指出如果您只需要最后一个数字,也可以使用类似的方法。 - blobbymatt
1
不是一个好的调试方法,转储的环境看起来很混乱。 - Itachi

6

如果我们研究Django如何验证主机,我们就能更好地了解如何制作更灵活的ALLOWED_HOSTS条目:

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
    )

这里有一个 RegexHost 工具,可以通过此验证。

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)

以下是使用示例:

# 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]

1
你是在建议使用一个超过65K个项目的数组来验证主机吗? - Ramy M. Mousa
1
不是吧?我希望有人能看到这些断言,确认它们已经通过测试,然后将它们从设置文件中删除(如果那里是测试的地方)。 - DragonBobZ
3
请记住,这只会在服务器启动时运行,所以即使他们将其保留在设置文件中,应用程序的性能也不会受到影响。为了避免某些人被困惑,我会将它们注释掉。 - DragonBobZ

3
我找到了一种过滤IP范围的解决方案:
使用这种方法,我们可以通过任何方式(例如使用正则表达式)过滤IP。
参考链接:https://dev59.com/HpXfa4cB1Zd3GeqPem3q#36222755
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

感谢 @Zorgmorduk。

1

这个解决方案对我很有效:

  • django-allow-cidr==0.5.0 添加到你的 requirements.txt 文件中
  • 在你的 setting.py 文件中添加:

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

ALLOWED_CIDR_NETS = ['172.17.0.0/16']

像这样:

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',
]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接