diff --git a/src/config.rs b/src/config.rs index b76c484..bf48e76 100644 --- a/src/config.rs +++ b/src/config.rs @@ -23,6 +23,7 @@ use crate::{ use async_trait::async_trait; use derive_builder::Builder; use dyn_clone::{clone_trait_object, DynClone}; +use itertools::Itertools; use moka::future::Cache; use once_cell::sync::Lazy; use regex::Regex; @@ -185,27 +186,26 @@ impl FederationConfig { // 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.to_owned(), 80)) - .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() + let mut ips = lookup_host((domain.to_owned(), 80)).await?; + let allow_local = std::env::var("DANGER_FEDERATION_ALLOW_LOCAL_IP").is_ok(); + let invalid_ip = !allow_local + && ips.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", - )); + let ip_addrs = ips.join(", "); + return Err(Error::DomainResolveError(domain.to_string(), ip_addrs)); } } diff --git a/src/error.rs b/src/error.rs index 4a53fd8..643a1e6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { /// url verification error #[error("URL failed verification: {0}")] UrlVerificationError(&'static str), + /// Resolving domain points to local IP. + #[error("Resolving domain {0} points to local IP {1}. This may indicate an attacker attempting to access internal services. If intentional, you can ignore this error by setting DANGER_FEDERATION_ALLOW_LOCAL_IP=1")] + DomainResolveError(String, String), /// Incoming activity has invalid digest for body #[error("Incoming activity has invalid digest for body")] ActivityBodyDigestInvalid,