forked from MirrorHub/synapse
Allow adding random delay to push (#15516)
This is to discourage timing based profiling on the push gateways.
This commit is contained in:
parent
6aca4e7cb8
commit
4de271a7fc
5 changed files with 68 additions and 2 deletions
1
changelog.d/15516.feature
Normal file
1
changelog.d/15516.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Add a config option to delay push notifications by a random amount, to discourage time-based profiling.
|
|
@ -3442,6 +3442,9 @@ This option has a number of sub-options. They are as follows:
|
|||
user has unread messages in. Defaults to true, meaning push clients will see the number of
|
||||
rooms with unread messages in them. Set to false to instead send the number
|
||||
of unread messages.
|
||||
* `jitter_delay`: Delays push notifications by a random amount up to the given
|
||||
duration. Useful for mitigating timing attacks. Optional, defaults to no
|
||||
delay. _Added in Synapse 1.84.0._
|
||||
|
||||
Example configuration:
|
||||
```yaml
|
||||
|
@ -3449,6 +3452,7 @@ push:
|
|||
enabled: true
|
||||
include_content: false
|
||||
group_unread_count_by_room: false
|
||||
jitter_delay: "10s"
|
||||
```
|
||||
---
|
||||
## Rooms
|
||||
|
|
|
@ -42,11 +42,17 @@ class PushConfig(Config):
|
|||
|
||||
# Now check for the one in the 'email' section and honour it,
|
||||
# with a warning.
|
||||
push_config = config.get("email") or {}
|
||||
redact_content = push_config.get("redact_content")
|
||||
email_push_config = config.get("email") or {}
|
||||
redact_content = email_push_config.get("redact_content")
|
||||
if redact_content is not None:
|
||||
print(
|
||||
"The 'email.redact_content' option is deprecated: "
|
||||
"please set push.include_content instead"
|
||||
)
|
||||
self.push_include_content = not redact_content
|
||||
|
||||
# Whether to apply a random delay to outbound push.
|
||||
self.push_jitter_delay_ms = None
|
||||
push_jitter_delay = push_config.get("jitter_delay", None)
|
||||
if push_jitter_delay:
|
||||
self.push_jitter_delay_ms = self.parse_duration(push_jitter_delay)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
import random
|
||||
import urllib.parse
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
|
||||
|
@ -114,6 +115,8 @@ class HttpPusher(Pusher):
|
|||
)
|
||||
self._pusherpool = hs.get_pusherpool()
|
||||
|
||||
self.push_jitter_delay_ms = hs.config.push.push_jitter_delay_ms
|
||||
|
||||
self.data = pusher_config.data
|
||||
if self.data is None:
|
||||
raise PusherConfigException("'data' key can not be null for HTTP pusher")
|
||||
|
@ -327,6 +330,21 @@ class HttpPusher(Pusher):
|
|||
event = await self.store.get_event(push_action.event_id, allow_none=True)
|
||||
if event is None:
|
||||
return True # It's been redacted
|
||||
|
||||
# Check if we should delay sending out the notification by a random
|
||||
# amount.
|
||||
#
|
||||
# Note: we base the delay off of when the event was sent, rather than
|
||||
# now, to handle the case where we need to send out many notifications
|
||||
# at once. If we just slept the random amount each loop then the last
|
||||
# push notification in the set could be delayed by many times the max
|
||||
# delay.
|
||||
if self.push_jitter_delay_ms:
|
||||
delay_ms = random.randint(1, self.push_jitter_delay_ms)
|
||||
diff_ms = event.origin_server_ts + delay_ms - self.clock.time_msec()
|
||||
if diff_ms > 0:
|
||||
await self.clock.sleep(diff_ms / 1000)
|
||||
|
||||
rejected = await self.dispatch_push_event(event, tweaks, badge)
|
||||
if rejected is False:
|
||||
return False
|
||||
|
|
|
@ -962,3 +962,40 @@ class HTTPPusherTests(HomeserverTestCase):
|
|||
channel.json_body["pushers"][0]["org.matrix.msc3881.device_id"],
|
||||
lookup_result.device_id,
|
||||
)
|
||||
|
||||
@override_config({"push": {"jitter_delay": "10s"}})
|
||||
def test_jitter(self) -> None:
|
||||
"""Tests that enabling jitter actually delays sending push."""
|
||||
user_id, access_token = self._make_user_with_pusher("user")
|
||||
other_user_id, other_access_token = self._make_user_with_pusher("otheruser")
|
||||
|
||||
room = self.helper.create_room_as(user_id, tok=access_token)
|
||||
self.helper.join(room=room, user=other_user_id, tok=other_access_token)
|
||||
|
||||
# Send a message and check that it did not generate a push, as it should
|
||||
# be delayed.
|
||||
self.helper.send(room, body="Hi!", tok=other_access_token)
|
||||
self.assertEqual(len(self.push_attempts), 0)
|
||||
|
||||
# Now advance time past the max jitter, and assert the message was sent.
|
||||
self.reactor.advance(15)
|
||||
self.assertEqual(len(self.push_attempts), 1)
|
||||
|
||||
self.push_attempts[0][0].callback({})
|
||||
|
||||
# Now we send a bunch of messages and assert that they were all sent
|
||||
# within the 10s max delay.
|
||||
for _ in range(10):
|
||||
self.helper.send(room, body="Hi!", tok=other_access_token)
|
||||
|
||||
index = 1
|
||||
for _ in range(11):
|
||||
while len(self.push_attempts) > index:
|
||||
self.push_attempts[index][0].callback({})
|
||||
self.pump()
|
||||
index += 1
|
||||
|
||||
self.reactor.advance(1)
|
||||
self.pump()
|
||||
|
||||
self.assertEqual(len(self.push_attempts), 11)
|
||||
|
|
Loading…
Reference in a new issue