feat: Fetch policy server signatures
This commit is contained in:
parent
867d0ab671
commit
72f0eb9493
5 changed files with 184 additions and 34 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -4063,7 +4063,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
|
|||
[[package]]
|
||||
name = "ruma"
|
||||
version = "0.10.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"js_int",
|
||||
|
|
@ -4083,7 +4083,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-appservice-api"
|
||||
version = "0.10.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4095,7 +4095,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-client-api"
|
||||
version = "0.18.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"assign",
|
||||
|
|
@ -4118,7 +4118,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-common"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"base64 0.22.1",
|
||||
|
|
@ -4150,7 +4150,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-events"
|
||||
version = "0.28.1"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"as_variant",
|
||||
"indexmap",
|
||||
|
|
@ -4175,7 +4175,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-federation-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"headers",
|
||||
|
|
@ -4197,7 +4197,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identifiers-validation"
|
||||
version = "0.9.5"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"thiserror 2.0.17",
|
||||
|
|
@ -4206,7 +4206,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-identity-service-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4216,7 +4216,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-macros"
|
||||
version = "0.13.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro-crate",
|
||||
|
|
@ -4231,7 +4231,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-push-gateway-api"
|
||||
version = "0.9.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"js_int",
|
||||
"ruma-common",
|
||||
|
|
@ -4243,7 +4243,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "ruma-signatures"
|
||||
version = "0.15.0"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=50b2a91b2ab8f9830eea80b9911e11234e0eac66#50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=c091171279f76d34e530f7b7d7008d12e7429b1a#c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek",
|
||||
|
|
|
|||
|
|
@ -351,7 +351,7 @@ version = "0.1.2"
|
|||
# Used for matrix spec type definitions and helpers
|
||||
[workspace.dependencies.ruma]
|
||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||
rev = "50b2a91b2ab8f9830eea80b9911e11234e0eac66"
|
||||
rev = "c091171279f76d34e530f7b7d7008d12e7429b1a"
|
||||
features = [
|
||||
"compat",
|
||||
"rand",
|
||||
|
|
|
|||
|
|
@ -3,14 +3,21 @@
|
|||
//! This module implements a check against a room-specific policy server, as
|
||||
//! described in the relevant Matrix spec proposal (see: https://github.com/matrix-org/matrix-spec-proposals/pull/4284).
|
||||
|
||||
use std::time::Duration;
|
||||
use std::{collections::BTreeMap, time::Duration};
|
||||
|
||||
use conduwuit::{Err, Event, PduEvent, Result, debug, debug_info, implement, trace, warn};
|
||||
use conduwuit::{
|
||||
Err, Event, PduEvent, Result, debug, debug_error, debug_info, debug_warn, implement, trace,
|
||||
warn,
|
||||
};
|
||||
use ruma::{
|
||||
CanonicalJsonObject, RoomId, ServerName,
|
||||
api::federation::room::policy::v1::Request as PolicyRequest,
|
||||
CanonicalJsonObject, CanonicalJsonValue, KeyId, RoomId, ServerName,
|
||||
api::federation::room::{
|
||||
policy_check::unstable::Request as PolicyCheckRequest,
|
||||
policy_sign::unstable::Request as PolicySignRequest,
|
||||
},
|
||||
events::{StateEventType, room::policy::RoomPolicyEventContent},
|
||||
};
|
||||
use serde_json::value::RawValue;
|
||||
|
||||
/// Asks a remote policy server if the event is allowed.
|
||||
///
|
||||
|
|
@ -24,25 +31,18 @@ use ruma::{
|
|||
/// contacted for whatever reason, Err(e) is returned, which generally is a
|
||||
/// fail-open operation.
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(skip_all, level = "debug")]
|
||||
#[tracing::instrument(skip(self, pdu, pdu_json))]
|
||||
pub async fn ask_policy_server(
|
||||
&self,
|
||||
pdu: &PduEvent,
|
||||
pdu_json: &CanonicalJsonObject,
|
||||
pdu_json: &mut CanonicalJsonObject,
|
||||
room_id: &RoomId,
|
||||
incoming: bool,
|
||||
) -> Result<bool> {
|
||||
if !self.services.server.config.enable_msc4284_policy_servers {
|
||||
trace!("policy server checking is disabled");
|
||||
return Ok(true); // don't ever contact policy servers
|
||||
}
|
||||
if self.services.server.config.policy_server_check_own_events
|
||||
&& pdu.origin.is_some()
|
||||
&& self
|
||||
.services
|
||||
.server
|
||||
.is_ours(pdu.origin.as_ref().unwrap().as_str())
|
||||
{
|
||||
return Ok(true); // don't contact policy servers for locally generated events
|
||||
}
|
||||
|
||||
if *pdu.event_type() == StateEventType::RoomPolicy.into() {
|
||||
debug!(
|
||||
|
|
@ -52,16 +52,29 @@ pub async fn ask_policy_server(
|
|||
);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let Ok(policyserver) = self
|
||||
.services
|
||||
.state_accessor
|
||||
.room_state_get_content(room_id, &StateEventType::RoomPolicy, "")
|
||||
.await
|
||||
.inspect_err(|e| debug_error!("failed to load room policy server state event: {e}"))
|
||||
.map(|c: RoomPolicyEventContent| c)
|
||||
else {
|
||||
debug!("room has no policy server configured");
|
||||
return Ok(true);
|
||||
};
|
||||
|
||||
if self.services.server.config.policy_server_check_own_events
|
||||
&& !incoming
|
||||
&& policyserver.public_key.is_none()
|
||||
{
|
||||
// don't contact policy servers for locally generated events, but only when the
|
||||
// policy server does not require signatures
|
||||
trace!("won't contact policy server for locally generated event");
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let via = match policyserver.via {
|
||||
| Some(ref via) => ServerName::parse(via)?,
|
||||
| None => {
|
||||
|
|
@ -75,7 +88,6 @@ pub async fn ask_policy_server(
|
|||
}
|
||||
if !self.services.state_cache.server_in_room(via, room_id).await {
|
||||
debug!(
|
||||
room_id = %room_id,
|
||||
via = %via,
|
||||
"Policy server is not in the room, skipping spam check"
|
||||
);
|
||||
|
|
@ -86,17 +98,33 @@ pub async fn ask_policy_server(
|
|||
.sending
|
||||
.convert_to_outgoing_federation_event(pdu_json.clone())
|
||||
.await;
|
||||
if policyserver.public_key.is_some() {
|
||||
if !incoming {
|
||||
debug_info!(
|
||||
via = %via,
|
||||
outgoing = ?pdu_json,
|
||||
"Getting policy server signature on event"
|
||||
);
|
||||
return self
|
||||
.fetch_policy_server_signature(pdu, pdu_json, via, outgoing, room_id)
|
||||
.await;
|
||||
}
|
||||
debug!("Event is not local, performing legacy spam check");
|
||||
// If we got this far, that means that there was likely no policy server
|
||||
// signature on the given event. Fall through to asking the PS if it's
|
||||
// spam.
|
||||
// TODO: this should probably be marking it as failed, but for now fall
|
||||
// thru
|
||||
}
|
||||
debug_info!(
|
||||
room_id = %room_id,
|
||||
via = %via,
|
||||
outgoing = ?pdu_json,
|
||||
"Checking event for spam with policy server"
|
||||
"Checking event for spam with policy server via legacy check"
|
||||
);
|
||||
let response = tokio::time::timeout(
|
||||
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
|
||||
self.services
|
||||
.sending
|
||||
.send_federation_request(via, PolicyRequest {
|
||||
.send_federation_request(via, PolicyCheckRequest {
|
||||
event_id: pdu.event_id().to_owned(),
|
||||
pdu: Some(outgoing),
|
||||
}),
|
||||
|
|
@ -142,3 +170,120 @@ pub async fn ask_policy_server(
|
|||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Asks a remote policy server for a signature on this event.
|
||||
/// If the policy server signs this event, the original data is mutated.
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn fetch_policy_server_signature(
|
||||
&self,
|
||||
pdu: &PduEvent,
|
||||
pdu_json: &mut CanonicalJsonObject,
|
||||
via: &ServerName,
|
||||
outgoing: Box<RawValue>,
|
||||
room_id: &RoomId,
|
||||
) -> Result<bool> {
|
||||
debug!("Requesting policy server signature");
|
||||
let response = tokio::time::timeout(
|
||||
Duration::from_secs(self.services.server.config.policy_server_request_timeout),
|
||||
self.services
|
||||
.sending
|
||||
.send_federation_request(via, PolicySignRequest { pdu: outgoing }),
|
||||
)
|
||||
.await;
|
||||
|
||||
let response = match response {
|
||||
| Ok(Ok(response)) => {
|
||||
debug!("Response from policy server: {:?}", response);
|
||||
response
|
||||
},
|
||||
| Ok(Err(e)) => {
|
||||
warn!(
|
||||
via = %via,
|
||||
event_id = %pdu.event_id(),
|
||||
room_id = %room_id,
|
||||
"Failed to contact policy server: {e}"
|
||||
);
|
||||
// Network or policy server errors are treated as non-fatal: event is allowed by
|
||||
// default.
|
||||
return Err(e);
|
||||
},
|
||||
| Err(elapsed) => {
|
||||
warn!(
|
||||
%via,
|
||||
event_id = %pdu.event_id(),
|
||||
%room_id,
|
||||
%elapsed,
|
||||
"Policy server request timed out after 10 seconds"
|
||||
);
|
||||
return Err!("Request to policy server timed out");
|
||||
},
|
||||
};
|
||||
if response.signatures.is_none() {
|
||||
debug!("Policy server refused to sign event");
|
||||
return Ok(false);
|
||||
}
|
||||
let sigs: ruma::Signatures<ruma::OwnedServerName, ruma::ServerSigningKeyVersion> =
|
||||
response.signatures.unwrap();
|
||||
if !sigs.contains_key(via) {
|
||||
debug_warn!(
|
||||
"Policy server returned signatures, but did not include the expected server name \
|
||||
'{}': {:?}",
|
||||
via,
|
||||
sigs
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
let keypairs = sigs.get(via).unwrap();
|
||||
let wanted_key_id = KeyId::parse("ed25519:policy_server")?;
|
||||
if !keypairs.contains_key(wanted_key_id) {
|
||||
debug_warn!(
|
||||
"Policy server returned signature, but did not use the key ID \
|
||||
'ed25519:policy_server'."
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
let signatures_entry = pdu_json
|
||||
.entry("signatures".to_owned())
|
||||
.or_insert_with(|| CanonicalJsonValue::Object(BTreeMap::default()));
|
||||
|
||||
if let CanonicalJsonValue::Object(signatures_map) = signatures_entry {
|
||||
let sig_value = keypairs.get(wanted_key_id).unwrap().to_owned();
|
||||
|
||||
match signatures_map.get_mut(via.as_str()) {
|
||||
| Some(CanonicalJsonValue::Object(inner_map)) => {
|
||||
trace!("inserting PS signature: {}", sig_value);
|
||||
inner_map.insert(
|
||||
"ed25519:policy_server".to_owned(),
|
||||
CanonicalJsonValue::String(sig_value),
|
||||
);
|
||||
},
|
||||
| Some(_) => {
|
||||
debug_warn!(
|
||||
"Existing `signatures[{}]` field is not an object; cannot insert policy \
|
||||
signature",
|
||||
via
|
||||
);
|
||||
return Ok(false);
|
||||
},
|
||||
| None => {
|
||||
let mut inner = BTreeMap::new();
|
||||
inner.insert(
|
||||
"ed25519:policy_server".to_owned(),
|
||||
CanonicalJsonValue::String(sig_value.clone()),
|
||||
);
|
||||
trace!(
|
||||
"created new signatures object for {via} with the signature {}",
|
||||
sig_value
|
||||
);
|
||||
signatures_map.insert(via.as_str().to_owned(), CanonicalJsonValue::Object(inner));
|
||||
},
|
||||
}
|
||||
} else {
|
||||
debug_warn!(
|
||||
"Existing `signatures` field is not an object; cannot insert policy signature"
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -256,7 +256,12 @@ where
|
|||
if incoming_pdu.state_key.is_none() {
|
||||
debug!(event_id = %incoming_pdu.event_id, "Checking policy server for event");
|
||||
match self
|
||||
.ask_policy_server(&incoming_pdu, &incoming_pdu.to_canonical_object(), room_id)
|
||||
.ask_policy_server(
|
||||
&incoming_pdu,
|
||||
&mut incoming_pdu.to_canonical_object(),
|
||||
room_id,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
| Ok(false) => {
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ pub async fn create_hash_and_sign_event(
|
|||
match self
|
||||
.services
|
||||
.event_handler
|
||||
.ask_policy_server(&pdu, &pdu_json, pdu.room_id().expect("has room ID"))
|
||||
.ask_policy_server(&pdu, &mut pdu_json, pdu.room_id().expect("has room ID"), false)
|
||||
.await
|
||||
{
|
||||
| Ok(true) => {},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue