mirror of
https://github.com/frappe/gunicorn.git
synced 2026-07-04 03:31:29 +08:00
feat: support CIDR networks in forwarded_allow_ips and proxy_allow_ips
Use Python's ipaddress module to support IP networks in allow lists. Individual IP addresses are converted to /32 (IPv4) or /128 (IPv6) networks. CIDR notation (e.g., 192.168.0.0/16) is now supported. Fixes #1485 Closes #2390
This commit is contained in:
parent
b0d38928c8
commit
e52ac46e29
@ -407,12 +407,15 @@ def validate_list_of_existing_files(val):
|
||||
def validate_string_to_addr_list(val):
|
||||
val = validate_string_to_list(val)
|
||||
|
||||
result = []
|
||||
for addr in val:
|
||||
if addr == "*":
|
||||
result.append(addr)
|
||||
continue
|
||||
_vaid_ip = ipaddress.ip_address(addr)
|
||||
# Support both single IPs and CIDR networks
|
||||
result.append(ipaddress.ip_network(addr, strict=False))
|
||||
|
||||
return val
|
||||
return result
|
||||
|
||||
|
||||
def validate_string_to_list(val):
|
||||
@ -1278,8 +1281,11 @@ class ForwardedAllowIPS(Setting):
|
||||
validator = validate_string_to_addr_list
|
||||
default = os.environ.get("FORWARDED_ALLOW_IPS", "127.0.0.1,::1")
|
||||
desc = """\
|
||||
Front-end's IPs from which allowed to handle set secure headers.
|
||||
(comma separated).
|
||||
Front-end's IP addresses or networks from which allowed to handle
|
||||
set secure headers. (comma separated).
|
||||
|
||||
Supports both individual IP addresses (e.g., ``192.168.1.1``) and
|
||||
CIDR networks (e.g., ``192.168.0.0/16``).
|
||||
|
||||
Set to ``*`` to disable checking of front-end IPs. This is useful for setups
|
||||
where you don't know in advance the IP address of front-end, but
|
||||
@ -2094,7 +2100,11 @@ class ProxyAllowFrom(Setting):
|
||||
validator = validate_string_to_addr_list
|
||||
default = "127.0.0.1,::1"
|
||||
desc = """\
|
||||
Front-end's IPs from which allowed accept proxy requests (comma separated).
|
||||
Front-end's IP addresses or networks from which allowed accept
|
||||
proxy requests (comma separated).
|
||||
|
||||
Supports both individual IP addresses (e.g., ``192.168.1.1``) and
|
||||
CIDR networks (e.g., ``192.168.0.0/16``).
|
||||
|
||||
Set to ``*`` to disable checking of front-end IPs. This is useful for setups
|
||||
where you don't know in advance the IP address of front-end, but
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
# See the NOTICE for more information.
|
||||
|
||||
import io
|
||||
import ipaddress
|
||||
import re
|
||||
import socket
|
||||
|
||||
@ -30,6 +31,22 @@ VERSION_RE = re.compile(r"HTTP/(\d)\.(\d)")
|
||||
RFC9110_5_5_INVALID_AND_DANGEROUS = re.compile(r"[\0\r\n]")
|
||||
|
||||
|
||||
def _ip_in_allow_list(ip_str, allow_list):
|
||||
"""Check if IP address is in the allow list (which may contain networks)."""
|
||||
if '*' in allow_list:
|
||||
return True
|
||||
try:
|
||||
ip = ipaddress.ip_address(ip_str)
|
||||
except ValueError:
|
||||
return False
|
||||
for network in allow_list:
|
||||
if network == '*':
|
||||
return True
|
||||
if ip in network:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Message:
|
||||
def __init__(self, cfg, unreader, peer_addr):
|
||||
self.cfg = cfg
|
||||
@ -82,9 +99,8 @@ class Message:
|
||||
# nonsense. either a request is https from the beginning
|
||||
# .. or we are just behind a proxy who does not remove conflicting trailers
|
||||
pass
|
||||
elif ('*' in cfg.forwarded_allow_ips or
|
||||
not isinstance(self.peer_addr, tuple)
|
||||
or self.peer_addr[0] in cfg.forwarded_allow_ips):
|
||||
elif (not isinstance(self.peer_addr, tuple)
|
||||
or _ip_in_allow_list(self.peer_addr[0], cfg.forwarded_allow_ips)):
|
||||
secure_scheme_headers = cfg.secure_scheme_headers
|
||||
forwarder_headers = cfg.forwarder_headers
|
||||
|
||||
@ -352,9 +368,8 @@ class Request(Message):
|
||||
|
||||
def proxy_protocol_access_check(self):
|
||||
# check in allow list
|
||||
if ("*" not in self.cfg.proxy_allow_ips and
|
||||
isinstance(self.peer_addr, tuple) and
|
||||
self.peer_addr[0] not in self.cfg.proxy_allow_ips):
|
||||
if (isinstance(self.peer_addr, tuple) and
|
||||
not _ip_in_allow_list(self.peer_addr[0], self.cfg.proxy_allow_ips)):
|
||||
raise ForbiddenProxyRequest(self.peer_addr[0])
|
||||
|
||||
def parse_proxy_protocol(self, line):
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
# This file is part of gunicorn released under the MIT license.
|
||||
# See the NOTICE for more information.
|
||||
|
||||
import ipaddress
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@ -165,10 +166,30 @@ def test_str_validation():
|
||||
|
||||
def test_str_to_addr_list_validation():
|
||||
c = config.Config()
|
||||
assert c.proxy_allow_ips == ["127.0.0.1", "::1"]
|
||||
assert c.forwarded_allow_ips == ["127.0.0.1", "::1"]
|
||||
# Default values are now network objects
|
||||
assert c.proxy_allow_ips == [
|
||||
ipaddress.ip_network("127.0.0.1/32"),
|
||||
ipaddress.ip_network("::1/128")
|
||||
]
|
||||
assert c.forwarded_allow_ips == [
|
||||
ipaddress.ip_network("127.0.0.1/32"),
|
||||
ipaddress.ip_network("::1/128")
|
||||
]
|
||||
# Single IPs are converted to /32 or /128 networks
|
||||
c.set("forwarded_allow_ips", "127.0.0.1,192.0.2.1")
|
||||
assert c.forwarded_allow_ips == ["127.0.0.1", "192.0.2.1"]
|
||||
assert c.forwarded_allow_ips == [
|
||||
ipaddress.ip_network("127.0.0.1/32"),
|
||||
ipaddress.ip_network("192.0.2.1/32")
|
||||
]
|
||||
# CIDR networks are supported
|
||||
c.set("forwarded_allow_ips", "127.0.0.0/8,192.168.0.0/16")
|
||||
assert c.forwarded_allow_ips == [
|
||||
ipaddress.ip_network("127.0.0.0/8"),
|
||||
ipaddress.ip_network("192.168.0.0/16")
|
||||
]
|
||||
# Wildcard is preserved as string
|
||||
c.set("forwarded_allow_ips", "*")
|
||||
assert c.forwarded_allow_ips == ["*"]
|
||||
c.set("forwarded_allow_ips", "")
|
||||
assert c.forwarded_allow_ips == []
|
||||
c.set("forwarded_allow_ips", None)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user