Skip to content

Commit 0dd37ea

Browse files
authored
Merge pull request #1889 from yuvipanda/ip-check
Better ipv6 support when checking network bans
2 parents 0284be9 + 694628c commit 0dd37ea

File tree

5 files changed

+27
-57
lines changed

5 files changed

+27
-57
lines changed

binderhub/app.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -736,19 +736,6 @@ def _cast_ban_networks(self, proposal):
736736

737737
return networks
738738

739-
ban_networks_min_prefix_len = Integer(
740-
1,
741-
help="The shortest prefix in ban_networks",
742-
)
743-
744-
@observe("ban_networks")
745-
def _update_prefix_len(self, change):
746-
if not change.new:
747-
min_len = 1
748-
else:
749-
min_len = min(net.prefixlen for net in change.new)
750-
self.ban_networks_min_prefix_len = min_len or 1
751-
752739
tornado_settings = Dict(
753740
config=True,
754741
help="""
@@ -928,7 +915,6 @@ def initialize(self, *args, **kwargs):
928915
"debug": self.debug,
929916
"launcher": self.launcher,
930917
"ban_networks": self.ban_networks,
931-
"ban_networks_min_prefix_len": self.ban_networks_min_prefix_len,
932918
"build_pool": self.build_pool,
933919
"build_token_check_origin": self.build_token_check_origin,
934920
"build_token_secret": self.build_token_secret,

binderhub/base.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ def check_request_ip(self):
3838
match = ip_in_networks(
3939
request_ip,
4040
ban_networks,
41-
min_prefix_len=self.settings["ban_networks_min_prefix_len"],
4241
)
4342
if match:
44-
network, message = match
43+
network_spec = match
44+
message = ban_networks[network_spec]
4545
app_log.warning(
46-
f"Blocking request from {request_ip} matching banned network {network}: {message}"
46+
f"Blocking request from {request_ip} matching banned network {network_spec}: {message}"
4747
)
4848
raise web.HTTPError(403, f"Requests from {message} are not allowed")
4949

binderhub/tests/test_auth.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,14 @@ async def test_ban_networks(request, app, use_session, path, banned, prefixlen,
9191
"255.255.255.255/32": "255.x",
9292
"1.0.0.0/8": "1.x",
9393
}
94-
local_net = str(ipaddress.ip_network("127.0.0.1").supernet(new_prefix=prefixlen))
94+
local_net = [
95+
str(ipaddress.ip_network("127.0.0.1").supernet(new_prefix=prefixlen)),
96+
str(ipaddress.ip_network("::1").supernet(new_prefix=prefixlen)),
97+
]
98+
9599
if banned:
96-
ban_networks[local_net] = "local"
100+
for net in local_net:
101+
ban_networks[net] = "local"
97102

98103
# pass through trait validators on app
99104
app.ban_networks = ban_networks
@@ -106,7 +111,6 @@ def reset():
106111
app.tornado_app.settings,
107112
{
108113
"ban_networks": app.ban_networks,
109-
"ban_networks_min_prefix_len": app.ban_networks_min_prefix_len,
110114
},
111115
):
112116
r = await async_requests.get(url)

binderhub/tests/test_utils.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,24 +116,14 @@ def later():
116116
("192.168.1.2", ["192.168.1.0/24", "255.255.0.0/16"], True),
117117
("192.168.1.2", ["255.255.0.0/16", "192.168.1.0/24"], True),
118118
("192.168.1.2", [], False),
119+
("2001:db8:0:0:0:0:0:1", ["2001:db8::/32", "192.168.1.1/32"], True),
120+
("3001:db8:0:0:0:0:0:1", ["2001:db8::/32", "192.168.1.1/32"], False),
119121
],
120122
)
121123
def test_ip_in_networks(ip, cidrs, found):
122-
networks = {ipaddress.ip_network(cidr): f"message {cidr}" for cidr in cidrs}
123-
if networks:
124-
min_prefix = min(net.prefixlen for net in networks)
125-
else:
126-
min_prefix = 1
127-
match = utils.ip_in_networks(ip, networks, min_prefix)
124+
match = utils.ip_in_networks(ip, [ipaddress.ip_network(c) for c in cidrs])
128125
if found:
129126
assert match
130-
net, message = match
131-
assert message == f"message {net}"
132-
assert ipaddress.ip_address(ip) in net
127+
assert str(match) in cidrs
133128
else:
134129
assert match is False
135-
136-
137-
def test_ip_in_networks_invalid():
138-
with pytest.raises(ValueError):
139-
utils.ip_in_networks("1.2.3.4", {}, 0)

binderhub/utils.py

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import time
55
from collections import OrderedDict
66
from hashlib import blake2b
7+
from typing import Iterable
78
from unittest.mock import Mock
89

910
from kubernetes.client import api_client
@@ -167,32 +168,21 @@ def url_path_join(*pieces):
167168
return result
168169

169170

170-
def ip_in_networks(ip, networks, min_prefix_len=1):
171-
"""Return whether `ip` is in the dict of networks
172-
173-
This is O(1) regardless of the size of networks
174-
175-
Implementation based on netaddr.IPSet.__contains__
176-
177-
Repeatedly checks if ip/32; ip/31; ip/30; etc. is in networks
178-
for all netmasks that match the given ip,
179-
for a max of 32 dict key lookups for ipv4.
171+
def ip_in_networks(
172+
ip_addr: str, networks: Iterable[ipaddress.IPv4Network | ipaddress.IPv6Network]
173+
):
174+
"""
175+
Checks if `ip_addr` is contained within any of the networks in `networks`
180176
181-
If all netmasks have a prefix length of e.g. 24 or greater,
182-
min_prefix_len prevents checking wider network masks that can't possibly match.
177+
If ip_addr is in any of the provided networks, return the first network that matches.
178+
If not, return False
183179
184-
Returns `(netmask, networks[netmask])` for matching netmask
185-
in networks, if found; False, otherwise.
180+
Both ipv6 and ipv4 are supported
186181
"""
187-
if min_prefix_len < 1:
188-
raise ValueError(f"min_prefix_len must be >= 1, got {min_prefix_len}")
189-
if not networks:
190-
return False
191-
check_net = ipaddress.ip_network(ip)
192-
while check_net.prefixlen >= min_prefix_len:
193-
if check_net in networks:
194-
return check_net, networks[check_net]
195-
check_net = check_net.supernet(1)
182+
ip = ipaddress.ip_address(ip_addr)
183+
for network in networks:
184+
if ip in network:
185+
return network
196186
return False
197187

198188

0 commit comments

Comments
 (0)