diff --git a/.drone.yml b/.drone.yml index f29528e..e1e28d4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,20 +14,27 @@ steps: - /root/.cargo/bin/cargo fmt -- --check - name: cargo check - image: clux/muslrust:1.59.0 + image: rust:1.61-bullseye commands: - - cargo check + - cargo check --all --all-targets - name: cargo clippy - image: clux/muslrust:1.59.0 + image: rust:1.61-bullseye commands: - rustup component add clippy - cargo clippy --workspace --tests --all-targets --all-features -- -D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro - cargo clippy --workspace -- -D clippy::unwrap_used - name: cargo test - image: clux/muslrust:1.59.0 + image: rust:1.61-bullseye environment: RUST_BACKTRACE: 1 commands: - cargo test --workspace --no-fail-fast + + - name: cargo run + image: rust:1.61-bullseye + environment: + RUST_BACKTRACE: 1 + commands: + - cargo run -p activitypub_federation --example federation diff --git a/examples/federation/instance.rs b/examples/federation/instance.rs index 87ebf8e..adb930e 100644 --- a/examples/federation/instance.rs +++ b/examples/federation/instance.rs @@ -40,7 +40,7 @@ pub struct Instance { impl Instance { pub fn new(hostname: String) -> Result { let settings = InstanceSettingsBuilder::default() - .testing_send_sync(true) + .debug(true) .worker_count(1) .build()?; let local_instance = diff --git a/src/core/activity_queue.rs b/src/core/activity_queue.rs index 43460d2..749c7a4 100644 --- a/src/core/activity_queue.rs +++ b/src/core/activity_queue.rs @@ -1,5 +1,6 @@ use crate::{ core::signatures::{sign_request, PublicKey}, + utils::verify_url_valid, Error, LocalInstance, APUB_JSON_CONTENT_TYPE, @@ -44,6 +45,9 @@ impl SendActivity { pub async fn send(self, instance: &LocalInstance) -> Result<(), Error> { let activity_queue = &instance.activity_queue; for inbox in self.inboxes { + if verify_url_valid(&inbox, &instance.settings).is_err() { + continue; + } let message = SendActivityTask { activity_id: self.activity_id.clone(), inbox, @@ -51,7 +55,7 @@ impl SendActivity { public_key: self.actor_public_key.clone(), private_key: self.actor_private_key.clone(), }; - if instance.settings.testing_send_sync { + if instance.settings.debug { let res = do_send(message, &instance.client, instance.settings.request_timeout).await; // Don't fail on error, as we intentionally do some invalid actions in tests, to verify that diff --git a/src/core/inbox.rs b/src/core/inbox.rs index 37dfe5f..b84a11a 100644 --- a/src/core/inbox.rs +++ b/src/core/inbox.rs @@ -2,7 +2,7 @@ use crate::{ core::{object_id::ObjectId, signatures::verify_signature}, data::Data, traits::{ActivityHandler, ApubObject}, - utils::verify_domains_match, + utils::{verify_domains_match, verify_url_valid}, Error, LocalInstance, }; @@ -29,13 +29,11 @@ where E: From + From, { verify_domains_match(activity.id(), activity.actor())?; + verify_url_valid(activity.id(), &local_instance.settings)?; if local_instance.is_local_url(activity.id()) { return Err(Error::UrlVerificationError("Activity was sent from local instance").into()); } - (local_instance.settings.verify_url_function)(activity.id()) - .map_err(Error::UrlVerificationError)?; - let request_counter = &mut 0; let actor = ObjectId::::new(activity.actor().clone()) .dereference::(data, local_instance, request_counter) diff --git a/src/core/object_id.rs b/src/core/object_id.rs index 8010bb7..855df62 100644 --- a/src/core/object_id.rs +++ b/src/core/object_id.rs @@ -159,7 +159,7 @@ where Kind: ApubObject + Send + 'static, for<'de2> ::ApubType: serde::Deserialize<'de2>, { - #[allow(clippy::to_string_in_display)] + #[allow(clippy::recursive_format_impl)] fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // Use to_string here because Url.display is not useful for us write!(f, "{}", self.0) diff --git a/src/lib.rs b/src/lib.rs index 0093f53..ffcace2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,10 +32,12 @@ pub struct InstanceSettings { /// Number of worker threads for sending outgoing activities #[builder(default = "64")] worker_count: u64, - /// Send outgoing activities synchronously, not in background thread. Helps to make tests - /// more consistent, but not recommended for production. + /// Run library in debug mode. This allows usage of http and localhost urls. It also sends + /// outgoing activities synchronously, not in background thread. This helps to make tests + /// more consistent. + /// Do not use for production. #[builder(default = "false")] - testing_send_sync: bool, + debug: bool, /// Timeout for all HTTP requests. HTTP signatures are valid for 10s, so it makes sense to /// use the same as timeout when sending #[builder(default = "Duration::from_secs(10)")] diff --git a/src/utils.rs b/src/utils.rs index 416260a..76d21db 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -use crate::{Error, LocalInstance, APUB_JSON_CONTENT_TYPE}; +use crate::{Error, InstanceSettings, LocalInstance, APUB_JSON_CONTENT_TYPE}; use http::StatusCode; use serde::de::DeserializeOwned; use tracing::log::info; @@ -11,6 +11,7 @@ pub async fn fetch_object_http( ) -> Result { // dont fetch local objects this way debug_assert!(url.domain() != Some(&instance.hostname)); + verify_url_valid(url, &instance.settings)?; info!("Fetching remote object {}", url.to_string()); *request_counter += 1; @@ -49,3 +50,35 @@ pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> { } Ok(()) } + +/// Perform some security checks on URLs as mentioned in activitypub spec, and call user-supplied +/// [`InstanceSettings.verify_url_function`]. +/// +/// https://www.w3.org/TR/activitypub/#security-considerations +pub fn verify_url_valid(url: &Url, settings: &InstanceSettings) -> Result<(), Error> { + match url.scheme() { + "https" => {} + "http" => { + if !settings.debug { + return Err(Error::UrlVerificationError( + "Http urls are only allowed in debug mode", + )); + } + } + _ => return Err(Error::UrlVerificationError("Invalid url scheme")), + }; + + if url.domain().is_none() { + return Err(Error::UrlVerificationError("Url must have a domain")); + } + + if url.domain() == Some("localhost") && !settings.debug { + return Err(Error::UrlVerificationError( + "Localhost is only allowed in debug mode", + )); + } + + (settings.verify_url_function)(url).map_err(Error::UrlVerificationError)?; + + Ok(()) +}