fix: Don't attempt to contact origins as a notary more than once a minute

This commit is contained in:
timedout authored and Jade Ellis committed 2026-10-14 10:32:36 +01:00
1 parent a5a7905494
commit 39cd151ca3
3 files changed
+38 -8

No files matched your search

+6 -3
View File
@@ -177,10 +177,13 @@ pub(crate) async fn get_remote_server_keys_route(
body: Ruma<get_remote_server_keys::v2::Request>, body: Ruma<get_remote_server_keys::v2::Request>,
) -> Result<get_remote_server_keys::v2::Response> { ) -> Result<get_remote_server_keys::v2::Response> {
let min_valid_ts = body.minimum_valid_until_ts; let min_valid_ts = body.minimum_valid_until_ts;
if let Ok(response) = services if services
.server_keys .server_keys
.origin_request(body.server_name.clone(), min_valid_ts) .notary_may_contact_origin(&body.server_name)
.await && let Ok(response) = services
.server_keys
.origin_request(body.server_name.clone(), min_valid_ts)
.await
{ {
return sign_ssk(&services, response, &body.server_name) return sign_ssk(&services, response, &body.server_name)
.await .await
+21 -4
View File
@@ -5,18 +5,22 @@ mod sign;
mod util; mod util;
mod verify; mod verify;
use std::{collections::BTreeMap, sync::Arc}; use std::{
collections::{BTreeMap, HashMap},
sync::Arc,
time::{Duration, Instant},
};
use conduwuit::{ use conduwuit::{
Result, Server, Result, Server, SyncRwLock,
utils::{IterStream, ReadyExt, stream::TryIgnore}, utils::{IterStream, ReadyExt, stream::TryIgnore},
}; };
use database::{Deserialized, Ignore, Interfix, Json, Map}; use database::{Deserialized, Ignore, Interfix, Json, Map};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
pub use request::in_one_week; pub use request::in_one_week;
use ruma::{ use ruma::{
CanonicalJsonObject, MilliSecondsSinceUnixEpoch, OwnedServerSigningKeyId, ServerName, CanonicalJsonObject, MilliSecondsSinceUnixEpoch, OwnedServerName, OwnedServerSigningKeyId,
ServerSigningKeyId, ServerName, ServerSigningKeyId,
api::federation::discovery::{ServerSigningKeys, VerifyKey}, api::federation::discovery::{ServerSigningKeys, VerifyKey},
room_version_rules::RoomVersionRules, room_version_rules::RoomVersionRules,
signatures::{Ed25519KeyPair, PublicKeyMap, PublicKeySet}, signatures::{Ed25519KeyPair, PublicKeyMap, PublicKeySet},
@@ -30,6 +34,7 @@ pub struct Service {
verify_keys: VerifyKeys, verify_keys: VerifyKeys,
services: Services, services: Services,
db: Data, db: Data,
last_lookup: SyncRwLock<HashMap<OwnedServerName, Instant>>,
} }
struct Services { struct Services {
@@ -62,6 +67,7 @@ impl crate::Service for Service {
db: Data { db: Data {
servernamekeyid_response: args.db["servernamekeyid_response"].clone(), servernamekeyid_response: args.db["servernamekeyid_response"].clone(),
}, },
last_lookup: SyncRwLock::new(HashMap::new()),
})) }))
} }
@@ -194,4 +200,15 @@ impl Service {
.ignore_err() .ignore_err()
.map(|(_, v): (Ignore, ServerSigningKeys)| v) .map(|(_, v): (Ignore, ServerSigningKeys)| v)
} }
/// Determines if the server may contact the origin server to fetch keys
/// when acting as a notary server. This limits origin lookups to once per
/// minute, which prevents amplification attacks.
#[must_use]
pub fn notary_may_contact_origin(&self, server_name: &ServerName) -> bool {
self.last_lookup
.read()
.get(server_name)
.is_none_or(|last| last.elapsed() >= Duration::from_mins(1))
}
} }
+11 -1
View File
@@ -1,4 +1,4 @@
use std::collections::BTreeMap; use std::{collections::BTreeMap, time::Instant};
use conduwuit::{Err, Result, trace, utils::millis_since_unix_epoch, warn}; use conduwuit::{Err, Result, trace, utils::millis_since_unix_epoch, warn};
use ruma::{ use ruma::{
@@ -30,6 +30,16 @@ impl super::Service {
) -> Result<ServerSigningKeys> { ) -> Result<ServerSigningKeys> {
use get_server_keys::v2::Request; use get_server_keys::v2::Request;
// N.B. The "last lookup" is written before the request is actually made
// to prevent concurrent notary requests from spawning... concurrent
// origin requests, especially if the origin is slow.
// This has the downside that, if the origin is temporarily unreachable
// (including if we're backing off from it), the notary might take an
// additional minute to recover compared to the rest of the server. This
// is deemed acceptable.
self.last_lookup
.write()
.insert(target.clone(), Instant::now());
let server_signing_key = self let server_signing_key = self
.services .services
.sending .sending