use std::net::IpAddr;
use actix_utils::future::{err, ok, Ready};
use actix_web::{
dev::{self, PeerAddr},
http::header::Header as _,
FromRequest, HttpRequest,
};
use crate::{fetch_cf_ips::TrustedIps, CfConnectingIp, CfConnectingIpv6};
fn bad_req(err: impl Into<String>) -> actix_web::error::Error {
actix_web::error::ErrorBadRequest(format!("TrustedClientIp error: {}", err.into()))
}
#[derive(Debug, Clone)]
pub struct TrustedClientIp(pub IpAddr);
impl_more::forward_display!(TrustedClientIp);
impl FromRequest for TrustedClientIp {
type Error = actix_web::Error;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _pl: &mut dev::Payload) -> Self::Future {
let client_ip_hdr = match CfConnectingIp::parse(req) {
Ok(ip) => Ok(ip.ip()),
Err(_) => Err(()),
};
let client_ipv6_hdr = match CfConnectingIpv6::parse(req) {
Ok(ip) => Ok(ip.ip()),
Err(_) => Err(()),
};
let client_ip = match client_ip_hdr.or(client_ipv6_hdr) {
Ok(ip) => ip,
Err(_) => return err(bad_req("cf-connecting-ip header not present")),
};
let trusted_ips = match req.app_data::<TrustedIps>() {
Some(ips) => ips,
None => return err(bad_req("trusted IPs not in app data")),
};
let peer_ip = PeerAddr::extract(req).into_inner().unwrap().0.ip();
if trusted_ips.contains(peer_ip) {
ok(Self(client_ip))
} else {
err(bad_req("cf-connecting-ip read from untrusted peer"))
}
}
}
#[cfg(test)]
mod tests {
use actix_web::test::TestRequest;
use cidr_utils::utils::IpCidrCombiner;
use super::*;
fn sample_trusted_ips() -> TrustedIps {
let mut cidr_ranges = IpCidrCombiner::new();
cidr_ranges.push("103.21.244.0/22".parse().unwrap());
cidr_ranges.push("198.41.128.0/17".parse().unwrap());
TrustedIps { cidr_ranges }
}
#[test]
fn missing_app_data() {
let req = TestRequest::default()
.insert_header(("CF-Connecting-IP", "4.5.6.7"))
.to_http_request();
TrustedClientIp::extract(&req).into_inner().unwrap_err();
}
#[test]
fn from_untrusted_peer() {
let trusted_ips = sample_trusted_ips();
let req = TestRequest::default()
.insert_header(("CF-Connecting-IP", "4.5.6.7"))
.peer_addr("10.0.1.1:27432".parse().unwrap())
.app_data(trusted_ips)
.to_http_request();
TrustedClientIp::extract(&req).into_inner().unwrap_err();
}
#[test]
fn from_trusted_peer() {
let trusted_ips = sample_trusted_ips();
let req = TestRequest::default()
.insert_header(("CF-Connecting-IP", "4.5.6.7"))
.peer_addr("103.21.244.0:27432".parse().unwrap())
.app_data(trusted_ips)
.to_http_request();
TrustedClientIp::extract(&req).into_inner().unwrap();
}
#[test]
fn from_additional_trusted_peer() {
let trusted_ips = sample_trusted_ips().add_ip_range("10.0.1.0/24".parse().unwrap());
let req = TestRequest::default()
.insert_header(("CF-Connecting-IP", "4.5.6.7"))
.peer_addr("10.0.1.1:27432".parse().unwrap())
.app_data(trusted_ips)
.to_http_request();
TrustedClientIp::extract(&req).into_inner().unwrap();
}
}