Skip to content

Commit e2603aa

Browse files
committed
Revert "Update all other code to remove IPList class"
This reverts commit fec4645.
1 parent b446a59 commit e2603aa

10 files changed

Lines changed: 286 additions & 46 deletions

File tree

aikido_zen/background_process/commands/sync_data_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
from unittest.mock import MagicMock
55
from .sync_data import process_sync_data
66
from aikido_zen.background_process.routes import Routes
7+
from aikido_zen.helpers.iplist import IPList
78
from ..packages import PackagesStore
8-
from ...helpers.ip_matcher import IPMatcher
99
from ...storage.hostnames import Hostnames
1010
from ...storage.statistics import Statistics
1111

@@ -18,7 +18,8 @@ def setup_connection_manager():
1818
connection_manager.hostnames = Hostnames()
1919
connection_manager.conf.endpoints = ["endpoint1", "endpoint2"]
2020

21-
connection_manager.conf.bypassed_ips = IPMatcher(["192.168.1.1"])
21+
connection_manager.conf.bypassed_ips = IPList()
22+
connection_manager.conf.bypassed_ips.add("192.168.1.1")
2223
connection_manager.conf.blocked_uids = ["user1", "user2"]
2324
connection_manager.conf.last_updated_at = 200
2425
connection_manager.statistics = Statistics()

aikido_zen/background_process/service_config.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
from typing import Pattern
66

77
import regex as re
8-
9-
from aikido_zen.helpers.ip_matcher import IPMatcher
108
from aikido_zen.helpers.match_endpoints import match_endpoints
9+
from aikido_zen.helpers.iplist import IPList
1110

1211

1312
# noinspection PyAttributeOutsideInit
@@ -51,7 +50,7 @@ def set_endpoints(self, endpoints):
5150
endpoint for endpoint in endpoints if not endpoint.get("graphql")
5251
]
5352

54-
# Create an IPMatcher instance for each endpoint
53+
# Create a IPList instance for each endpoint
5554
for endpoint in self.endpoints:
5655
if not "allowedIPAddresses" in endpoint:
5756
# This feature is not supported by the current aikido server version
@@ -63,7 +62,7 @@ def set_endpoints(self, endpoints):
6362
# Skip empty allowlist
6463
continue
6564

66-
endpoint["allowedIPAddresses"] = IPMatcher(endpoint["allowedIPAddresses"])
65+
endpoint["allowedIPAddresses"] = IPList(endpoint["allowedIPAddresses"])
6766

6867
def get_endpoints(self, route_metadata):
6968
"""
@@ -73,14 +72,14 @@ def get_endpoints(self, route_metadata):
7372
return match_endpoints(route_metadata, self.endpoints)
7473

7574
def set_bypassed_ips(self, bypassed_ips):
76-
"""Creates an IPMatcher from the given bypassed ip set"""
77-
self.bypassed_ips = IPMatcher()
75+
"""Creates an IPList from the given bypassed ip set"""
76+
self.bypassed_ips = IPList()
7877
for ip in bypassed_ips:
7978
self.bypassed_ips.add(ip)
8079

8180
def is_bypassed_ip(self, ip):
8281
"""Checks if the IP is on the bypass list"""
83-
return self.bypassed_ips.has(ip)
82+
return self.bypassed_ips.matches(ip)
8483

8584
def set_blocked_ips(self, blocked_ip_entries):
8685
self.blocked_ips = list(map(parse_ip_entry, blocked_ip_entries))
@@ -90,7 +89,7 @@ def set_allowed_ips(self, allowed_ip_entries):
9089

9190
def is_blocked_ip(self, ip):
9291
for entry in self.blocked_ips:
93-
if entry["iplist"].has(ip):
92+
if entry["iplist"].matches(ip):
9493
return entry["description"]
9594
return False
9695

@@ -112,8 +111,12 @@ def parse_ip_entry(entry):
112111
"""
113112
Converts ip entry: {"source": "example", "description": "Example description", "ips": []}
114113
"""
114+
iplist = IPList()
115+
for ip in entry["ips"]:
116+
iplist.add(ip)
117+
115118
return {
116119
"source": entry["source"],
117120
"description": entry["description"],
118-
"iplist": IPMatcher(entry["ips"]),
121+
"iplist": iplist,
119122
}

aikido_zen/background_process/service_config_test.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22
from .service_config import ServiceConfig
3-
from aikido_zen.helpers.ip_matcher import IPMatcher
3+
from aikido_zen.helpers.iplist import IPList
44

55

66
def test_service_config_initialization():
@@ -66,10 +66,10 @@ def test_service_config_initialization():
6666
assert service_config.endpoints[1]["route"] == "/v3"
6767
assert service_config.endpoints[2]["route"] == "/admin"
6868
assert service_config.last_updated_at == last_updated_at
69-
assert isinstance(service_config.bypassed_ips, IPMatcher)
70-
assert service_config.bypassed_ips.has("127.0.0.1")
71-
assert service_config.bypassed_ips.has("123.1.2.2")
72-
assert not service_config.bypassed_ips.has("1.1.1.1")
69+
assert isinstance(service_config.bypassed_ips, IPList)
70+
assert service_config.bypassed_ips.matches("127.0.0.1")
71+
assert service_config.bypassed_ips.matches("123.1.2.2")
72+
assert not service_config.bypassed_ips.matches("1.1.1.1")
7373
assert service_config.blocked_uids == set(["1", "0", "5"])
7474

7575
v1_endpoint = service_config.get_endpoints(
@@ -90,10 +90,10 @@ def test_service_config_initialization():
9090
}
9191
)[0]
9292
assert admin_endpoint["route"] == "/admin"
93-
assert isinstance(admin_endpoint["allowedIPAddresses"], IPMatcher)
94-
assert admin_endpoint["allowedIPAddresses"].has("192.168.2.1")
95-
assert admin_endpoint["allowedIPAddresses"].has("1.2.3.4")
96-
assert not admin_endpoint["allowedIPAddresses"].has("192.168.0.1")
93+
assert isinstance(admin_endpoint["allowedIPAddresses"], IPList)
94+
assert admin_endpoint["allowedIPAddresses"].matches("192.168.2.1")
95+
assert admin_endpoint["allowedIPAddresses"].matches("1.2.3.4")
96+
assert not admin_endpoint["allowedIPAddresses"].matches("192.168.0.1")
9797

9898

9999
# Sample data for testing
@@ -118,7 +118,7 @@ def service_config():
118118
def test_initialization(service_config):
119119
assert len(service_config.endpoints) == 2 # Only non-graphql endpoints
120120
assert service_config.last_updated_at == "2023-10-01T00:00:00Z"
121-
assert isinstance(service_config.bypassed_ips, IPMatcher)
121+
assert isinstance(service_config.bypassed_ips, IPList)
122122
assert service_config.blocked_uids == {"user1", "user2"}
123123

124124

@@ -194,10 +194,10 @@ def test_service_config_with_empty_allowlist():
194194
assert len(service_config.endpoints) == 1
195195
assert service_config.endpoints[0]["route"] == "/admin"
196196
assert service_config.last_updated_at == last_updated_at
197-
assert isinstance(service_config.bypassed_ips, IPMatcher)
198-
assert service_config.bypassed_ips.has("127.0.0.1")
199-
assert service_config.bypassed_ips.has("123.1.2.2")
200-
assert not service_config.bypassed_ips.has("1.1.1.1")
197+
assert isinstance(service_config.bypassed_ips, IPList)
198+
assert service_config.bypassed_ips.matches("127.0.0.1")
199+
assert service_config.bypassed_ips.matches("123.1.2.2")
200+
assert not service_config.bypassed_ips.matches("1.1.1.1")
201201
assert service_config.blocked_uids == set(["1", "0", "5"])
202202

203203
admin_endpoint = service_config.get_endpoints(

aikido_zen/helpers/iplist.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Exports blocklist class and get_ip_address_type"""
2+
3+
import ipaddress
4+
5+
6+
def get_ip_address_type(ip):
7+
"""Returns type, ipv4 or ipv6"""
8+
try:
9+
if ipaddress.ip_address(ip).version == 4:
10+
return "ipv4"
11+
elif ipaddress.ip_address(ip).version == 6:
12+
return "ipv6"
13+
except ValueError:
14+
return None
15+
16+
17+
class IPList:
18+
"""A blocklist, where you can add subnets and addresses"""
19+
20+
def __init__(self, ip_list=None):
21+
"""Initializes the IPList, optionally with a list of IPs or CIDRs"""
22+
self.blocked_addresses = set()
23+
self.blocked_subnets = []
24+
25+
if ip_list:
26+
for ip_or_cidr in ip_list:
27+
self.add(ip_or_cidr)
28+
29+
def add_address(self, ip: str, ip_type: str):
30+
self.blocked_addresses.add((ip, ip_type))
31+
32+
def add_subnet(self, plain_ip: str, ip_range: int, ip_type: str):
33+
self.blocked_subnets.append((plain_ip, ip_range, ip_type))
34+
35+
def add(self, ip_or_cidr):
36+
"""
37+
Checks whether ip_or_cidr is an IP address or is a subnet, and decides the correct IP type (IPv4 or IPv6)
38+
"""
39+
if "/" not in ip_or_cidr: # IP Address
40+
ip_type = get_ip_address_type(ip_or_cidr)
41+
if ip_type:
42+
self.add_address(ip_or_cidr, ip_type)
43+
else: # Subnet
44+
plain_ip, range_str = ip_or_cidr.split("/")
45+
try:
46+
ip_range = int(range_str)
47+
except ValueError:
48+
return
49+
ip_type = get_ip_address_type(plain_ip)
50+
if not ip_type:
51+
return
52+
53+
self.add_subnet(plain_ip, ip_range, ip_type)
54+
55+
def matches(self, ip: str) -> bool:
56+
ip_type = get_ip_address_type(ip)
57+
if not ip_type:
58+
return False
59+
60+
# Check if the IP address is in the blocked addresses
61+
if (ip, ip_type) in self.blocked_addresses:
62+
return True
63+
64+
# Check if the IP address is in any of the blocked subnets
65+
ip_addr = ipaddress.ip_address(ip)
66+
for plain_ip, ip_range, _ in self.blocked_subnets:
67+
subnet = ipaddress.ip_network(f"{plain_ip}/{ip_range}", strict=False)
68+
if ip_addr in subnet:
69+
return True
70+
71+
return False

aikido_zen/helpers/iplist_test.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import pytest
2+
from .iplist import IPList, get_ip_address_type
3+
4+
5+
def test_get_ip_address_type():
6+
# Valid IPv4 addresses
7+
assert get_ip_address_type("192.168.1.1") == "ipv4"
8+
assert get_ip_address_type("255.255.255.255") == "ipv4"
9+
assert get_ip_address_type("0.0.0.0") == "ipv4"
10+
11+
# Valid IPv6 addresses
12+
assert get_ip_address_type("::1") == "ipv6"
13+
assert get_ip_address_type("2001:0db8:85a3:0000:0000:8a2e:0370:7334") == "ipv6"
14+
assert get_ip_address_type("::") == "ipv6"
15+
16+
# Invalid IP addresses
17+
assert get_ip_address_type("invalid_ip") is None
18+
assert get_ip_address_type("") is None
19+
assert get_ip_address_type("192.168.1.256") is None # Invalid octet
20+
assert (
21+
get_ip_address_type("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1234") is None
22+
) # Too many segments
23+
24+
25+
def test_blocklist_add_address():
26+
blocklist = IPList()
27+
assert blocklist.matches("192.168.1.1") is False
28+
blocklist.add_address("192.168.1.1", "ipv4")
29+
assert blocklist.matches("192.168.1.1") is True
30+
31+
# Test adding the same address again
32+
blocklist.add_address("192.168.1.1", "ipv4")
33+
assert len(blocklist.blocked_addresses) == 1 # Should still be one entry
34+
35+
36+
def test_blocklist_add_subnet():
37+
blocklist = IPList()
38+
assert blocklist.matches("10.0.0.1") is False
39+
blocklist.add_subnet("10.0.0.0", 8, "ipv4")
40+
assert blocklist.matches("10.0.0.1") is True
41+
assert blocklist.matches("10.1.1.1") is True
42+
assert blocklist.matches("192.168.1.1") is False
43+
44+
45+
def test_blocklist_multiple_addresses_and_subnets():
46+
blocklist = IPList()
47+
blocklist.add_address("192.168.1.1", "ipv4")
48+
blocklist.add_subnet("10.0.0.0", 8, "ipv4")
49+
50+
assert blocklist.matches("192.168.1.1") is True
51+
assert blocklist.matches("10.0.0.1") is True
52+
assert blocklist.matches("10.1.1.1") is True
53+
assert blocklist.matches("172.16.0.1") is False
54+
55+
56+
def test_blocklist_invalid_ip():
57+
blocklist = IPList()
58+
blocklist.add_address("192.168.1.1", "ipv4")
59+
assert blocklist.matches("invalid_ip") is False
60+
61+
62+
def test_blocklist_subnet_edge_cases():
63+
blocklist = IPList()
64+
blocklist.add_subnet("192.168.1.0", 24, "ipv4")
65+
66+
assert blocklist.matches("192.168.1.255") is True # Last address in the subnet
67+
assert blocklist.matches("192.168.1.0") is True # First address in the subnet
68+
assert blocklist.matches("192.168.2.1") is False # Outside the subnet
69+
assert blocklist.matches("192.168.1.128") is True # Middle of the subnet
70+
71+
72+
def test_blocklist_ipv6():
73+
blocklist = IPList()
74+
blocklist.add_address("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "ipv6")
75+
assert blocklist.matches("2001:0db8:85a3:0000:0000:8a2e:0370:7334") is True
76+
assert blocklist.matches("::1") is False # Not in the blocklist
77+
78+
blocklist.add_subnet("2001:0db8::", 32, "ipv6")
79+
assert blocklist.matches("2001:0db8:abcd:0012:0000:0000:0000:0001") is True
80+
assert blocklist.matches("2001:0db9::") is False # Outside the subnet
81+
82+
83+
def test_blocklist_overlapping_subnets():
84+
blocklist = IPList()
85+
blocklist.add_subnet(
86+
"192.168.1.0", 24, "ipv4"
87+
) # Covers 192.168.1.0 to 192.168.1.255
88+
blocklist.add_subnet(
89+
"192.168.1.128", 25, "ipv4"
90+
) # Covers 192.168.1.128 to 192.168.1.255
91+
92+
assert blocklist.matches("192.168.1.130") is True # Inside both subnets
93+
assert blocklist.matches("192.168.1.0") is True # Inside first subnet
94+
assert (
95+
blocklist.matches("192.168.1.127") is True
96+
) # Inside first subnet, outside second
97+
assert blocklist.matches("192.168.1.255") is True # Last address in both subnets
98+
assert blocklist.matches("192.168.2.1") is False # Outside both subnets
99+
100+
101+
def test_blocklist_mixed_address_types():
102+
blocklist = IPList()
103+
blocklist.add_address("192.168.1.1", "ipv4")
104+
blocklist.add_address("2001:0db8:85a3:0000:0000:8a2e:0370:7334", "ipv6")
105+
106+
assert blocklist.matches("192.168.1.1") is True
107+
assert blocklist.matches("2001:0db8:85a3:0000:0000:8a2e:0370:7334") is True
108+
assert blocklist.matches("192.168.1.2") is False # Different IPv4
109+
assert (
110+
blocklist.matches("2001:0db8:85a3:0000:0000:8a2e:0370:7335") is False
111+
) # Different IPv6
112+
113+
114+
def test_blocklist_subnet_with_single_ip():
115+
blocklist = IPList()
116+
blocklist.add_subnet("192.168.1.1", 32, "ipv4") # Single IP subnet
117+
118+
assert blocklist.matches("192.168.1.1") is True
119+
assert blocklist.matches("192.168.1.2") is False # Outside the subnet
120+
121+
122+
# Tests add function
123+
def test_valid_ips():
124+
blocklist = IPList()
125+
126+
blocklist.add("1.2.3.4")
127+
assert blocklist.matches("1.2.3.4") is True
128+
129+
blocklist.add("fd00:ec2::254")
130+
assert blocklist.matches("fd00:ec2::254") is True
131+
132+
blocklist.add("192.168.2.1/24")
133+
assert blocklist.matches("192.168.2.1") is True
134+
assert blocklist.matches("192.168.2.240") is True
135+
136+
blocklist.add("fd00:124::1/64")
137+
assert blocklist.matches("fd00:124::1") is True
138+
assert blocklist.matches("fd00:124::f") is True
139+
assert blocklist.matches("fd00:124::ff13") is True
140+
141+
blocklist.add("fd00:f123::1/128")
142+
assert blocklist.matches("fd00:f123::1") is True
143+
144+
assert blocklist.matches("2.3.4.5") is False
145+
assert blocklist.matches("fd00:125::ff13") is False
146+
assert blocklist.matches("fd00:f123::2") is False
147+
148+
149+
def test_invalid_ips():
150+
blocklist = IPList()
151+
152+
blocklist.add("192.168.2.2.1/24")
153+
assert blocklist.matches("192.168.2.2.1") is False
154+
155+
blocklist.add("test")
156+
assert blocklist.matches("test") is False
157+
158+
blocklist.add("")
159+
assert blocklist.matches("") is False
160+
161+
blocklist.add("fd00:124::1/test")
162+
assert blocklist.matches("fd00:124::1") is False

aikido_zen/helpers/is_ip_allowed_by_allowlist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def is_ip_allowed_by_allowlist(service_config, ip):
1313
return True
1414

1515
for entry in service_config.allowed_ips:
16-
if entry["iplist"].has(ip):
16+
if entry["iplist"].matches(ip):
1717
# If the IP matches one of the lists the IP is allowed :
1818
return True
1919
return False

0 commit comments

Comments
 (0)