Add configurable rate limiting for the creation of rooms. (#18514)
Default values will be 1 room per minute, with a burst count of 10. It's hard to imagine most users will be affected by this default rate, but it's intentionally non-invasive in case of bots or other users that need to create rooms at a large rate. Server admins might want to down-tune this on their deployments. --------- Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
This commit is contained in:
1
changelog.d/18514.feature
Normal file
1
changelog.d/18514.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add configurable rate limiting for the creation of rooms.
|
||||
@@ -98,6 +98,10 @@ rc_delayed_event_mgmt:
|
||||
per_second: 9999
|
||||
burst_count: 9999
|
||||
|
||||
rc_room_creation:
|
||||
per_second: 9999
|
||||
burst_count: 9999
|
||||
|
||||
federation_rr_transactions_per_room_per_second: 9999
|
||||
|
||||
allow_device_name_lookup_over_federation: true
|
||||
|
||||
@@ -1996,6 +1996,31 @@ rc_reports:
|
||||
burst_count: 20.0
|
||||
```
|
||||
---
|
||||
### `rc_room_creation`
|
||||
|
||||
*(object)* Sets rate limits for how often users are able to create rooms.
|
||||
|
||||
This setting has the following sub-options:
|
||||
|
||||
* `per_second` (number): Maximum number of requests a client can send per second.
|
||||
|
||||
* `burst_count` (number): Maximum number of requests a client can send before being throttled.
|
||||
|
||||
Default configuration:
|
||||
```yaml
|
||||
rc_room_creation:
|
||||
per_user:
|
||||
per_second: 0.016
|
||||
burst_count: 10.0
|
||||
```
|
||||
|
||||
Example configuration:
|
||||
```yaml
|
||||
rc_room_creation:
|
||||
per_second: 1.0
|
||||
burst_count: 5.0
|
||||
```
|
||||
---
|
||||
### `federation_rr_transactions_per_room_per_second`
|
||||
|
||||
*(integer)* Sets outgoing federation transaction frequency for sending read-receipts, per-room.
|
||||
|
||||
@@ -2228,6 +2228,17 @@ properties:
|
||||
examples:
|
||||
- per_second: 2.0
|
||||
burst_count: 20.0
|
||||
rc_room_creation:
|
||||
$ref: "#/$defs/rc"
|
||||
description: >-
|
||||
Sets rate limits for how often users are able to create rooms.
|
||||
default:
|
||||
per_user:
|
||||
per_second: 0.016
|
||||
burst_count: 10.0
|
||||
examples:
|
||||
- per_second: 1.0
|
||||
burst_count: 5.0
|
||||
federation_rr_transactions_per_room_per_second:
|
||||
type: integer
|
||||
description: >-
|
||||
|
||||
@@ -241,6 +241,12 @@ class RatelimitConfig(Config):
|
||||
defaults={"per_second": 1, "burst_count": 5},
|
||||
)
|
||||
|
||||
self.rc_room_creation = RatelimitSettings.parse(
|
||||
config,
|
||||
"rc_room_creation",
|
||||
defaults={"per_second": 0.016, "burst_count": 10},
|
||||
)
|
||||
|
||||
self.rc_reports = RatelimitSettings.parse(
|
||||
config,
|
||||
"rc_reports",
|
||||
|
||||
@@ -66,6 +66,7 @@ from synapse.api.errors import (
|
||||
SynapseError,
|
||||
)
|
||||
from synapse.api.filtering import Filter
|
||||
from synapse.api.ratelimiting import Ratelimiter
|
||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS, RoomVersion
|
||||
from synapse.event_auth import validate_event_for_room_version
|
||||
from synapse.events import EventBase
|
||||
@@ -131,7 +132,12 @@ class RoomCreationHandler:
|
||||
self.room_member_handler = hs.get_room_member_handler()
|
||||
self._event_auth_handler = hs.get_event_auth_handler()
|
||||
self.config = hs.config
|
||||
self.request_ratelimiter = hs.get_request_ratelimiter()
|
||||
self.common_request_ratelimiter = hs.get_request_ratelimiter()
|
||||
self.creation_ratelimiter = Ratelimiter(
|
||||
store=self.store,
|
||||
clock=self.clock,
|
||||
cfg=self.config.ratelimiting.rc_room_creation,
|
||||
)
|
||||
|
||||
# Room state based off defined presets
|
||||
self._presets_dict: Dict[str, Dict[str, Any]] = {
|
||||
@@ -203,7 +209,11 @@ class RoomCreationHandler:
|
||||
Raises:
|
||||
ShadowBanError if the requester is shadow-banned.
|
||||
"""
|
||||
await self.request_ratelimiter.ratelimit(requester)
|
||||
await self.creation_ratelimiter.ratelimit(requester, update=False)
|
||||
|
||||
# then apply the ratelimits
|
||||
await self.common_request_ratelimiter.ratelimit(requester)
|
||||
await self.creation_ratelimiter.ratelimit(requester)
|
||||
|
||||
user_id = requester.user.to_string()
|
||||
|
||||
@@ -809,11 +819,23 @@ class RoomCreationHandler:
|
||||
)
|
||||
|
||||
if ratelimit:
|
||||
# Rate limit once in advance, but don't rate limit the individual
|
||||
# events in the room — room creation isn't atomic and it's very
|
||||
# janky if half the events in the initial state don't make it because
|
||||
# of rate limiting.
|
||||
await self.request_ratelimiter.ratelimit(requester)
|
||||
# Limit the rate of room creations,
|
||||
# using both the limiter specific to room creations as well
|
||||
# as the general request ratelimiter.
|
||||
#
|
||||
# Note that we don't rate limit the individual
|
||||
# events in the room — room creation isn't atomic and
|
||||
# historically it was very janky if half the events in the
|
||||
# initial state don't make it because of rate limiting.
|
||||
|
||||
# First check the room creation ratelimiter without updating it
|
||||
# (this is so we don't consume a token if the other ratelimiter doesn't
|
||||
# allow us to proceed)
|
||||
await self.creation_ratelimiter.ratelimit(requester, update=False)
|
||||
|
||||
# then apply the ratelimits
|
||||
await self.common_request_ratelimiter.ratelimit(requester)
|
||||
await self.creation_ratelimiter.ratelimit(requester)
|
||||
|
||||
room_version_id = config.get(
|
||||
"room_version", self.config.server.default_room_version.identifier
|
||||
|
||||
@@ -45,6 +45,7 @@ from synapse.types import JsonDict, UserID, create_requester
|
||||
from synapse.util import Clock
|
||||
|
||||
from tests import unittest
|
||||
from tests.unittest import override_config
|
||||
|
||||
|
||||
def _create_event(
|
||||
@@ -245,6 +246,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||
)
|
||||
self._assert_hierarchy(result, expected)
|
||||
|
||||
@override_config({"rc_room_creation": {"burst_count": 1000, "per_second": 1}})
|
||||
def test_large_space(self) -> None:
|
||||
"""Test a space with a large number of rooms."""
|
||||
rooms = [self.room]
|
||||
@@ -527,6 +529,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||
)
|
||||
self._assert_hierarchy(result, expected)
|
||||
|
||||
@override_config({"rc_room_creation": {"burst_count": 1000, "per_second": 1}})
|
||||
def test_pagination(self) -> None:
|
||||
"""Test simple pagination works."""
|
||||
room_ids = []
|
||||
@@ -564,6 +567,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||
self._assert_hierarchy(result, expected)
|
||||
self.assertNotIn("next_batch", result)
|
||||
|
||||
@override_config({"rc_room_creation": {"burst_count": 1000, "per_second": 1}})
|
||||
def test_invalid_pagination_token(self) -> None:
|
||||
"""An invalid pagination token, or changing other parameters, shoudl be rejected."""
|
||||
room_ids = []
|
||||
@@ -615,6 +619,7 @@ class SpaceSummaryTestCase(unittest.HomeserverTestCase):
|
||||
SynapseError,
|
||||
)
|
||||
|
||||
@override_config({"rc_room_creation": {"burst_count": 1000, "per_second": 1}})
|
||||
def test_max_depth(self) -> None:
|
||||
"""Create a deep tree to test the max depth against."""
|
||||
spaces = [self.space]
|
||||
|
||||
Reference in New Issue
Block a user