forked from mirrors/relay
Check for signature errors, record signing strings
This commit is contained in:
parent
7e01cbfc41
commit
f55ea0a550
2 changed files with 63 additions and 69 deletions
|
@ -100,6 +100,9 @@ pub(crate) enum ErrorKind {
|
||||||
#[error("Couldn't sign digest")]
|
#[error("Couldn't sign digest")]
|
||||||
Signature(#[from] signature::Error),
|
Signature(#[from] signature::Error),
|
||||||
|
|
||||||
|
#[error("Server {0} failed to validate our signature")]
|
||||||
|
SignedDelivery(String),
|
||||||
|
|
||||||
#[error("Couldn't parse the signature header")]
|
#[error("Couldn't parse the signature header")]
|
||||||
HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue),
|
HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue),
|
||||||
|
|
||||||
|
|
129
src/requests.rs
129
src/requests.rs
|
@ -1,7 +1,7 @@
|
||||||
use crate::error::{Error, ErrorKind};
|
use crate::error::{Error, ErrorKind};
|
||||||
use activitystreams::iri_string::types::IriString;
|
use activitystreams::iri_string::types::IriString;
|
||||||
use actix_web::{http::header::Date, web::Bytes};
|
use actix_web::{http::header::Date, web::Bytes};
|
||||||
use awc::Client;
|
use awc::{Client, ClientResponse};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use http_signature_normalization_actix::prelude::*;
|
use http_signature_normalization_actix::prelude::*;
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
|
@ -53,6 +53,9 @@ impl Breakers {
|
||||||
let should_write = {
|
let should_write = {
|
||||||
if let Some(mut breaker) = self.inner.get_mut(authority) {
|
if let Some(mut breaker) = self.inner.get_mut(authority) {
|
||||||
breaker.fail();
|
breaker.fail();
|
||||||
|
if !breaker.should_try() {
|
||||||
|
tracing::warn!("Failed breaker for {}", authority);
|
||||||
|
}
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
|
@ -101,17 +104,12 @@ struct Breaker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Breaker {
|
impl Breaker {
|
||||||
const fn failure_threshold() -> usize {
|
const FAILURE_WAIT: Duration = Duration::from_secs(ONE_DAY);
|
||||||
10
|
const FAILURE_THRESHOLD: usize = 10;
|
||||||
}
|
|
||||||
|
|
||||||
fn failure_wait() -> Duration {
|
|
||||||
Duration::from_secs(ONE_DAY)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_try(&self) -> bool {
|
fn should_try(&self) -> bool {
|
||||||
self.failures < Self::failure_threshold()
|
self.failures < Self::FAILURE_THRESHOLD
|
||||||
|| self.last_attempt + Self::failure_wait() < SystemTime::now()
|
|| self.last_attempt + Self::FAILURE_WAIT < SystemTime::now()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fail(&mut self) {
|
fn fail(&mut self) {
|
||||||
|
@ -192,7 +190,7 @@ impl Requests {
|
||||||
fn count_err(&self) {
|
fn count_err(&self) {
|
||||||
let count = self.consecutive_errors.fetch_add(1, Ordering::Relaxed);
|
let count = self.consecutive_errors.fetch_add(1, Ordering::Relaxed);
|
||||||
if count + 1 >= self.error_limit {
|
if count + 1 >= self.error_limit {
|
||||||
tracing::warn!("{} consecutive errors, rebuilding http client", count);
|
tracing::warn!("{} consecutive errors, rebuilding http client", count + 1);
|
||||||
*self.client.borrow_mut() = build_client(&self.user_agent);
|
*self.client.borrow_mut() = build_client(&self.user_agent);
|
||||||
self.reset_err();
|
self.reset_err();
|
||||||
}
|
}
|
||||||
|
@ -202,7 +200,36 @@ impl Requests {
|
||||||
self.consecutive_errors.swap(0, Ordering::Relaxed);
|
self.consecutive_errors.swap(0, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "Fetch Json", skip(self))]
|
async fn check_response(
|
||||||
|
&self,
|
||||||
|
parsed_url: &IriString,
|
||||||
|
res: &mut ClientResponse,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.reset_err();
|
||||||
|
|
||||||
|
if !res.status().is_success() {
|
||||||
|
self.breakers.fail(&parsed_url);
|
||||||
|
|
||||||
|
if let Ok(bytes) = res.body().await {
|
||||||
|
if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) {
|
||||||
|
if s.to_lowercase().contains("http signature") {
|
||||||
|
return Err(ErrorKind::SignedDelivery(parsed_url.to_string()).into());
|
||||||
|
}
|
||||||
|
if !s.is_empty() {
|
||||||
|
tracing::warn!("Response from {}, {}", parsed_url, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(ErrorKind::Status(parsed_url.to_string(), res.status()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.breakers.succeed(&parsed_url);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(name = "Fetch Json", skip(self), fields(signing_string))]
|
||||||
pub(crate) async fn fetch_json<T>(&self, url: &str) -> Result<T, Error>
|
pub(crate) async fn fetch_json<T>(&self, url: &str) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned,
|
T: serde::de::DeserializeOwned,
|
||||||
|
@ -210,7 +237,7 @@ impl Requests {
|
||||||
self.do_fetch(url, "application/json").await
|
self.do_fetch(url, "application/json").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "Fetch Activity+Json", skip(self))]
|
#[tracing::instrument(name = "Fetch Activity+Json", skip(self), fields(signing_string))]
|
||||||
pub(crate) async fn fetch<T>(&self, url: &str) -> Result<T, Error>
|
pub(crate) async fn fetch<T>(&self, url: &str) -> Result<T, Error>
|
||||||
where
|
where
|
||||||
T: serde::de::DeserializeOwned,
|
T: serde::de::DeserializeOwned,
|
||||||
|
@ -229,6 +256,7 @@ impl Requests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let signer = self.signer();
|
let signer = self.signer();
|
||||||
|
let span = tracing::Span::current();
|
||||||
|
|
||||||
let client: Client = self.client.borrow().clone();
|
let client: Client = self.client.borrow().clone();
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -238,7 +266,10 @@ impl Requests {
|
||||||
.signature(
|
.signature(
|
||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
self.key_id.clone(),
|
self.key_id.clone(),
|
||||||
move |signing_string| signer.sign(signing_string),
|
move |signing_string| {
|
||||||
|
span.record("signing_string", signing_string);
|
||||||
|
span.in_scope(|| signer.sign(signing_string))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.send()
|
.send()
|
||||||
|
@ -251,23 +282,7 @@ impl Requests {
|
||||||
|
|
||||||
let mut res = res.map_err(|e| ErrorKind::SendRequest(url.to_string(), e.to_string()))?;
|
let mut res = res.map_err(|e| ErrorKind::SendRequest(url.to_string(), e.to_string()))?;
|
||||||
|
|
||||||
self.reset_err();
|
self.check_response(&parsed_url, &mut res).await?;
|
||||||
|
|
||||||
if !res.status().is_success() {
|
|
||||||
if let Ok(bytes) = res.body().await {
|
|
||||||
if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) {
|
|
||||||
if !s.is_empty() {
|
|
||||||
tracing::warn!("Response from {}, {}", url, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.breakers.fail(&parsed_url);
|
|
||||||
|
|
||||||
return Err(ErrorKind::Status(url.to_string(), res.status()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.breakers.succeed(&parsed_url);
|
|
||||||
|
|
||||||
let body = res
|
let body = res
|
||||||
.body()
|
.body()
|
||||||
|
@ -277,7 +292,7 @@ impl Requests {
|
||||||
Ok(serde_json::from_slice(body.as_ref())?)
|
Ok(serde_json::from_slice(body.as_ref())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "Fetch Bytes", skip(self))]
|
#[tracing::instrument(name = "Fetch Bytes", skip(self), fields(signing_string))]
|
||||||
pub(crate) async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), Error> {
|
pub(crate) async fn fetch_bytes(&self, url: &str) -> Result<(String, Bytes), Error> {
|
||||||
let parsed_url = url.parse::<IriString>()?;
|
let parsed_url = url.parse::<IriString>()?;
|
||||||
|
|
||||||
|
@ -285,8 +300,8 @@ impl Requests {
|
||||||
return Err(ErrorKind::Breaker.into());
|
return Err(ErrorKind::Breaker.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Fetching bytes for {}", url);
|
|
||||||
let signer = self.signer();
|
let signer = self.signer();
|
||||||
|
let span = tracing::Span::current();
|
||||||
|
|
||||||
let client: Client = self.client.borrow().clone();
|
let client: Client = self.client.borrow().clone();
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -296,7 +311,10 @@ impl Requests {
|
||||||
.signature(
|
.signature(
|
||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
self.key_id.clone(),
|
self.key_id.clone(),
|
||||||
move |signing_string| signer.sign(signing_string),
|
move |signing_string| {
|
||||||
|
span.record("signing_string", signing_string);
|
||||||
|
span.in_scope(|| signer.sign(signing_string))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.send()
|
.send()
|
||||||
|
@ -309,7 +327,7 @@ impl Requests {
|
||||||
|
|
||||||
let mut res = res.map_err(|e| ErrorKind::SendRequest(url.to_string(), e.to_string()))?;
|
let mut res = res.map_err(|e| ErrorKind::SendRequest(url.to_string(), e.to_string()))?;
|
||||||
|
|
||||||
self.reset_err();
|
self.check_response(&parsed_url, &mut res).await?;
|
||||||
|
|
||||||
let content_type = if let Some(content_type) = res.headers().get("content-type") {
|
let content_type = if let Some(content_type) = res.headers().get("content-type") {
|
||||||
if let Ok(s) = content_type.to_str() {
|
if let Ok(s) = content_type.to_str() {
|
||||||
|
@ -321,22 +339,6 @@ impl Requests {
|
||||||
return Err(ErrorKind::ContentType.into());
|
return Err(ErrorKind::ContentType.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
if !res.status().is_success() {
|
|
||||||
if let Ok(bytes) = res.body().await {
|
|
||||||
if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) {
|
|
||||||
if !s.is_empty() {
|
|
||||||
tracing::warn!("Response from {}, {}", url, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.breakers.fail(&parsed_url);
|
|
||||||
|
|
||||||
return Err(ErrorKind::Status(url.to_string(), res.status()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.breakers.succeed(&parsed_url);
|
|
||||||
|
|
||||||
let bytes = match res.body().limit(1024 * 1024 * 4).await {
|
let bytes = match res.body().limit(1024 * 1024 * 4).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(ErrorKind::ReceiveResponse(url.to_string(), e.to_string()).into());
|
return Err(ErrorKind::ReceiveResponse(url.to_string(), e.to_string()).into());
|
||||||
|
@ -350,7 +352,7 @@ impl Requests {
|
||||||
#[tracing::instrument(
|
#[tracing::instrument(
|
||||||
"Deliver to Inbox",
|
"Deliver to Inbox",
|
||||||
skip_all,
|
skip_all,
|
||||||
fields(inbox = inbox.to_string().as_str())
|
fields(inbox = inbox.to_string().as_str(), signing_string)
|
||||||
)]
|
)]
|
||||||
pub(crate) async fn deliver<T>(&self, inbox: IriString, item: &T) -> Result<(), Error>
|
pub(crate) async fn deliver<T>(&self, inbox: IriString, item: &T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
|
@ -361,6 +363,7 @@ impl Requests {
|
||||||
}
|
}
|
||||||
|
|
||||||
let signer = self.signer();
|
let signer = self.signer();
|
||||||
|
let span = tracing::Span::current();
|
||||||
let item_string = serde_json::to_string(item)?;
|
let item_string = serde_json::to_string(item)?;
|
||||||
|
|
||||||
let client: Client = self.client.borrow().clone();
|
let client: Client = self.client.borrow().clone();
|
||||||
|
@ -374,7 +377,10 @@ impl Requests {
|
||||||
self.key_id.clone(),
|
self.key_id.clone(),
|
||||||
Sha256::new(),
|
Sha256::new(),
|
||||||
item_string,
|
item_string,
|
||||||
move |signing_string| signer.sign(signing_string),
|
move |signing_string| {
|
||||||
|
span.record("signing_string", signing_string);
|
||||||
|
span.in_scope(|| signer.sign(signing_string))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.split();
|
.split();
|
||||||
|
@ -388,22 +394,7 @@ impl Requests {
|
||||||
|
|
||||||
let mut res = res.map_err(|e| ErrorKind::SendRequest(inbox.to_string(), e.to_string()))?;
|
let mut res = res.map_err(|e| ErrorKind::SendRequest(inbox.to_string(), e.to_string()))?;
|
||||||
|
|
||||||
self.reset_err();
|
self.check_response(&inbox, &mut res).await?;
|
||||||
|
|
||||||
if !res.status().is_success() {
|
|
||||||
if let Ok(bytes) = res.body().await {
|
|
||||||
if let Ok(s) = String::from_utf8(bytes.as_ref().to_vec()) {
|
|
||||||
if !s.is_empty() {
|
|
||||||
tracing::warn!("Response from {}, {}", inbox.as_str(), s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.breakers.fail(&inbox);
|
|
||||||
return Err(ErrorKind::Status(inbox.to_string(), res.status()).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.breakers.succeed(&inbox);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue