Skip to content

Commit 52636ef

Browse files
committed
Merge branch '3078-rate-limit-users' into develop
Issue #3078 PR #3220
2 parents c5616b0 + 91d177a commit 52636ef

File tree

3 files changed

+67
-7
lines changed

3 files changed

+67
-7
lines changed

envs/monkey_zoo/blackbox/test_blackbox.py

+62
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from http import HTTPStatus
55
from threading import Thread
66
from time import sleep
7+
from typing import List
78
from uuid import uuid4
89

910
import pytest
@@ -205,6 +206,67 @@ def make_request(monkey_island_requests, request_callback):
205206
assert response_codes.count(HTTPStatus.TOO_MANY_REQUESTS) == 1
206207

207208

209+
RATE_LIMIT_AGENT1_ID = uuid4()
210+
RATE_LIMIT_AGENT2_ID = uuid4()
211+
212+
213+
@pytest.mark.parametrize(
214+
"request_callback, successful_request_status, max_requests_per_second",
215+
[
216+
(lambda mir: mir.get(GET_AGENT_OTP_ENDPOINT), HTTPStatus.OK, MAX_OTP_REQUESTS_PER_SECOND),
217+
],
218+
)
219+
def test_rate_limit__agent_user(
220+
island,
221+
monkey_island_requests,
222+
request_callback,
223+
successful_request_status,
224+
max_requests_per_second,
225+
):
226+
monkey_island_requests.login()
227+
response = monkey_island_requests.get(GET_AGENT_OTP_ENDPOINT)
228+
otp1 = response.json()["otp"]
229+
response = monkey_island_requests.get(GET_AGENT_OTP_ENDPOINT)
230+
otp2 = response.json()["otp"]
231+
232+
agent1_requests = AgentRequests(island, RATE_LIMIT_AGENT1_ID, OTP(otp1))
233+
agent1_requests.login()
234+
agent2_requests = AgentRequests(island, RATE_LIMIT_AGENT2_ID, OTP(otp2))
235+
agent2_requests.login()
236+
237+
threads = []
238+
response_codes1: List[int] = []
239+
response_codes2: List[int] = []
240+
241+
def make_request(agent_requests, request_callback, response_codes):
242+
response = request_callback(agent_requests)
243+
response_codes.append(response.status_code)
244+
245+
for _ in range(0, max_requests_per_second + 1):
246+
t1 = Thread(
247+
target=make_request,
248+
args=(agent1_requests, request_callback, response_codes1),
249+
daemon=True,
250+
)
251+
t1.start()
252+
t2 = Thread(
253+
target=make_request,
254+
args=(agent2_requests, request_callback, response_codes2),
255+
daemon=True,
256+
)
257+
t2.start()
258+
threads.append(t1)
259+
threads.append(t2)
260+
261+
for t in threads:
262+
t.join()
263+
264+
assert response_codes1.count(successful_request_status) == max_requests_per_second
265+
assert response_codes1.count(HTTPStatus.TOO_MANY_REQUESTS) == 1
266+
assert response_codes2.count(successful_request_status) == max_requests_per_second
267+
assert response_codes2.count(HTTPStatus.TOO_MANY_REQUESTS) == 1
268+
269+
208270
def test_refresh_access_token(monkey_island_requests):
209271
monkey_island_requests.login()
210272
original_token = monkey_island_requests.token

monkey/monkey_island/cc/services/authentication_service/flask_resources/agent_otp.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from flask import make_response
55
from flask_limiter import Limiter, RateLimitExceeded
6-
from flask_limiter.util import get_remote_address
6+
from flask_login import current_user
77
from flask_security import auth_token_required, roles_accepted
88

99
from monkey_island.cc.flask_utils import AbstractResource
@@ -31,15 +31,14 @@ def __init__(self, otp_generator: IOTPGenerator, limiter: Limiter):
3131
# we need to ensure that a single instance of the limiter is used. Hence
3232
# the class variable.
3333
#
34-
# TODO: The limit is currently applied per IP address. We will want to change
35-
# it to per-user, per-IP once we require authentication for this endpoint.
3634
# Note that we do not want to limit to just per-user, otherwise this endpoint could be used
37-
# to enumerate users/tokens.
35+
# to enumerate users/tokens. This should already be captured by the role-based access
36+
# control.
3837
with AgentOTP.lock:
3938
if AgentOTP.limiter is None:
4039
AgentOTP.limiter = limiter.limit(
4140
f"{MAX_OTP_REQUESTS_PER_SECOND}/second",
42-
key_func=get_remote_address,
41+
key_func=lambda: current_user.username,
4342
per_method=True,
4443
)
4544

monkey/monkey_island/cc/services/authentication_service/flask_resources/refresh_authentication_token.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from flask import make_response
66
from flask_limiter import Limiter, RateLimitExceeded
7-
from flask_limiter.util import get_remote_address
87
from flask_login import current_user
98
from flask_security import auth_token_required
109

@@ -38,7 +37,7 @@ def __init__(self, authentication_facade: AuthenticationFacade, limiter: Limiter
3837
if RefreshAuthenticationToken.limiter is None:
3938
RefreshAuthenticationToken.limiter = limiter.limit(
4039
f"{MAX_REFRESH_AUTHENTICATION_TOKEN_REQUESTS_PER_SECOND}/second",
41-
key_func=get_remote_address,
40+
key_func=lambda: current_user.username,
4241
per_method=True,
4342
)
4443

0 commit comments

Comments
 (0)