Skip to content

Commit a762233

Browse files
committed
Merge branch 'main' into improved-wrapping-main
2 parents 9c93a37 + c11c101 commit a762233

23 files changed

Lines changed: 286 additions & 136 deletions

aikido_zen/background_process/cloud_connection_manager/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
""" This file simply exports the CloudConnectionManager class"""
22

3-
import time
4-
from aikido_zen.helpers.token import Token
5-
from aikido_zen.helpers.get_current_unixtime_ms import get_unixtime_ms
63
from aikido_zen.background_process.heartbeats import send_heartbeats_every_x_secs
74
from aikido_zen.background_process.routes import Routes
85
from aikido_zen.ratelimiting.rate_limiter import RateLimiter
@@ -11,7 +8,7 @@
118
from ..api.http_api import ReportingApiHTTP
129
from ..service_config import ServiceConfig
1310
from ..users import Users
14-
from ..hostnames import Hostnames
11+
from aikido_zen.storage.hostnames import Hostnames
1512
from ..realtime.start_polling_for_changes import start_polling_for_changes
1613
from ..statistics import Statistics
1714

aikido_zen/background_process/cloud_connection_manager/init_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from aikido_zen.background_process.api.http_api import ReportingApiHTTP
44
from aikido_zen.background_process.service_config import ServiceConfig
55
from aikido_zen.background_process.users import Users
6-
from aikido_zen.background_process.hostnames import Hostnames
6+
from aikido_zen.storage.hostnames import Hostnames
77
from aikido_zen.ratelimiting.rate_limiter import RateLimiter
88
from aikido_zen.background_process.statistics import Statistics
99
from . import CloudConnectionManager

aikido_zen/background_process/cloud_connection_manager/on_detected_attack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
"""Mainly exports on_detected_attack"""
22

33
import json
4+
45
from aikido_zen.helpers.get_current_unixtime_ms import get_unixtime_ms
56
from aikido_zen.helpers.logging import logger
67
from aikido_zen.helpers.limit_length_metadata import limit_length_metadata
7-
from aikido_zen.helpers.get_ua_from_context import get_ua_from_context
88
from aikido_zen.helpers.serialize_to_json import serialize_to_json
99

1010

@@ -31,7 +31,7 @@ def on_detected_attack(connection_manager, attack, context, blocked, stack):
3131
"method": context.method,
3232
"url": context.url,
3333
"ipAddress": context.remote_address,
34-
"userAgent": get_ua_from_context(context),
34+
"userAgent": context.get_user_agent(),
3535
"body": context.body,
3636
"headers": context.headers,
3737
"source": context.source,

aikido_zen/background_process/cloud_connection_manager/on_detected_attack_test.py

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
from unittest.mock import MagicMock, patch
33
from .on_detected_attack import on_detected_attack
4+
from ...context import Context
45

56

67
@pytest.fixture
@@ -16,16 +17,22 @@ def mock_connection_manager():
1617

1718
@pytest.fixture
1819
def mock_context():
19-
class Context:
20-
method = "POST"
21-
url = "http://example.com/api"
22-
remote_address = "192.168.1.1"
23-
body = "test body"
24-
headers = {"Content-Type": "application/json"}
25-
source = "test_source"
26-
route = "test_route"
20+
basic_wsgi_req = {
21+
"REQUEST_METHOD": "GET",
22+
"HTTP_HEADER_1": "header 1 value",
23+
"HTTP_HEADER_2": "Header 2 value",
24+
"RANDOM_VALUE": "Random value",
25+
"HTTP_COOKIE": "sessionId=abc123xyz456;",
26+
"wsgi.url_scheme": "http",
27+
"HTTP_HOST": "localhost:8080",
28+
"PATH_INFO": "/hello",
29+
"QUERY_STRING": "user=JohnDoe&age=30&age=35",
30+
"CONTENT_TYPE": "application/json",
31+
"REMOTE_ADDR": "198.51.100.23",
32+
"HTTP_USER_AGENT": "Mozilla/5.0",
33+
}
2734

28-
return Context()
35+
return Context(req=basic_wsgi_req, body=123, source="django")
2936

3037

3138
def test_on_detected_attack_no_token(mock_context):
@@ -114,3 +121,64 @@ def test_on_detected_attack_with_blocked_and_stack(
114121
assert attack["blocked"] is True
115122
assert attack["stack"] == stack
116123
assert mock_connection_manager.api.report.call_count == 1
124+
125+
126+
def test_on_detected_attack_request_data_and_attack_data(
127+
mock_connection_manager, mock_context
128+
):
129+
attack = {
130+
"payload": {"key": "value"},
131+
"metadata": {"test": "true"},
132+
}
133+
134+
on_detected_attack(
135+
mock_connection_manager, attack, mock_context, blocked=False, stack=None
136+
)
137+
138+
# Extract the call arguments for the report method
139+
_, event, _ = mock_connection_manager.api.report.call_args[0]
140+
141+
# Verify the request attribute in the payload
142+
request_data = event["request"]
143+
144+
assert request_data["method"] == "GET"
145+
assert request_data["url"] == "http://localhost:8080/hello"
146+
assert request_data["ipAddress"] == "198.51.100.23"
147+
assert request_data["body"] == 123
148+
assert request_data["headers"] == {
149+
"CONTENT_TYPE": "application/json",
150+
"USER_AGENT": "Mozilla/5.0",
151+
"COOKIE": "sessionId=abc123xyz456;",
152+
"HEADER_1": "header 1 value",
153+
"HEADER_2": "Header 2 value",
154+
"HOST": "localhost:8080",
155+
}
156+
assert request_data["source"] == "django"
157+
assert request_data["route"] == "/hello"
158+
assert request_data["userAgent"] == "Mozilla/5.0"
159+
160+
attack_data = event["attack"]
161+
assert attack_data["blocked"] == False
162+
assert attack_data["metadata"] == {"test": "true"}
163+
assert attack_data["payload"] == '{"key": "value"}'
164+
assert attack_data["stack"] is None
165+
assert attack_data["user"] is None
166+
167+
168+
def test_on_detected_attack_with_user(mock_connection_manager, mock_context):
169+
attack = {
170+
"payload": {"key": "value"},
171+
"metadata": {},
172+
}
173+
# Simulate a user in the context
174+
mock_context.user = "test_user"
175+
176+
on_detected_attack(
177+
mock_connection_manager, attack, mock_context, blocked=False, stack=None
178+
)
179+
180+
# Extract the call arguments for the report method
181+
_, event, _ = mock_connection_manager.api.report.call_args[0]
182+
183+
# Verify the user is included in the attack data
184+
assert event["attack"]["user"] == "test_user"

aikido_zen/background_process/commands/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from .user import process_user
77
from .should_ratelimit import process_should_ratelimit
88
from .kill import process_kill
9-
from .hostnames_add import process_hostnames_add
109
from .statistics import process_statistics
1110
from .ping import process_ping
1211
from .sync_data import process_sync_data
@@ -18,7 +17,6 @@
1817
"USER": (process_user, False),
1918
"KILL": (process_kill, False),
2019
"STATISTICS": (process_statistics, False),
21-
"HOSTNAMES_ADD": (process_hostnames_add, False),
2220
# Commands that return data :
2321
"SYNC_DATA": (process_sync_data, True),
2422
"READ_PROPERTY": (process_read_property, True),

aikido_zen/background_process/commands/hostnames_add.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

aikido_zen/background_process/commands/sync_data.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def process_sync_data(connection_manager, data, conn, queue=None):
88
"""
99
Synchronizes data between the thread-local cache (with a TTL of usually 1 minute) and the
1010
background thread. Which data gets synced?
11-
Thread -> BG Process : Hits, request statistics, api specs
11+
Thread -> BG Process : Hits, request statistics, api specs, hostnames
1212
BG Process -> Thread : Routes, endpoints, bypasssed ip's, blocked users
1313
"""
1414
routes = connection_manager.routes
@@ -31,6 +31,15 @@ def process_sync_data(connection_manager, data, conn, queue=None):
3131
# Save middleware installed :
3232
if data.get("middleware_installed", False):
3333
connection_manager.middleware_installed = True
34+
35+
# Sync hostnames
36+
for hostnames_entry in data.get("hostnames", list()):
37+
connection_manager.hostnames.add(
38+
hostnames_entry["hostname"],
39+
hostnames_entry["port"],
40+
hostnames_entry["hits"],
41+
)
42+
3443
if connection_manager.conf.last_updated_at > 0:
3544
# Only report data if the config has been fetched.
3645
return {

aikido_zen/background_process/commands/sync_data_test.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
from .sync_data import process_sync_data
66
from aikido_zen.background_process.routes import Routes
77
from aikido_zen.helpers.iplist import IPList
8+
from ...storage.hostnames import Hostnames
89

910

1011
@pytest.fixture
1112
def setup_connection_manager():
1213
"""Fixture to set up a mock connection manager."""
1314
connection_manager = MagicMock()
1415
connection_manager.routes = Routes()
16+
connection_manager.hostnames = Hostnames()
1517
connection_manager.conf.endpoints = ["endpoint1", "endpoint2"]
1618

1719
connection_manager.conf.bypassed_ips = IPList()
@@ -26,6 +28,10 @@ def setup_connection_manager():
2628
def test_process_sync_data_initialization(setup_connection_manager):
2729
"""Test the initialization of routes and hits update."""
2830
connection_manager = setup_connection_manager
31+
test_hostnames = Hostnames()
32+
test_hostnames.add("example2.com", 443, 15)
33+
test_hostnames.add("bumblebee.com", 8080)
34+
2935
data = {
3036
"current_routes": {
3137
"route1": {
@@ -43,6 +49,7 @@ def test_process_sync_data_initialization(setup_connection_manager):
4349
},
4450
"reqs": 10, # Total requests to be added
4551
"middleware_installed": False,
52+
"hostnames": test_hostnames.as_array(),
4653
}
4754

4855
result = process_sync_data(connection_manager, data, None)
@@ -69,6 +76,10 @@ def test_process_sync_data_initialization(setup_connection_manager):
6976
assert result["routes"] == dict(connection_manager.routes.routes)
7077
assert result["config"] == connection_manager.conf
7178
assert connection_manager.middleware_installed == False
79+
assert connection_manager.hostnames.as_array() == [
80+
{"hits": 15, "hostname": "example2.com", "port": 443},
81+
{"hits": 1, "hostname": "bumblebee.com", "port": 8080},
82+
]
7283

7384

7485
def test_process_sync_data_with_last_updated_at_below_zero(setup_connection_manager):
@@ -114,13 +125,22 @@ def test_process_sync_data_with_last_updated_at_below_zero(setup_connection_mana
114125
# Check that the total requests were updated
115126
assert connection_manager.statistics.requests["total"] == 10
116127
assert connection_manager.middleware_installed == True
128+
assert len(connection_manager.hostnames.as_array()) == 0
117129
# Check that the return value is correct
118130
assert result == {}
119131

120132

121-
def test_process_sync_data_existing_route(setup_connection_manager):
133+
def test_process_sync_data_existing_route_and_hostnames(setup_connection_manager):
122134
"""Test updating an existing route's hit count."""
123135
connection_manager = setup_connection_manager
136+
connection_manager.hostnames.add("example.com", 443, 200)
137+
connection_manager.hostnames.add("example.org", 443)
138+
connection_manager.hostnames.add("a.com", 443)
139+
140+
hostnames_sync = Hostnames()
141+
hostnames_sync.add("example.com", 443, 15)
142+
hostnames_sync.add("c.com", 443)
143+
124144
data = {
125145
"current_routes": {
126146
"route1": {
@@ -131,6 +151,7 @@ def test_process_sync_data_existing_route(setup_connection_manager):
131151
}
132152
},
133153
"reqs": 5, # Total requests to be added
154+
"hostnames": hostnames_sync.as_array(),
134155
}
135156

136157
# First call to initialize the route
@@ -162,6 +183,12 @@ def test_process_sync_data_existing_route(setup_connection_manager):
162183
# Check that the total requests were updated
163184
assert connection_manager.statistics.requests["total"] == 20 # 5 + 15
164185
assert connection_manager.middleware_installed == False
186+
assert connection_manager.hostnames.as_array() == [
187+
{"hits": 215, "hostname": "example.com", "port": 443},
188+
{"hits": 1, "hostname": "example.org", "port": 443},
189+
{"hits": 1, "hostname": "a.com", "port": 443},
190+
{"hits": 1, "hostname": "c.com", "port": 443},
191+
]
165192

166193
# Check that the return value is correct
167194
assert result["routes"] == dict(connection_manager.routes.routes)

aikido_zen/background_process/routes/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ def get(self, route_metadata):
5959
key = route_to_key(route_metadata)
6060
return self.routes.get(key)
6161

62+
def get_routes_with_hits(self):
63+
"""Gets you all routes with a positive hits delta"""
64+
result = dict()
65+
for key, route in self.routes.items():
66+
if route["hits_delta_since_sync"] <= 0:
67+
continue # do not add routes without a hit delta
68+
result[key] = route
69+
return result
70+
6271
def clear(self):
6372
"""Deletes all routes"""
6473
self.routes = {}

aikido_zen/helpers/get_ua_from_context.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)