don't send requests to specified list of IP CIDRs

this can most definitely be improved but this is a decent attempt.
the only annoying this is i couldn't just use a Vec<IPAddress> which
would have significantly simplified all of this, but serde can't
deserialise it on the config side i guess.

i may find a better way to do this in the future, but this should cover
most areas anyways.

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-01-21 22:59:06 -05:00 committed by June
parent 71d247232d
commit fa0c083555
8 changed files with 186 additions and 3 deletions

61
Cargo.lock generated
View file

@ -400,6 +400,7 @@ dependencies = [
"hyper", "hyper",
"hyperlocal", "hyperlocal",
"image", "image",
"ipaddress",
"jsonwebtoken", "jsonwebtoken",
"lazy_static", "lazy_static",
"lru-cache", "lru-cache",
@ -1080,6 +1081,20 @@ version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
[[package]]
name = "ipaddress"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957bb9f3645d6bb7f36df99d5105b4866aa79749819d7c176a170a27dc477cbf"
dependencies = [
"lazy_static",
"libc",
"num",
"num-integer",
"num-traits",
"regex",
]
[[package]] [[package]]
name = "ipconfig" name = "ipconfig"
version = "0.3.2" version = "0.3.2"
@ -1404,6 +1419,20 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "num"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.4" version = "0.4.4"
@ -1415,6 +1444,15 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-complex"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -1425,6 +1463,29 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.17" version = "0.2.17"

View file

@ -101,6 +101,9 @@ tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_suppo
lazy_static = "1.4.0" lazy_static = "1.4.0"
async-trait = "0.1.77" async-trait = "0.1.77"
# used for checking if an IP is in specific subnets / CIDR ranges
ipaddress = "0.1.3"
sd-notify = { version = "0.4.1", optional = true } sd-notify = { version = "0.4.1", optional = true }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]

View file

@ -77,7 +77,33 @@ max_request_size = 20_000_000 # in bytes
# See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH before deciding to enable this. # See https://breachattack.com/ and https://wikipedia.org/wiki/BREACH before deciding to enable this.
zstd_compression = false zstd_compression = false
# Vector list of IPv4 and IPv6 CIDR ranges / subnets *in quotes* that you do not want conduwuit to send outbound requests to.
# Defaults to RFC1918, unroutable, loopback, multicast, and testnet addresses for security.
#
# To disable, set this to be an empty vector (`[]`).
#
# Currently this does not account for proxies in use like Synapse does.
ip_range_denylist = [
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"100.64.0.0/10",
"192.0.0.0/24",
"169.254.0.0/16",
"192.88.99.0/24",
"198.18.0.0/15",
"192.0.2.0/24",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/4",
"::1/128",
"fe80::/10",
"fc00::/7",
"2001:db8::/32",
"ff00::/8",
"fec0::/10",
]
### Moderation / Privacy / Security ### Moderation / Privacy / Security

View file

@ -11,6 +11,7 @@ use futures_util::future::TryFutureExt;
use get_profile_information::v1::ProfileField; use get_profile_information::v1::ProfileField;
use http::header::{HeaderValue, AUTHORIZATION}; use http::header::{HeaderValue, AUTHORIZATION};
use ipaddress::IPAddress;
use ruma::{ use ruma::{
api::{ api::{
client::error::{Error as RumaError, ErrorKind}, client::error::{Error as RumaError, ErrorKind},
@ -114,7 +115,6 @@ impl FedDest {
} }
} }
#[tracing::instrument(skip(request))]
pub(crate) async fn send_request<T: OutgoingRequest>( pub(crate) async fn send_request<T: OutgoingRequest>(
destination: &ServerName, destination: &ServerName,
request: T, request: T,
@ -132,6 +132,29 @@ where
)); ));
} }
if destination.is_ip_literal() {
info!("Destination is an IP literal, checking against IP range denylist.");
let ip = IPAddress::parse(destination.host()).map_err(|e| {
warn!("Failed to parse IP literal from string: {}", e);
Error::BadServerResponse("Invalid IP address")
})?;
let cidr_ranges_s = services().globals.ip_range_denylist().to_vec();
let mut cidr_ranges: Vec<IPAddress> = Vec::new();
for cidr in cidr_ranges_s {
cidr_ranges.push(IPAddress::parse(cidr).expect("we checked this at startup"));
}
for cidr in cidr_ranges {
if ip.includes(&cidr) {
return Err(Error::BadServerResponse(
"Not allowed to send requests to this IP",
));
}
}
}
debug!("Preparing to send request to {destination}"); debug!("Preparing to send request to {destination}");
let mut write_destination_to_cache = false; let mut write_destination_to_cache = false;

View file

@ -6,6 +6,7 @@ use std::{
}; };
use figment::Figment; use figment::Figment;
use ruma::{OwnedServerName, RoomVersionId}; use ruma::{OwnedServerName, RoomVersionId};
use serde::{de::IgnoredAny, Deserialize}; use serde::{de::IgnoredAny, Deserialize};
use tracing::{error, warn}; use tracing::{error, warn};
@ -128,6 +129,9 @@ pub struct Config {
#[serde(default = "Vec::new")] #[serde(default = "Vec::new")]
pub prevent_media_downloads_from: Vec<OwnedServerName>, pub prevent_media_downloads_from: Vec<OwnedServerName>,
#[serde(default = "default_ip_range_denylist")]
pub ip_range_denylist: Vec<String>,
#[serde(flatten)] #[serde(flatten)]
pub catchall: BTreeMap<String, IgnoredAny>, pub catchall: BTreeMap<String, IgnoredAny>,
} }
@ -307,6 +311,14 @@ impl fmt::Display for Config {
} }
&lst.join(", ") &lst.join(", ")
}), }),
("Outbound Request IP Range Denylist", {
let mut lst = vec![];
for item in self.ip_range_denylist.iter().cloned().enumerate() {
let (_, ip): (usize, String) = item;
lst.push(ip);
}
&lst.join(", ")
}),
]; ];
let mut msg: String = "Active config values:\n\n".to_owned(); let mut msg: String = "Active config values:\n\n".to_owned();
@ -408,3 +420,27 @@ fn default_rocksdb_max_log_file_size() -> usize {
// 4 megabytes // 4 megabytes
4 * 1024 * 1024 4 * 1024 * 1024
} }
fn default_ip_range_denylist() -> Vec<String> {
vec![
"127.0.0.0/8".to_owned(),
"10.0.0.0/8".to_owned(),
"172.16.0.0/12".to_owned(),
"192.168.0.0/16".to_owned(),
"100.64.0.0/10".to_owned(),
"192.0.0.0/24".to_owned(),
"169.254.0.0/16".to_owned(),
"192.88.99.0/24".to_owned(),
"198.18.0.0/15".to_owned(),
"192.0.2.0/24".to_owned(),
"198.51.100.0/24".to_owned(),
"203.0.113.0/24".to_owned(),
"224.0.0.0/4".to_owned(),
"::1/128".to_owned(),
"fe80::/10".to_owned(),
"fc00::/7".to_owned(),
"2001:db8::/32".to_owned(),
"ff00::/8".to_owned(),
"fec0::/10".to_owned(),
]
}

View file

@ -147,6 +147,12 @@ async fn main() {
}; };
let config = &services().globals.config; let config = &services().globals.config;
// check if user specified valid IP CIDR ranges on startup
for cidr in services().globals.ip_range_denylist() {
let _ = ipaddress::IPAddress::parse(cidr)
.map_err(|e| error!("Error parsing specified IP CIDR range: {e}"));
}
if config.allow_registration if config.allow_registration
&& !config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse && !config.yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse
{ {

View file

@ -427,6 +427,10 @@ impl Service<'_> {
&self.config.prevent_media_downloads_from &self.config.prevent_media_downloads_from
} }
pub fn ip_range_denylist(&self) -> &[String] {
&self.config.ip_range_denylist
}
pub fn supported_room_versions(&self) -> Vec<RoomVersionId> { pub fn supported_room_versions(&self) -> Vec<RoomVersionId> {
let mut room_versions: Vec<RoomVersionId> = vec![]; let mut room_versions: Vec<RoomVersionId> = vec![];
room_versions.extend(self.stable_room_versions.clone()); room_versions.extend(self.stable_room_versions.clone());

View file

@ -1,6 +1,7 @@
mod data; mod data;
pub use data::Data; pub use data::Data;
use ipaddress::IPAddress;
use std::{ use std::{
collections::{BTreeMap, HashMap, HashSet}, collections::{BTreeMap, HashMap, HashSet},
@ -43,7 +44,7 @@ use tokio::{
select, select,
sync::{mpsc, Mutex, Semaphore}, sync::{mpsc, Mutex, Semaphore},
}; };
use tracing::{debug, error, warn}; use tracing::{debug, error, info, warn};
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum OutgoingKind { pub enum OutgoingKind {
@ -716,6 +717,29 @@ impl Service {
where where
T: Debug, T: Debug,
{ {
if destination.is_ip_literal() {
info!("Destination is an IP literal, checking against IP range denylist.");
let ip = IPAddress::parse(destination.host()).map_err(|e| {
warn!("Failed to parse IP literal from string: {}", e);
Error::BadServerResponse("Invalid IP address")
})?;
let cidr_ranges_s = services().globals.ip_range_denylist().to_vec();
let mut cidr_ranges: Vec<IPAddress> = Vec::new();
for cidr in cidr_ranges_s {
cidr_ranges.push(IPAddress::parse(cidr).expect("we checked this at startup"));
}
for cidr in cidr_ranges {
if ip.includes(&cidr) {
return Err(Error::BadServerResponse(
"Not allowed to send requests to this IP",
));
}
}
}
debug!("Waiting for permit"); debug!("Waiting for permit");
let permit = self.maximum_requests.acquire().await; let permit = self.maximum_requests.acquire().await;
debug!("Got permit"); debug!("Got permit");