Compare commits

...

8 commits

Author SHA1 Message Date
nexy7574
5ebe8cd8dd
feat(policy-server): Limit policy server request timeout to 10 seconds
Some checks failed
Checks / Prefligit / prefligit (push) Failing after 7s
Release Docker Image / define-variables (push) Failing after 18s
Release Docker Image / build-image (linux/amd64, release, linux-amd64, base) (push) Has been skipped
Release Docker Image / build-image (linux/arm64, release, linux-arm64, base) (push) Has been skipped
Release Docker Image / merge (push) Has been skipped
Checks / Rust / Format (push) Failing after 6s
Checks / Rust / Clippy (push) Failing after 20s
Checks / Rust / Cargo Test (push) Failing after 15s
2025-07-19 22:07:18 +01:00
nexy7574
d4049a79bf
feat(policy-server): Prevent local events that fail the policy check
Some checks failed
Checks / Prefligit / prefligit (push) Failing after 1s
Release Docker Image / define-variables (push) Failing after 1s
Release Docker Image / build-image (linux/amd64, release, linux-amd64, base) (push) Has been skipped
Release Docker Image / build-image (linux/arm64, release, linux-arm64, base) (push) Has been skipped
Release Docker Image / merge (push) Has been skipped
Checks / Rust / Format (push) Failing after 1s
Checks / Rust / Clippy (push) Failing after 12s
Checks / Rust / Cargo Test (push) Failing after 15s
2025-07-19 21:09:23 +01:00
nexy7574
1bc06fcd57
feat(policy-server): Soft-fail redactions for failed events 2025-07-19 20:54:06 +01:00
nexy7574
cff4076fad
style(policy-server): Restructure logging 2025-07-19 20:50:47 +01:00
nexy7574
dded3a2981
fix(policy-server): Avoid unnecessary database lookup 2025-07-19 20:47:02 +01:00
nexy7574
ca263af321
chore: Update ruwuma & fix lints
Some checks failed
Checks / Prefligit / prefligit (push) Failing after 3s
Release Docker Image / define-variables (push) Failing after 1s
Release Docker Image / build-image (linux/amd64, release, linux-amd64, base) (push) Has been skipped
Release Docker Image / build-image (linux/arm64, release, linux-arm64, base) (push) Has been skipped
Release Docker Image / merge (push) Has been skipped
Checks / Rust / Format (push) Failing after 6s
Checks / Rust / Clippy (push) Failing after 22s
Checks / Rust / Cargo Test (push) Failing after 12s
2025-07-19 20:34:34 +01:00
nexy7574
19a8eaca39
chore: Merge branch 'main' into nex/serving-policy
# Conflicts:
#	src/service/rooms/event_handler/upgrade_outlier_pdu.rs
#	src/service/rooms/timeline/mod.rs
2025-07-19 20:26:08 +01:00
nexy7574
ce94e241b3
feat(policy-server): Policy server following 2025-07-19 20:22:29 +01:00
7 changed files with 147 additions and 21 deletions

22
Cargo.lock generated
View file

@ -3900,7 +3900,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3"
[[package]] [[package]]
name = "ruma" name = "ruma"
version = "0.10.1" version = "0.10.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"assign", "assign",
"js_int", "js_int",
@ -3920,7 +3920,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-appservice-api" name = "ruma-appservice-api"
version = "0.10.0" version = "0.10.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -3932,7 +3932,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-client-api" name = "ruma-client-api"
version = "0.18.0" version = "0.18.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"assign", "assign",
@ -3955,7 +3955,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-common" name = "ruma-common"
version = "0.13.0" version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"base64 0.22.1", "base64 0.22.1",
@ -3987,7 +3987,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-events" name = "ruma-events"
version = "0.28.1" version = "0.28.1"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"as_variant", "as_variant",
"indexmap 2.9.0", "indexmap 2.9.0",
@ -4012,7 +4012,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-federation-api" name = "ruma-federation-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"bytes", "bytes",
"headers", "headers",
@ -4034,7 +4034,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identifiers-validation" name = "ruma-identifiers-validation"
version = "0.9.5" version = "0.9.5"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"js_int", "js_int",
"thiserror 2.0.12", "thiserror 2.0.12",
@ -4043,7 +4043,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-identity-service-api" name = "ruma-identity-service-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -4053,7 +4053,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-macros" name = "ruma-macros"
version = "0.13.0" version = "0.13.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"proc-macro-crate", "proc-macro-crate",
@ -4068,7 +4068,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-push-gateway-api" name = "ruma-push-gateway-api"
version = "0.9.0" version = "0.9.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"js_int", "js_int",
"ruma-common", "ruma-common",
@ -4080,7 +4080,7 @@ dependencies = [
[[package]] [[package]]
name = "ruma-signatures" name = "ruma-signatures"
version = "0.15.0" version = "0.15.0"
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=a4b948b40417a65ab0282ae47cc50035dd455e02#a4b948b40417a65ab0282ae47cc50035dd455e02" source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ed25519-dalek", "ed25519-dalek",

View file

@ -352,7 +352,7 @@ version = "0.1.2"
[workspace.dependencies.ruma] [workspace.dependencies.ruma]
git = "https://forgejo.ellis.link/continuwuation/ruwuma" git = "https://forgejo.ellis.link/continuwuation/ruwuma"
#branch = "conduwuit-changes" #branch = "conduwuit-changes"
rev = "a4b948b40417a65ab0282ae47cc50035dd455e02" rev = "b753738047d1f443aca870896ef27ecaacf027da"
features = [ features = [
"compat", "compat",
"rand", "rand",

View file

@ -217,8 +217,9 @@ where
} }
/* /*
// TODO: In the past this code caused problems federating with synapse, maybe this has been // TODO: In the past this code was commented as it caused problems with Synapse. This is no
// resolved already. Needs testing. // longer the case. This needs to be implemented.
// See also: https://github.com/ruma/ruma/pull/2064
// //
// 2. Reject if auth_events // 2. Reject if auth_events
// a. auth_events cannot have duplicate keys since it's a BTree // a. auth_events cannot have duplicate keys since it's a BTree

View file

@ -0,0 +1,79 @@
use std::time::Duration;
use conduwuit::{Err, Event, PduEvent, Result, debug, implement, warn};
use ruma::{
RoomId, ServerName,
api::federation::room::policy::v1::Request as PolicyRequest,
events::{StateEventType, room::policy::RoomPolicyEventContent},
};
/// Returns Ok if the policy server allows the event
#[implement(super::Service)]
#[tracing::instrument(skip_all, level = "debug")]
pub async fn policyserv_check(&self, pdu: &PduEvent, room_id: &RoomId) -> Result {
let Ok(policyserver) = self
.services
.state_accessor
.room_state_get_content(room_id, &StateEventType::RoomPolicy, "")
.await
.map(|c: RoomPolicyEventContent| c)
else {
return Ok(());
};
let via = match policyserver.via {
| Some(ref via) => ServerName::parse(via)?,
| None => {
debug!("No policy server configured for room {room_id}");
return Ok(());
},
};
let outgoing = self
.services
.sending
.convert_to_outgoing_federation_event(pdu.to_canonical_object())
.await;
debug!("Checking pdu {outgoing:?} for spam with policy server {via} for room {room_id}");
let response = tokio::time::timeout(
Duration::from_secs(10),
self.services
.sending
.send_federation_request(via, PolicyRequest {
event_id: pdu.event_id().to_owned(),
pdu: Some(outgoing),
}),
)
.await;
let response = match response {
| Ok(Ok(response)) => response,
| Ok(Err(e)) => {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Failed to contact policy server: {e}"
);
return Ok(());
},
| Err(_) => {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Policy server request timed out after 10 seconds"
);
return Ok(());
},
};
if response.recommendation == "spam" {
warn!(
via = %via,
event_id = %pdu.event_id(),
room_id = %room_id,
"Event was marked as spam by policy server",
);
return Err!(Request(Forbidden("Event was marked as spam by policy server")));
}
Ok(())
}

View file

@ -1,4 +1,5 @@
mod acl_check; mod acl_check;
mod call_policyserv;
mod fetch_and_handle_outliers; mod fetch_and_handle_outliers;
mod fetch_prev; mod fetch_prev;
mod fetch_state; mod fetch_state;

View file

@ -1,7 +1,7 @@
use std::{borrow::Borrow, collections::BTreeMap, iter::once, sync::Arc, time::Instant}; use std::{borrow::Borrow, collections::BTreeMap, iter::once, sync::Arc, time::Instant};
use conduwuit::{ use conduwuit::{
Err, Result, debug, debug_info, err, implement, is_equal_to, Err, Result, debug, debug_info, err, implement, info, is_equal_to,
matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res}, matrix::{Event, EventTypeExt, PduEvent, StateKey, state_res},
trace, trace,
utils::stream::{BroadbandExt, ReadyExt}, utils::stream::{BroadbandExt, ReadyExt},
@ -47,7 +47,7 @@ where
return Err!(Request(InvalidParam("Event has been soft failed"))); return Err!(Request(InvalidParam("Event has been soft failed")));
} }
debug!("Upgrading to timeline pdu"); debug!("Upgrading pdu {} from outlier to timeline pdu", incoming_pdu.event_id);
let timer = Instant::now(); let timer = Instant::now();
let room_version_id = get_room_version_id(create_event)?; let room_version_id = get_room_version_id(create_event)?;
@ -74,7 +74,7 @@ where
let room_version = to_room_version(&room_version_id); let room_version = to_room_version(&room_version_id);
debug!("Performing auth check"); debug!("Performing auth check to upgrade {}", incoming_pdu.event_id);
// 11. Check the auth of the event passes based on the state of the event // 11. Check the auth of the event passes based on the state of the event
let state_fetch_state = &state_at_incoming_event; let state_fetch_state = &state_at_incoming_event;
let state_fetch = |k: StateEventType, s: StateKey| async move { let state_fetch = |k: StateEventType, s: StateKey| async move {
@ -84,6 +84,7 @@ where
self.services.timeline.get_pdu(event_id).await.ok() self.services.timeline.get_pdu(event_id).await.ok()
}; };
debug!("running auth check on {}", incoming_pdu.event_id);
let auth_check = state_res::event_auth::auth_check( let auth_check = state_res::event_auth::auth_check(
&room_version, &room_version,
&incoming_pdu, &incoming_pdu,
@ -97,7 +98,7 @@ where
return Err!(Request(Forbidden("Event has failed auth check with state at the event."))); return Err!(Request(Forbidden("Event has failed auth check with state at the event.")));
} }
debug!("Gathering auth events"); debug!("Gathering auth events for {}", incoming_pdu.event_id);
let auth_events = self let auth_events = self
.services .services
.state .state
@ -115,6 +116,7 @@ where
ready(auth_events.get(&key).map(ToOwned::to_owned)) ready(auth_events.get(&key).map(ToOwned::to_owned))
}; };
debug!("running auth check on {} with claimed state auth", incoming_pdu.event_id);
let auth_check = state_res::event_auth::auth_check( let auth_check = state_res::event_auth::auth_check(
&room_version, &room_version,
&incoming_pdu, &incoming_pdu,
@ -125,8 +127,8 @@ where
.map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?; .map_err(|e| err!(Request(Forbidden("Auth check failed: {e:?}"))))?;
// Soft fail check before doing state res // Soft fail check before doing state res
debug!("Performing soft-fail check"); debug!("Performing soft-fail check on {}", incoming_pdu.event_id);
let soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_id)) { let mut soft_fail = match (auth_check, incoming_pdu.redacts_id(&room_version_id)) {
| (false, _) => true, | (false, _) => true,
| (true, None) => false, | (true, None) => false,
| (true, Some(redact_id)) => | (true, Some(redact_id)) =>
@ -219,10 +221,42 @@ where
.await?; .await?;
} }
// 14-pre. If the event is not a state event, ask the policy server about it
if incoming_pdu.state_key.is_none() {
debug!("Checking policy server for event {}", incoming_pdu.event_id);
let policy = self.policyserv_check(&incoming_pdu, room_id);
if let Err(e) = policy.await {
warn!("Policy server check failed for event {}: {e}", incoming_pdu.event_id);
if !soft_fail {
soft_fail = true;
}
}
debug!("Policy server check passed for event {}", incoming_pdu.event_id);
}
// Additionally, if this is a redaction for a soft-failed event, we soft-fail it
// also
if let Some(redact_id) = incoming_pdu.redacts_id(&room_version_id) {
debug!("Checking if redaction {} is for a soft-failed event", redact_id);
if self
.services
.pdu_metadata
.is_event_soft_failed(&redact_id)
.await
{
warn!(
"Redaction {} is for a soft-failed event, soft failing the redaction",
redact_id
);
soft_fail = true;
}
}
// 14. Check if the event passes auth based on the "current state" of the room, // 14. Check if the event passes auth based on the "current state" of the room,
// if not soft fail it // if not soft fail it
if soft_fail { if soft_fail {
debug!("Soft failing event"); info!("Soft failing event {}", incoming_pdu.event_id);
// assert!(extremities.is_empty(), "soft_fail extremities empty");
let extremities = extremities.iter().map(Borrow::borrow); let extremities = extremities.iter().map(Borrow::borrow);
self.services self.services

View file

@ -165,6 +165,17 @@ pub async fn create_hash_and_sign_event(
return Err!(Request(Forbidden("Event is not authorized."))); return Err!(Request(Forbidden("Event is not authorized.")));
} }
// Check with the policy server
if self
.services
.event_handler
.policyserv_check(&pdu, room_id)
.await
.is_err()
{
return Err!(Request(Forbidden(debug_warn!("Policy server marked this event as spam"))));
}
// Hash and sign // Hash and sign
let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| { let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| {
err!(Request(BadJson(warn!("Failed to convert PDU to canonical JSON: {e}")))) err!(Request(BadJson(warn!("Failed to convert PDU to canonical JSON: {e}"))))