-
Notifications
You must be signed in to change notification settings - Fork 562
MSC4140: rate limit management against delay_id #19830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
a016de0
6526be9
33eca5e
f2ba5b3
9d9f542
97a65a8
dc3772e
239c6f4
410f4fa
0199f65
5e0f44a
f43bc62
efe144c
3745bd7
0ad97ef
863e586
c17cdf1
c50e040
881e33e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [MSC4140: Cancellable delayed events](https://github.com/matrix-org/matrix-spec-proposals/pull/4140): Rate limit the delayed event management endpoints based on the `delay_id`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -413,7 +413,7 @@ async def cancel(self, request: SynapseRequest, delay_id: str) -> None: | |
| NotFoundError: if no matching delayed event could be found. | ||
| """ | ||
| assert self._is_master | ||
| await self._mgmt_ratelimit(request) | ||
| await self._mgmt_ratelimit(request, delay_id) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this weaken the rate-limiting too much? Now that a user can spam requests, as long as they cycle through (effectively unlimited) delay IDs? I get the reasoning that an external service would end up getting rate-limited if it's managing lots of delay IDs for a bunch of users... but would it not make sense to just exclude such a service from rate-limiting? And finally, I get that lots of logged-out users on the same corporate VPN would end up getting rate-limited due to sharing one IP... but at this point, what is the rate-limit protecting against? Seems it's only stopping a buggy client from spamming the same endpoint accidentally?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The worst that such spammy requests would do is make the server look up from a database index (to check for the existence of the target delayed event). IMO this is an acceptable cost to allow for the kind of rate-limiting this PR is after, but if that's still too costly, we can consider another approach.
That's not a bad idea, but then the question is how Synapse would identify such an external service, given the "service" is currently just any arbitrary HTTP client that's been given some delay IDs.
Yes, be it a buggy or malicious client. It takes non-trivial work to reset a delayed event (past the initial quick check for the existence of the delayed event), so it's worth something to protect it with a rate limit.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It bears mentioning that the long-term solution to all of this is to have the external services be OAuth 2.0 clients, with a token that has scoped access to these management endpoints (detailed in MSC4140). Rate limits could then be applied per client, as the clients would be identifiable by their token. The idea of using |
||
| await make_deferred_yieldable(self._initialized_from_db) | ||
|
|
||
| next_send_ts = await self._store.cancel_delayed_event(delay_id) | ||
|
|
@@ -428,7 +428,7 @@ async def restart(self, request: SynapseRequest, delay_id: str) -> None: | |
| Raises: | ||
| NotFoundError: if no matching delayed event could be found. | ||
| """ | ||
| await self._mgmt_ratelimit(request) | ||
| await self._mgmt_ratelimit(request, delay_id) | ||
|
|
||
| # Note: We don't need to wait on `self._initialized_from_db` here as the | ||
| # events that deals with are already marked as processed. | ||
|
|
@@ -452,7 +452,7 @@ async def send(self, request: SynapseRequest, delay_id: str) -> None: | |
| NotFoundError: if no matching delayed event could be found. | ||
| """ | ||
| assert self._is_master | ||
| await self._mgmt_ratelimit(request) | ||
| await self._mgmt_ratelimit(request, delay_id) | ||
| await make_deferred_yieldable(self._initialized_from_db) | ||
|
|
||
| event, next_send_ts = await self._store.process_target_delayed_event(delay_id) | ||
|
|
@@ -462,18 +462,27 @@ async def send(self, request: SynapseRequest, delay_id: str) -> None: | |
|
|
||
| await self._send_event(event) | ||
|
|
||
| async def _mgmt_ratelimit(self, request: SynapseRequest) -> None: | ||
| async def _mgmt_ratelimit(self, request: SynapseRequest, delay_id: str) -> None: | ||
| """ | ||
| Ratelimit requests with the `_delayed_event_mgmt_ratelimiter` keyed on the | ||
| ID of the delayed event to be managed, and either the | ||
| user making the request, or the request's IP address if unauthed. | ||
| """ | ||
| # NOTE: it would be more accurate to run this in a transaction that also contains | ||
| # the action to be done with the delay_id, but the worst that can happen without it | ||
| # is the possibility of the delay_id disappearing after having checked for it here, | ||
| # which isn't terrible since it's used here only for applying a ratelimit. | ||
| await self._store.assert_delayed_event(delay_id) | ||
|
anoadragon453 marked this conversation as resolved.
|
||
|
|
||
| if self._auth.has_access_token(request): | ||
| requester = await self._auth.get_user_by_req(request) | ||
| key = None | ||
| requester_key = requester.user.to_string() | ||
| else: | ||
| requester = None | ||
| key = request.getClientAddress().host | ||
| await self._delayed_event_mgmt_ratelimiter.ratelimit(requester, key) | ||
| requester_key = request.getClientAddress().host | ||
| await self._delayed_event_mgmt_ratelimiter.ratelimit( | ||
| requester, (requester_key, delay_id) | ||
| ) | ||
|
|
||
| async def _send_on_timeout(self) -> None: | ||
| self._next_delayed_event_call = None | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.