improvement: federation get_keys and optimize signingkey storage
- get encryption keys over federation - optimize signing key storage - rate limit parsing of bad events - rate limit signature fetching - dependency bumps
This commit is contained in:
parent
ae41bc5067
commit
09157b2096
18 changed files with 566 additions and 371 deletions
|
@ -1,7 +1,10 @@
|
|||
use crate::{client_server, utils, ConduitResult, Database, Error, PduEvent, Result, Ruma};
|
||||
use crate::{
|
||||
client_server::{self, get_keys_helper},
|
||||
utils, ConduitResult, Database, Error, PduEvent, Result, Ruma,
|
||||
};
|
||||
use get_profile_information::v1::ProfileField;
|
||||
use http::header::{HeaderValue, AUTHORIZATION, HOST};
|
||||
use log::{debug, error, info, warn};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use regex::Regex;
|
||||
use rocket::{response::content::Json, State};
|
||||
use ruma::{
|
||||
|
@ -15,6 +18,7 @@ use ruma::{
|
|||
VerifyKey,
|
||||
},
|
||||
event::{get_event, get_missing_events, get_room_state_ids},
|
||||
keys::get_keys,
|
||||
membership::{
|
||||
create_invite,
|
||||
create_join_event::{self, RoomState},
|
||||
|
@ -32,12 +36,14 @@ use ruma::{
|
|||
create::CreateEventContent,
|
||||
member::{MemberEventContent, MembershipState},
|
||||
},
|
||||
AnyEphemeralRoomEvent, AnyEvent as EduEvent, EventType,
|
||||
AnyEphemeralRoomEvent, EventType,
|
||||
},
|
||||
receipt::ReceiptType,
|
||||
serde::Raw,
|
||||
signatures::{CanonicalJsonObject, CanonicalJsonValue},
|
||||
state_res::{self, Event, EventMap, RoomVersion, StateMap},
|
||||
uint, EventId, RoomId, RoomVersionId, ServerName, ServerSigningKeyId, UserId,
|
||||
uint, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, ServerName,
|
||||
ServerSigningKeyId, UserId,
|
||||
};
|
||||
use std::{
|
||||
collections::{btree_map::Entry, BTreeMap, BTreeSet, HashSet},
|
||||
|
@ -49,8 +55,9 @@ use std::{
|
|||
pin::Pin,
|
||||
result::Result as StdResult,
|
||||
sync::{Arc, RwLock},
|
||||
time::{Duration, SystemTime},
|
||||
time::{Duration, Instant, SystemTime},
|
||||
};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
#[cfg(feature = "conduit_bin")]
|
||||
use rocket::{get, post, put};
|
||||
|
@ -452,7 +459,10 @@ pub fn get_server_keys_route(db: State<'_, Database>) -> Json<String> {
|
|||
verify_keys,
|
||||
old_verify_keys: BTreeMap::new(),
|
||||
signatures: BTreeMap::new(),
|
||||
valid_until_ts: SystemTime::now() + Duration::from_secs(60 * 2),
|
||||
valid_until_ts: MilliSecondsSinceUnixEpoch::from_system_time(
|
||||
SystemTime::now() + Duration::from_secs(60 * 2),
|
||||
)
|
||||
.expect("time is valid"),
|
||||
},
|
||||
}
|
||||
.try_into_http_response::<Vec<u8>>()
|
||||
|
@ -608,6 +618,7 @@ pub async fn send_transaction_message_route<'a>(
|
|||
}
|
||||
};
|
||||
|
||||
let start_time = Instant::now();
|
||||
if let Err(e) = handle_incoming_pdu(
|
||||
&body.origin,
|
||||
&event_id,
|
||||
|
@ -619,7 +630,17 @@ pub async fn send_transaction_message_route<'a>(
|
|||
)
|
||||
.await
|
||||
{
|
||||
resolved_map.insert(event_id, Err(e));
|
||||
resolved_map.insert(event_id.clone(), Err(e));
|
||||
}
|
||||
|
||||
let elapsed = start_time.elapsed();
|
||||
if elapsed > Duration::from_secs(1) {
|
||||
warn!(
|
||||
"Handling event {} took {}m{}s",
|
||||
event_id,
|
||||
elapsed.as_secs() / 60,
|
||||
elapsed.as_secs() % 60
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -653,19 +674,16 @@ pub async fn send_transaction_message_route<'a>(
|
|||
let mut user_receipts = BTreeMap::new();
|
||||
user_receipts.insert(user_id.clone(), user_updates.data);
|
||||
|
||||
let mut receipt_content = BTreeMap::new();
|
||||
receipt_content.insert(
|
||||
event_id.to_owned(),
|
||||
ruma::events::receipt::Receipts {
|
||||
read: Some(user_receipts),
|
||||
},
|
||||
);
|
||||
let mut receipts = BTreeMap::new();
|
||||
receipts.insert(ReceiptType::Read, user_receipts);
|
||||
|
||||
let event =
|
||||
EduEvent::Ephemeral(AnyEphemeralRoomEvent::Receipt(ReceiptEvent {
|
||||
content: ReceiptEventContent(receipt_content),
|
||||
room_id: room_id.clone(),
|
||||
}));
|
||||
let mut receipt_content = BTreeMap::new();
|
||||
receipt_content.insert(event_id.to_owned(), receipts);
|
||||
|
||||
let event = AnyEphemeralRoomEvent::Receipt(ReceiptEvent {
|
||||
content: ReceiptEventContent(receipt_content),
|
||||
room_id: room_id.clone(),
|
||||
});
|
||||
db.rooms.edus.readreceipt_update(
|
||||
&user_id,
|
||||
&room_id,
|
||||
|
@ -698,6 +716,8 @@ pub async fn send_transaction_message_route<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
info!("/send/{} done", body.transaction_id);
|
||||
|
||||
Ok(send_transaction_message::v1::Response { pdus: resolved_map }.into())
|
||||
}
|
||||
|
||||
|
@ -794,7 +814,7 @@ pub fn handle_incoming_pdu<'a>(
|
|||
) {
|
||||
Err(e) => {
|
||||
// Drop
|
||||
warn!("{:?}: {}", value, e);
|
||||
warn!("Dropping bad event {}: {}", event_id, e);
|
||||
return Err("Signature verification failed".to_string());
|
||||
}
|
||||
Ok(ruma::signatures::Verified::Signatures) => {
|
||||
|
@ -821,6 +841,7 @@ pub fn handle_incoming_pdu<'a>(
|
|||
|
||||
// 4. fetch any missing auth events doing all checks listed here starting at 1. These are not timeline events
|
||||
// 5. Reject "due to auth events" if can't get all the auth events or some of the auth events are also rejected "due to auth events"
|
||||
// EDIT: Step 5 is not applied anymore because it failed too often
|
||||
debug!("Fetching auth events for {}", incoming_pdu.event_id);
|
||||
fetch_and_handle_events(
|
||||
db,
|
||||
|
@ -1292,12 +1313,30 @@ pub(crate) fn fetch_and_handle_events<'a>(
|
|||
auth_cache: &'a mut EventMap<Arc<PduEvent>>,
|
||||
) -> AsyncRecursiveResult<'a, Vec<Arc<PduEvent>>, Error> {
|
||||
Box::pin(async move {
|
||||
let back_off = |id| match db.globals.bad_event_ratelimiter.write().unwrap().entry(id) {
|
||||
Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
}
|
||||
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||
};
|
||||
|
||||
let mut pdus = vec![];
|
||||
for id in events {
|
||||
if let Some((time, tries)) = db.globals.bad_event_ratelimiter.read().unwrap().get(&id) {
|
||||
// Exponential backoff
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
|
||||
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
debug!("Backing off from {}", id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// a. Look at auth cache
|
||||
let pdu = match auth_cache.get(id) {
|
||||
Some(pdu) => {
|
||||
debug!("Found {} in cache", id);
|
||||
// We already have the auth chain for events in cache
|
||||
pdu.clone()
|
||||
}
|
||||
|
@ -1306,7 +1345,7 @@ pub(crate) fn fetch_and_handle_events<'a>(
|
|||
// (get_pdu checks both)
|
||||
None => match db.rooms.get_pdu(&id)? {
|
||||
Some(pdu) => {
|
||||
debug!("Found {} in db", id);
|
||||
trace!("Found {} in db", id);
|
||||
// We need to fetch the auth chain
|
||||
let _ = fetch_and_handle_events(
|
||||
db,
|
||||
|
@ -1331,7 +1370,7 @@ pub(crate) fn fetch_and_handle_events<'a>(
|
|||
.await
|
||||
{
|
||||
Ok(res) => {
|
||||
debug!("Got {} over federation: {:?}", id, res);
|
||||
debug!("Got {} over federation", id);
|
||||
let (event_id, mut value) =
|
||||
crate::pdu::gen_event_id_canonical_json(&res.pdu)?;
|
||||
// This will also fetch the auth chain
|
||||
|
@ -1358,12 +1397,14 @@ pub(crate) fn fetch_and_handle_events<'a>(
|
|||
}
|
||||
Err(e) => {
|
||||
warn!("Authentication of event {} failed: {:?}", id, e);
|
||||
back_off(id.clone());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Failed to fetch event: {}", id);
|
||||
back_off(id.clone());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -1383,10 +1424,67 @@ pub(crate) fn fetch_and_handle_events<'a>(
|
|||
pub(crate) async fn fetch_signing_keys(
|
||||
db: &Database,
|
||||
origin: &ServerName,
|
||||
signature_ids: Vec<&String>,
|
||||
signature_ids: Vec<String>,
|
||||
) -> Result<BTreeMap<String, String>> {
|
||||
let contains_all_ids =
|
||||
|keys: &BTreeMap<String, String>| signature_ids.iter().all(|&id| keys.contains_key(id));
|
||||
|keys: &BTreeMap<String, String>| signature_ids.iter().all(|id| keys.contains_key(id));
|
||||
|
||||
let permit = db
|
||||
.globals
|
||||
.servername_ratelimiter
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(origin)
|
||||
.map(|s| Arc::clone(s).acquire_owned());
|
||||
|
||||
let permit = match permit {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
let mut write = db.globals.servername_ratelimiter.write().unwrap();
|
||||
let s = Arc::clone(
|
||||
write
|
||||
.entry(origin.to_owned())
|
||||
.or_insert_with(|| Arc::new(Semaphore::new(1))),
|
||||
);
|
||||
|
||||
s.acquire_owned()
|
||||
}
|
||||
}
|
||||
.await;
|
||||
|
||||
let back_off = |id| match db
|
||||
.globals
|
||||
.bad_signature_ratelimiter
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(id)
|
||||
{
|
||||
Entry::Vacant(e) => {
|
||||
e.insert((Instant::now(), 1));
|
||||
}
|
||||
Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
|
||||
};
|
||||
|
||||
if let Some((time, tries)) = db
|
||||
.globals
|
||||
.bad_signature_ratelimiter
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&signature_ids)
|
||||
{
|
||||
// Exponential backoff
|
||||
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
|
||||
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
|
||||
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
|
||||
}
|
||||
|
||||
if time.elapsed() < min_elapsed_duration {
|
||||
debug!("Backing off from {:?}", signature_ids);
|
||||
return Err(Error::BadServerResponse("bad signature, still backing off"));
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Loading signing keys for {}", origin);
|
||||
|
||||
let mut result = db
|
||||
.globals
|
||||
|
@ -1399,6 +1497,8 @@ pub(crate) async fn fetch_signing_keys(
|
|||
return Ok(result);
|
||||
}
|
||||
|
||||
debug!("Fetching signing keys for {} over federation", origin);
|
||||
|
||||
if let Ok(get_keys_response) = db
|
||||
.sending
|
||||
.send_federation_request(&db.globals, origin, get_server_keys::v2::Request::new())
|
||||
|
@ -1436,14 +1536,17 @@ pub(crate) async fn fetch_signing_keys(
|
|||
&server,
|
||||
get_remote_server_keys::v2::Request::new(
|
||||
origin,
|
||||
SystemTime::now()
|
||||
.checked_add(Duration::from_secs(3600))
|
||||
.expect("SystemTime to large"),
|
||||
MilliSecondsSinceUnixEpoch::from_system_time(
|
||||
SystemTime::now()
|
||||
.checked_add(Duration::from_secs(3600))
|
||||
.expect("SystemTime to large"),
|
||||
)
|
||||
.expect("time is valid"),
|
||||
),
|
||||
)
|
||||
.await
|
||||
{
|
||||
debug!("Got signing keys: {:?}", keys);
|
||||
trace!("Got signing keys: {:?}", keys);
|
||||
for k in keys.server_keys {
|
||||
db.globals.add_signing_key(origin, &k)?;
|
||||
result.extend(
|
||||
|
@ -1464,6 +1567,10 @@ pub(crate) async fn fetch_signing_keys(
|
|||
}
|
||||
}
|
||||
|
||||
drop(permit);
|
||||
|
||||
back_off(signature_ids);
|
||||
|
||||
warn!("Failed to find public key for server: {}", origin);
|
||||
Err(Error::BadServerResponse(
|
||||
"Failed to find public key for server",
|
||||
|
@ -1581,7 +1688,7 @@ pub fn get_event_route<'a>(
|
|||
|
||||
Ok(get_event::v1::Response {
|
||||
origin: db.globals.server_name().to_owned(),
|
||||
origin_server_ts: SystemTime::now(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
|
||||
pdu: PduEvent::convert_to_outgoing_federation_event(
|
||||
db.rooms
|
||||
.get_pdu_json(&body.event_id)?
|
||||
|
@ -2186,6 +2293,34 @@ pub fn get_profile_information_route<'a>(
|
|||
.into())
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "conduit_bin",
|
||||
post("/_matrix/federation/v1/user/keys/query", data = "<body>")
|
||||
)]
|
||||
#[tracing::instrument(skip(db, body))]
|
||||
pub fn get_keys_route<'a>(
|
||||
db: State<'a, Database>,
|
||||
body: Ruma<get_keys::v1::Request>,
|
||||
) -> ConduitResult<get_keys::v1::Response> {
|
||||
if !db.globals.allow_federation() {
|
||||
return Err(Error::bad_config("Federation is disabled."));
|
||||
}
|
||||
|
||||
let result = get_keys_helper(
|
||||
None,
|
||||
&body.device_keys,
|
||||
|u| Some(u.server_name()) == body.sender_servername.as_deref(),
|
||||
&db,
|
||||
)?;
|
||||
|
||||
Ok(get_keys::v1::Response {
|
||||
device_keys: result.device_keys,
|
||||
master_keys: result.master_keys,
|
||||
self_signing_keys: result.self_signing_keys,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
pub async fn fetch_required_signing_keys(
|
||||
event: &BTreeMap<String, CanonicalJsonValue>,
|
||||
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, String>>>,
|
||||
|
@ -2208,9 +2343,8 @@ pub async fn fetch_required_signing_keys(
|
|||
"Invalid signatures content object in server response pdu.",
|
||||
))?;
|
||||
|
||||
let signature_ids = signature_object.keys().collect::<Vec<_>>();
|
||||
let signature_ids = signature_object.keys().cloned().collect::<Vec<_>>();
|
||||
|
||||
debug!("Fetching signing keys for {}", signature_server);
|
||||
let fetch_res = fetch_signing_keys(
|
||||
db,
|
||||
&Box::<ServerName>::try_from(&**signature_server).map_err(|_| {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue