mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2025-05-20 01:18:52 +00:00
Add more url validation
This commit is contained in:
parent
43b51d79ce
commit
e4ea9abdb7
3 changed files with 47 additions and 7 deletions
|
@ -7,6 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use std::{env::args, str::FromStr};
|
use std::{env::args, str::FromStr};
|
||||||
|
use tokio::try_join;
|
||||||
use tracing::log::{info, LevelFilter};
|
use tracing::log::{info, LevelFilter};
|
||||||
|
|
||||||
mod activities;
|
mod activities;
|
||||||
|
@ -34,8 +35,10 @@ async fn main() -> Result<(), Error> {
|
||||||
.map(|arg| Webserver::from_str(&arg).unwrap())
|
.map(|arg| Webserver::from_str(&arg).unwrap())
|
||||||
.unwrap_or(Webserver::Axum);
|
.unwrap_or(Webserver::Axum);
|
||||||
|
|
||||||
let alpha = new_instance("localhost:8001", "alpha".to_string()).await?;
|
let (alpha, beta) = try_join!(
|
||||||
let beta = new_instance("localhost:8002", "beta".to_string()).await?;
|
new_instance("localhost:8001", "alpha".to_string()),
|
||||||
|
new_instance("localhost:8002", "beta".to_string())
|
||||||
|
)?;
|
||||||
listen(&alpha, &webserver)?;
|
listen(&alpha, &webserver)?;
|
||||||
listen(&beta, &webserver)?;
|
listen(&beta, &webserver)?;
|
||||||
info!("Local instances started");
|
info!("Local instances started");
|
||||||
|
|
|
@ -26,11 +26,14 @@ use bytes::Bytes;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
use dyn_clone::{clone_trait_object, DynClone};
|
use dyn_clone::{clone_trait_object, DynClone};
|
||||||
use moka::future::Cache;
|
use moka::future::Cache;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
use reqwest::Request;
|
use reqwest::Request;
|
||||||
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
||||||
use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey};
|
use rsa::{pkcs8::DecodePrivateKey, RsaPrivateKey};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::{
|
use std::{
|
||||||
|
net::IpAddr,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
|
@ -38,6 +41,7 @@ use std::{
|
||||||
},
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
use tokio::net::lookup_host;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// Configuration for this library, with various federation related settings
|
/// Configuration for this library, with various federation related settings
|
||||||
|
@ -159,14 +163,44 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.domain().is_none() {
|
let Some(domain) = url.domain() else {
|
||||||
return Err(Error::UrlVerificationError("Url must have a domain"));
|
return Err(Error::UrlVerificationError("Url must have a domain"));
|
||||||
|
};
|
||||||
|
|
||||||
|
static DOMAIN_REGEX: Lazy<Regex> =
|
||||||
|
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9.-]*$").expect("compile regex"));
|
||||||
|
if !DOMAIN_REGEX.is_match(domain) {
|
||||||
|
return Err(Error::UrlVerificationError("Invalid characters in domain"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if url.domain() == Some("localhost") && !self.debug {
|
// Extra checks only for production mode
|
||||||
return Err(Error::UrlVerificationError(
|
if !self.debug {
|
||||||
"Localhost is only allowed in debug mode",
|
if url.port().is_some() {
|
||||||
));
|
return Err(Error::UrlVerificationError("Explicit port is not allowed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve domain and see if it points to private IP
|
||||||
|
// TODO: Use is_global() once stabilized
|
||||||
|
// https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global
|
||||||
|
let invalid_ip = lookup_host(domain).await?.any(|addr| match addr.ip() {
|
||||||
|
IpAddr::V4(addr) => {
|
||||||
|
addr.is_private()
|
||||||
|
|| addr.is_link_local()
|
||||||
|
|| addr.is_loopback()
|
||||||
|
|| addr.is_multicast()
|
||||||
|
}
|
||||||
|
IpAddr::V6(addr) => {
|
||||||
|
addr.is_loopback()
|
||||||
|
|| addr.is_multicast()
|
||||||
|
|| ((addr.segments()[0] & 0xfe00) == 0xfc00) // is_unique_local
|
||||||
|
|| ((addr.segments()[0] & 0xffc0) == 0xfe80) // is_unicast_link_local
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if invalid_ip {
|
||||||
|
return Err(Error::UrlVerificationError(
|
||||||
|
"Localhost is only allowed in debug mode",
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.url_verifier.verify(url).await?;
|
self.url_verifier.verify(url).await?;
|
||||||
|
|
|
@ -78,6 +78,9 @@ pub enum Error {
|
||||||
/// Attempted to fetch object but the response's id field doesn't match
|
/// Attempted to fetch object but the response's id field doesn't match
|
||||||
#[error("Attempted to fetch object from {0} but the response's id field doesn't match")]
|
#[error("Attempted to fetch object from {0} but the response's id field doesn't match")]
|
||||||
FetchWrongId(Url),
|
FetchWrongId(Url),
|
||||||
|
/// I/O error from OS
|
||||||
|
#[error(transparent)]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
/// Other generic errors
|
/// Other generic errors
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
|
|
Loading…
Reference in a new issue