mirror of
https://github.com/actix/actix-web.git
synced 2024-12-16 21:26:34 +00:00
feat(web): add trusted proxies feature
This commit is contained in:
parent
002c1b5a19
commit
f4476fbd0d
9 changed files with 457 additions and 85 deletions
|
@ -5,6 +5,7 @@
|
||||||
- On Windows, an error is now returned from `HttpServer::bind()` (or TLS variants) when binding to a socket that's already in use.
|
- On Windows, an error is now returned from `HttpServer::bind()` (or TLS variants) when binding to a socket that's already in use.
|
||||||
- Update `brotli` dependency to `7`.
|
- Update `brotli` dependency to `7`.
|
||||||
- Minimum supported Rust version (MSRV) is now 1.75.
|
- Minimum supported Rust version (MSRV) is now 1.75.
|
||||||
|
- Add trusted proxies features to allow for customizing the list of trusted proxies and headers for determining host, scheme and ip of the `ConnectionInfo` object.
|
||||||
|
|
||||||
## 4.9.0
|
## 4.9.0
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ futures-core = { version = "0.3.17", default-features = false }
|
||||||
futures-util = { version = "0.3.17", default-features = false }
|
futures-util = { version = "0.3.17", default-features = false }
|
||||||
itoa = "1"
|
itoa = "1"
|
||||||
impl-more = "0.1.4"
|
impl-more = "0.1.4"
|
||||||
|
ipnet = "2.10.1"
|
||||||
language-tags = "0.3"
|
language-tags = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
|
|
@ -14,6 +14,7 @@ use crate::{
|
||||||
AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
|
AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
|
||||||
ServiceRequest, ServiceResponse,
|
ServiceRequest, ServiceResponse,
|
||||||
},
|
},
|
||||||
|
trusted_proxies::TrustedProxies,
|
||||||
};
|
};
|
||||||
|
|
||||||
type Guards = Vec<Box<dyn Guard>>;
|
type Guards = Vec<Box<dyn Guard>>;
|
||||||
|
@ -112,17 +113,28 @@ pub struct AppConfig {
|
||||||
secure: bool,
|
secure: bool,
|
||||||
host: String,
|
host: String,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
|
trusted_proxies: TrustedProxies,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
pub(crate) fn new(secure: bool, host: String, addr: SocketAddr) -> Self {
|
pub(crate) fn new(
|
||||||
AppConfig { secure, host, addr }
|
secure: bool,
|
||||||
|
host: String,
|
||||||
|
addr: SocketAddr,
|
||||||
|
trusted_proxies: TrustedProxies,
|
||||||
|
) -> Self {
|
||||||
|
AppConfig {
|
||||||
|
secure,
|
||||||
|
host,
|
||||||
|
addr,
|
||||||
|
trusted_proxies,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Needed in actix-test crate. Semver exempt.
|
/// Needed in actix-test crate. Semver exempt.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
|
pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
|
||||||
AppConfig::new(secure, host, addr)
|
AppConfig::new(secure, host, addr, TrustedProxies::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Server host name.
|
/// Server host name.
|
||||||
|
@ -146,6 +158,17 @@ impl AppConfig {
|
||||||
self.addr
|
self.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the trusted proxies list.
|
||||||
|
pub fn trusted_proxies(&self) -> &TrustedProxies {
|
||||||
|
&self.trusted_proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the trusted proxies
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn set_trusted_proxies(&mut self, proxies: TrustedProxies) {
|
||||||
|
self.trusted_proxies = proxies;
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn set_host(&mut self, host: &str) {
|
pub(crate) fn set_host(&mut self, host: &str) {
|
||||||
host.clone_into(&mut self.host);
|
host.clone_into(&mut self.host);
|
||||||
|
@ -168,6 +191,7 @@ impl Default for AppConfig {
|
||||||
false,
|
false,
|
||||||
"localhost:8080".to_owned(),
|
"localhost:8080".to_owned(),
|
||||||
"127.0.0.1:8080".parse().unwrap(),
|
"127.0.0.1:8080".parse().unwrap(),
|
||||||
|
TrustedProxies::default(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use std::{convert::Infallible, net::SocketAddr};
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
net::{IpAddr, SocketAddr},
|
||||||
|
};
|
||||||
|
|
||||||
use actix_utils::future::{err, ok, Ready};
|
use actix_utils::future::{err, ok, Ready};
|
||||||
use derive_more::derive::{Display, Error};
|
use derive_more::derive::{Display, Error};
|
||||||
|
@ -6,16 +9,12 @@ use derive_more::derive::{Display, Error};
|
||||||
use crate::{
|
use crate::{
|
||||||
dev::{AppConfig, Payload, RequestHead},
|
dev::{AppConfig, Payload, RequestHead},
|
||||||
http::{
|
http::{
|
||||||
header::{self, HeaderName},
|
header,
|
||||||
uri::{Authority, Scheme},
|
uri::{Authority, Scheme},
|
||||||
},
|
},
|
||||||
FromRequest, HttpRequest, ResponseError,
|
FromRequest, HttpRequest, ResponseError,
|
||||||
};
|
};
|
||||||
|
|
||||||
static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
|
|
||||||
static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host");
|
|
||||||
static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto");
|
|
||||||
|
|
||||||
/// Trim whitespace then any quote marks.
|
/// Trim whitespace then any quote marks.
|
||||||
fn unquote(val: &str) -> &str {
|
fn unquote(val: &str) -> &str {
|
||||||
val.trim().trim_start_matches('"').trim_end_matches('"')
|
val.trim().trim_start_matches('"').trim_end_matches('"')
|
||||||
|
@ -35,11 +34,25 @@ fn bare_address(val: &str) -> &str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts and trims first value for given header name.
|
/// Extract default host from request or server configuration.
|
||||||
fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> {
|
fn default_host<'a>(req: &'a RequestHead, cfg: &'a AppConfig) -> &'a str {
|
||||||
let hdr = req.headers.get(name)?.to_str().ok()?;
|
req.headers
|
||||||
let val = hdr.split(',').next()?.trim();
|
.get(&header::HOST)
|
||||||
Some(val)
|
.and_then(|v| v.to_str().ok())
|
||||||
|
// skip host header if HTTP/2, we should use :authority instead
|
||||||
|
.filter(|_| req.version < actix_http::Version::HTTP_2)
|
||||||
|
.or_else(|| req.uri.authority().map(Authority::as_str))
|
||||||
|
// @TODO can we get the sni host if in secure context ?
|
||||||
|
.unwrap_or_else(|| cfg.host())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract default scheme from request or server configuration.
|
||||||
|
fn default_scheme<'a>(req: &'a RequestHead, cfg: &'a AppConfig) -> &'a str {
|
||||||
|
req.uri
|
||||||
|
.scheme()
|
||||||
|
.map(Scheme::as_str)
|
||||||
|
.or_else(|| Some("https").filter(|_| cfg.secure()))
|
||||||
|
.unwrap_or("http")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// HTTP connection information.
|
/// HTTP connection information.
|
||||||
|
@ -70,6 +83,9 @@ fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<
|
||||||
/// If the older, related headers are also present (eg. `X-Forwarded-For`), then `Forwarded`
|
/// If the older, related headers are also present (eg. `X-Forwarded-For`), then `Forwarded`
|
||||||
/// is preferred.
|
/// is preferred.
|
||||||
///
|
///
|
||||||
|
/// Header are parsed only if the peer address is trusted and the header is trusted, otherwise the
|
||||||
|
/// request is considered to be direct and the headers are ignored.
|
||||||
|
///
|
||||||
/// [rfc7239]: https://datatracker.ietf.org/doc/html/rfc7239
|
/// [rfc7239]: https://datatracker.ietf.org/doc/html/rfc7239
|
||||||
/// [rfc7239-62]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.2
|
/// [rfc7239-62]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.2
|
||||||
/// [rfc7239-63]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.3
|
/// [rfc7239-63]: https://datatracker.ietf.org/doc/html/rfc7239#section-6.3
|
||||||
|
@ -83,67 +99,151 @@ pub struct ConnectionInfo {
|
||||||
|
|
||||||
impl ConnectionInfo {
|
impl ConnectionInfo {
|
||||||
pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
|
pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
|
||||||
|
let (host, scheme, peer_addr, realip_remote_addr) =
|
||||||
|
match req.peer_addr.map(|addr| addr.ip()) {
|
||||||
|
// since we don't have a peer address, we can't determine the real IP and we cannot trust any headers
|
||||||
|
// set the host and scheme to the server's configuration
|
||||||
|
None => (
|
||||||
|
default_host(req, cfg).to_string(),
|
||||||
|
default_scheme(req, cfg).to_string(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
Some(ip) => {
|
||||||
|
if !cfg.trusted_proxies().trust_ip(&ip) {
|
||||||
|
// if the peer address is not trusted, we can't trust the headers
|
||||||
|
// set the host and scheme to the server's configuration
|
||||||
|
(
|
||||||
|
default_host(req, cfg).to_string(),
|
||||||
|
default_scheme(req, cfg).to_string(),
|
||||||
|
Some(ip.to_string()),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// if the peer address is trusted, we can start to check trusted header to get correct information
|
||||||
|
|
||||||
let mut host = None;
|
let mut host = None;
|
||||||
let mut scheme = None;
|
let mut scheme = None;
|
||||||
let mut realip_remote_addr = None;
|
let mut realip_remote_addr = None;
|
||||||
|
|
||||||
for (name, val) in req
|
// first check the forwarded header if it is trusted
|
||||||
|
if cfg.trusted_proxies().trust_header(&header::FORWARDED) {
|
||||||
|
// quote from RFC 7239:
|
||||||
|
// A proxy server that wants to add a new "Forwarded" header field value
|
||||||
|
// can either append it to the last existing "Forwarded" header field
|
||||||
|
// after a comma separator or add a new field at the end of the header
|
||||||
|
// block.
|
||||||
|
// --- https://datatracker.ietf.org/doc/html/rfc7239#section-4
|
||||||
|
// so we get the values in reverse order as we want to get the first untrusted value
|
||||||
|
let forwarded_list = req
|
||||||
.headers
|
.headers
|
||||||
.get_all(&header::FORWARDED)
|
.get_all(&header::FORWARDED)
|
||||||
.filter_map(|hdr| hdr.to_str().ok())
|
.filter_map(|hdr| hdr.to_str().ok())
|
||||||
// "for=1.2.3.4, for=5.6.7.8; scheme=https"
|
// "for=1.2.3.4, for=5.6.7.8; scheme=https"
|
||||||
.flat_map(|val| val.split(';'))
|
|
||||||
// ["for=1.2.3.4, for=5.6.7.8", " scheme=https"]
|
|
||||||
.flat_map(|vals| vals.split(','))
|
.flat_map(|vals| vals.split(','))
|
||||||
// ["for=1.2.3.4", " for=5.6.7.8", " scheme=https"]
|
// ["for=1.2.3.4", "for=5.6.7.8; scheme=https"]
|
||||||
.flat_map(|pair| {
|
.rev();
|
||||||
let mut items = pair.trim().splitn(2, '=');
|
|
||||||
Some((items.next()?, items.next()?))
|
|
||||||
})
|
|
||||||
{
|
|
||||||
// [(name , val ), ... ]
|
|
||||||
// [("for", "1.2.3.4"), ("for", "5.6.7.8"), ("scheme", "https")]
|
|
||||||
|
|
||||||
// taking the first value for each property is correct because spec states that first
|
'forwaded: for forwarded in forwarded_list {
|
||||||
// "for" value is client and rest are proxies; multiple values other properties have
|
for (key, value) in forwarded.split(';').map(|item| {
|
||||||
// no defined semantics
|
let mut kv = item.splitn(2, '=');
|
||||||
//
|
|
||||||
// > In a chain of proxy servers where this is fully utilized, the first
|
|
||||||
// > "for" parameter will disclose the client where the request was first
|
|
||||||
// > made, followed by any subsequent proxy identifiers.
|
|
||||||
// --- https://datatracker.ietf.org/doc/html/rfc7239#section-5.2
|
|
||||||
|
|
||||||
match name.trim().to_lowercase().as_str() {
|
(
|
||||||
"for" => realip_remote_addr.get_or_insert_with(|| bare_address(unquote(val))),
|
kv.next().map(|s| s.trim()).unwrap_or_default(),
|
||||||
"proto" => scheme.get_or_insert_with(|| unquote(val)),
|
kv.next().map(|s| unquote(s.trim())).unwrap_or_default(),
|
||||||
"host" => host.get_or_insert_with(|| unquote(val)),
|
)
|
||||||
|
}) {
|
||||||
|
match key.to_lowercase().as_str() {
|
||||||
|
"for" => {
|
||||||
|
if let Ok(ip) = bare_address(value).parse::<IpAddr>() {
|
||||||
|
if cfg.trusted_proxies().trust_ip(&ip) {
|
||||||
|
host = None;
|
||||||
|
scheme = None;
|
||||||
|
realip_remote_addr = None;
|
||||||
|
|
||||||
|
continue 'forwaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realip_remote_addr = Some(bare_address(value));
|
||||||
|
}
|
||||||
|
"proto" => {
|
||||||
|
scheme = Some(value);
|
||||||
|
}
|
||||||
|
"host" => {
|
||||||
|
host = Some(value);
|
||||||
|
}
|
||||||
"by" => {
|
"by" => {
|
||||||
// TODO: implement https://datatracker.ietf.org/doc/html/rfc7239#section-5.1
|
// TODO: implement https://datatracker.ietf.org/doc/html/rfc7239#section-5.1
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break 'forwaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if realip_remote_addr.is_none()
|
||||||
|
&& cfg.trusted_proxies().trust_header(&header::X_FORWARDED_FOR)
|
||||||
|
{
|
||||||
|
for value in req
|
||||||
|
.headers
|
||||||
|
.get_all(&header::X_FORWARDED_FOR)
|
||||||
|
.filter_map(|hdr| hdr.to_str().ok())
|
||||||
|
.flat_map(|vals| vals.split(','))
|
||||||
|
.rev()
|
||||||
|
{
|
||||||
|
if let Ok(ip) = bare_address(value).parse::<IpAddr>() {
|
||||||
|
if cfg.trusted_proxies().trust_ip(&ip) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let scheme = scheme
|
realip_remote_addr = Some(bare_address(value));
|
||||||
.or_else(|| first_header_value(req, &X_FORWARDED_PROTO))
|
break;
|
||||||
.or_else(|| req.uri.scheme().map(Scheme::as_str))
|
}
|
||||||
.or_else(|| Some("https").filter(|_| cfg.secure()))
|
}
|
||||||
.unwrap_or("http")
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let host = host
|
if host.is_none()
|
||||||
.or_else(|| first_header_value(req, &X_FORWARDED_HOST))
|
&& cfg
|
||||||
.or_else(|| req.headers.get(&header::HOST)?.to_str().ok())
|
.trusted_proxies()
|
||||||
.or_else(|| req.uri.authority().map(Authority::as_str))
|
.trust_header(&header::X_FORWARDED_HOST)
|
||||||
.unwrap_or_else(|| cfg.host())
|
{
|
||||||
.to_owned();
|
host = req
|
||||||
|
.headers
|
||||||
|
.get_all(&header::X_FORWARDED_HOST)
|
||||||
|
.filter_map(|hdr| hdr.to_str().ok())
|
||||||
|
.flat_map(|vals| vals.split(','))
|
||||||
|
.rev()
|
||||||
|
.next();
|
||||||
|
}
|
||||||
|
|
||||||
let realip_remote_addr = realip_remote_addr
|
if scheme.is_none()
|
||||||
.or_else(|| first_header_value(req, &X_FORWARDED_FOR))
|
&& cfg
|
||||||
.map(str::to_owned);
|
.trusted_proxies()
|
||||||
|
.trust_header(&header::X_FORWARDED_PROTO)
|
||||||
|
{
|
||||||
|
scheme = req
|
||||||
|
.headers
|
||||||
|
.get_all(&header::X_FORWARDED_PROTO)
|
||||||
|
.filter_map(|hdr| hdr.to_str().ok())
|
||||||
|
.flat_map(|vals| vals.split(','))
|
||||||
|
.rev()
|
||||||
|
.next();
|
||||||
|
}
|
||||||
|
|
||||||
let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string());
|
(
|
||||||
|
host.unwrap_or_else(|| default_host(req, cfg)).to_string(),
|
||||||
|
scheme
|
||||||
|
.unwrap_or_else(|| default_scheme(req, cfg))
|
||||||
|
.to_string(),
|
||||||
|
Some(ip.to_string()),
|
||||||
|
realip_remote_addr.map(|s| s.to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ConnectionInfo {
|
ConnectionInfo {
|
||||||
host,
|
host,
|
||||||
|
@ -270,7 +370,7 @@ impl FromRequest for PeerAddr {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::TestRequest;
|
use crate::{test::TestRequest, trusted_proxies::TrustedProxies};
|
||||||
|
|
||||||
const X_FORWARDED_FOR: &str = "x-forwarded-for";
|
const X_FORWARDED_FOR: &str = "x-forwarded-for";
|
||||||
const X_FORWARDED_HOST: &str = "x-forwarded-host";
|
const X_FORWARDED_HOST: &str = "x-forwarded-host";
|
||||||
|
@ -297,8 +397,15 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn x_forwarded_for_header() {
|
fn x_forwarded_for_header_trusted() {
|
||||||
|
let mut trusted_proxies = TrustedProxies::new_local();
|
||||||
|
trusted_proxies.add_trusted_header(header::X_FORWARDED_FOR);
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.set_trusted_proxies(trusted_proxies)
|
||||||
.insert_header((X_FORWARDED_FOR, "192.0.2.60"))
|
.insert_header((X_FORWARDED_FOR, "192.0.2.60"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -306,18 +413,77 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn x_forwarded_host_header() {
|
fn x_forwarded_for_header_trusted_multiple() {
|
||||||
|
let mut trusted_proxies = TrustedProxies::new_local();
|
||||||
|
trusted_proxies
|
||||||
|
.add_trusted_proxy("192.0.2.60")
|
||||||
|
.expect("failed to add trusted proxy");
|
||||||
|
trusted_proxies.add_trusted_header(header::X_FORWARDED_FOR);
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.set_trusted_proxies(trusted_proxies)
|
||||||
|
.append_header((X_FORWARDED_FOR, "240.10.56.47"))
|
||||||
|
.append_header((X_FORWARDED_FOR, "192.0.2.60"))
|
||||||
|
.to_http_request();
|
||||||
|
let info = req.connection_info();
|
||||||
|
assert_eq!(info.realip_remote_addr(), Some("240.10.56.47"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn x_forwarded_for_header_untrusted() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.insert_header((X_FORWARDED_FOR, "192.0.2.60"))
|
||||||
|
.to_http_request();
|
||||||
|
let info = req.connection_info();
|
||||||
|
assert_eq!(info.realip_remote_addr(), Some("127.0.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn x_forwarded_host_header_trusted() {
|
||||||
|
let mut trusted_proxies = TrustedProxies::new_local();
|
||||||
|
trusted_proxies.add_trusted_header(header::X_FORWARDED_HOST);
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.set_trusted_proxies(trusted_proxies)
|
||||||
.insert_header((X_FORWARDED_HOST, "192.0.2.60"))
|
.insert_header((X_FORWARDED_HOST, "192.0.2.60"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
assert_eq!(info.host(), "192.0.2.60");
|
assert_eq!(info.host(), "192.0.2.60");
|
||||||
assert_eq!(info.realip_remote_addr(), None);
|
assert_eq!(info.realip_remote_addr(), Some("127.0.0.1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn x_forwarded_proto_header() {
|
fn x_forwarded_host_header_untrusted() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.insert_header((X_FORWARDED_HOST, "192.0.2.60"))
|
||||||
|
.to_http_request();
|
||||||
|
let info = req.connection_info();
|
||||||
|
assert_eq!(info.host(), "localhost:8080");
|
||||||
|
assert_eq!(info.realip_remote_addr(), Some("127.0.0.1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn x_forwarded_proto_header_trusted() {
|
||||||
|
let mut trusted_proxies = TrustedProxies::new_local();
|
||||||
|
trusted_proxies.add_trusted_header(header::X_FORWARDED_PROTO);
|
||||||
|
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.set_trusted_proxies(trusted_proxies)
|
||||||
.insert_header((X_FORWARDED_PROTO, "https"))
|
.insert_header((X_FORWARDED_PROTO, "https"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -325,8 +491,21 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_header() {
|
fn x_forwarded_proto_header_untrusted() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
|
.insert_header((X_FORWARDED_PROTO, "https"))
|
||||||
|
.to_http_request();
|
||||||
|
let info = req.connection_info();
|
||||||
|
assert_eq!(info.scheme(), "http");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forwarded_header() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((
|
.insert_header((
|
||||||
header::FORWARDED,
|
header::FORWARDED,
|
||||||
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
||||||
|
@ -339,6 +518,7 @@ mod tests {
|
||||||
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((
|
.insert_header((
|
||||||
header::FORWARDED,
|
header::FORWARDED,
|
||||||
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org",
|
||||||
|
@ -353,7 +533,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_case_sensitivity() {
|
fn forwarded_case_sensitivity() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, "For=192.0.2.60"))
|
.insert_header((header::FORWARDED, "For=192.0.2.60"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -362,7 +544,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_weird_whitespace() {
|
fn forwarded_weird_whitespace() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, "for= 1.2.3.4; proto= https"))
|
.insert_header((header::FORWARDED, "for= 1.2.3.4; proto= https"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -370,6 +554,7 @@ mod tests {
|
||||||
assert_eq!(info.scheme(), "https");
|
assert_eq!(info.scheme(), "https");
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, " for = 1.2.3.4 "))
|
.insert_header((header::FORWARDED, " for = 1.2.3.4 "))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -378,7 +563,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_for_quoted() {
|
fn forwarded_for_quoted() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
|
.insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -387,7 +574,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_for_ipv6() {
|
fn forwarded_for_ipv6() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
|
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -396,7 +585,9 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_for_ipv6_with_port() {
|
fn forwarded_for_ipv6_with_port() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
|
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
|
@ -405,11 +596,29 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn forwarded_for_multiple() {
|
fn forwarded_for_multiple() {
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17"))
|
.insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17"))
|
||||||
.to_http_request();
|
.to_http_request();
|
||||||
let info = req.connection_info();
|
let info = req.connection_info();
|
||||||
// takes the first value
|
|
||||||
|
// takes the last untrusted value
|
||||||
|
assert_eq!(info.realip_remote_addr(), Some("198.51.100.17"));
|
||||||
|
|
||||||
|
let mut trusted_proxies = TrustedProxies::new_local();
|
||||||
|
trusted_proxies
|
||||||
|
.add_trusted_proxy("198.51.100.17")
|
||||||
|
.expect("Failed to add trusted proxy");
|
||||||
|
|
||||||
|
let req = TestRequest::default()
|
||||||
|
.set_trusted_proxies(trusted_proxies)
|
||||||
|
.peer_addr(addr)
|
||||||
|
.insert_header((header::FORWARDED, "for=192.0.2.60, for=198.51.100.17"))
|
||||||
|
.to_http_request();
|
||||||
|
let info = req.connection_info();
|
||||||
|
|
||||||
|
// takes the last untrusted value
|
||||||
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ mod server;
|
||||||
mod service;
|
mod service;
|
||||||
pub mod test;
|
pub mod test;
|
||||||
mod thin_data;
|
mod thin_data;
|
||||||
|
pub mod trusted_proxies;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
pub mod web;
|
pub mod web;
|
||||||
|
|
||||||
|
|
|
@ -898,8 +898,10 @@ mod tests {
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_remote_addr_format() {
|
async fn test_remote_addr_format() {
|
||||||
let mut format = Format::new("%{r}a");
|
let mut format = Format::new("%{r}a");
|
||||||
|
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||||
|
|
||||||
let req = TestRequest::default()
|
let req = TestRequest::default()
|
||||||
|
.peer_addr(addr)
|
||||||
.insert_header((
|
.insert_header((
|
||||||
header::FORWARDED,
|
header::FORWARDED,
|
||||||
header::HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43"),
|
header::HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43"),
|
||||||
|
|
|
@ -17,7 +17,7 @@ use actix_service::{
|
||||||
#[cfg(feature = "openssl")]
|
#[cfg(feature = "openssl")]
|
||||||
use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorBuilder};
|
use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorBuilder};
|
||||||
|
|
||||||
use crate::{config::AppConfig, Error};
|
use crate::{config::AppConfig, trusted_proxies::TrustedProxies, Error};
|
||||||
|
|
||||||
struct Socket {
|
struct Socket {
|
||||||
scheme: &'static str,
|
scheme: &'static str,
|
||||||
|
@ -26,6 +26,7 @@ struct Socket {
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
host: Option<String>,
|
host: Option<String>,
|
||||||
|
trusted_proxies: TrustedProxies,
|
||||||
keep_alive: KeepAlive,
|
keep_alive: KeepAlive,
|
||||||
client_request_timeout: Duration,
|
client_request_timeout: Duration,
|
||||||
client_disconnect_timeout: Duration,
|
client_disconnect_timeout: Duration,
|
||||||
|
@ -110,6 +111,7 @@ where
|
||||||
factory,
|
factory,
|
||||||
config: Arc::new(Mutex::new(Config {
|
config: Arc::new(Mutex::new(Config {
|
||||||
host: None,
|
host: None,
|
||||||
|
trusted_proxies: TrustedProxies::new_local(),
|
||||||
keep_alive: KeepAlive::default(),
|
keep_alive: KeepAlive::default(),
|
||||||
client_request_timeout: Duration::from_secs(5),
|
client_request_timeout: Duration::from_secs(5),
|
||||||
client_disconnect_timeout: Duration::from_secs(1),
|
client_disconnect_timeout: Duration::from_secs(1),
|
||||||
|
@ -142,6 +144,14 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets server trusted proxies configuration.
|
||||||
|
///
|
||||||
|
/// By default server trusts loopback, link-local, and private networks and Forwarded Header.
|
||||||
|
pub fn trusted_proxies(self, val: TrustedProxies) -> Self {
|
||||||
|
self.config.lock().unwrap().trusted_proxies = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets server keep-alive preference.
|
/// Sets server keep-alive preference.
|
||||||
///
|
///
|
||||||
/// By default keep-alive is set to 5 seconds.
|
/// By default keep-alive is set to 5 seconds.
|
||||||
|
@ -526,6 +536,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let cfg = cfg.lock().unwrap();
|
let cfg = cfg.lock().unwrap();
|
||||||
let host = cfg.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = cfg.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = cfg.trusted_proxies.clone();
|
||||||
|
|
||||||
let mut svc = HttpService::build()
|
let mut svc = HttpService::build()
|
||||||
.keep_alive(cfg.keep_alive)
|
.keep_alive(cfg.keep_alive)
|
||||||
|
@ -543,7 +554,7 @@ where
|
||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(false, host.clone(), addr)
|
AppConfig::new(false, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.tcp()
|
.tcp()
|
||||||
})?;
|
})?;
|
||||||
|
@ -570,6 +581,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let cfg = cfg.lock().unwrap();
|
let cfg = cfg.lock().unwrap();
|
||||||
let host = cfg.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = cfg.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = cfg.trusted_proxies.clone();
|
||||||
|
|
||||||
let mut svc = HttpService::build()
|
let mut svc = HttpService::build()
|
||||||
.keep_alive(cfg.keep_alive)
|
.keep_alive(cfg.keep_alive)
|
||||||
|
@ -587,7 +599,7 @@ where
|
||||||
.map_err(|err| err.into().error_response());
|
.map_err(|err| err.into().error_response());
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(false, host.clone(), addr)
|
AppConfig::new(false, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.tcp_auto_h2c()
|
.tcp_auto_h2c()
|
||||||
})?;
|
})?;
|
||||||
|
@ -646,6 +658,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = c.trusted_proxies.clone();
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -668,7 +681,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(true, host.clone(), addr)
|
AppConfig::new(true, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.rustls_with_config(config.clone(), acceptor_config)
|
.rustls_with_config(config.clone(), acceptor_config)
|
||||||
})?;
|
})?;
|
||||||
|
@ -697,6 +710,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = c.trusted_proxies.clone();
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -719,7 +733,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(true, host.clone(), addr)
|
AppConfig::new(true, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.rustls_021_with_config(config.clone(), acceptor_config)
|
.rustls_021_with_config(config.clone(), acceptor_config)
|
||||||
})?;
|
})?;
|
||||||
|
@ -763,6 +777,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = c.trusted_proxies.clone();
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -785,7 +800,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(true, host.clone(), addr)
|
AppConfig::new(true, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.rustls_0_22_with_config(config.clone(), acceptor_config)
|
.rustls_0_22_with_config(config.clone(), acceptor_config)
|
||||||
})?;
|
})?;
|
||||||
|
@ -829,6 +844,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = c.trusted_proxies.clone();
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -851,7 +867,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(true, host.clone(), addr)
|
AppConfig::new(true, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.rustls_0_23_with_config(config.clone(), acceptor_config)
|
.rustls_0_23_with_config(config.clone(), acceptor_config)
|
||||||
})?;
|
})?;
|
||||||
|
@ -894,6 +910,7 @@ where
|
||||||
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
.listen(format!("actix-web-service-{}", addr), lst, move || {
|
||||||
let c = cfg.lock().unwrap();
|
let c = cfg.lock().unwrap();
|
||||||
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
|
||||||
|
let trusted_proxies = c.trusted_proxies.clone();
|
||||||
|
|
||||||
let svc = HttpService::build()
|
let svc = HttpService::build()
|
||||||
.keep_alive(c.keep_alive)
|
.keep_alive(c.keep_alive)
|
||||||
|
@ -919,7 +936,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
svc.finish(map_config(fac, move |_| {
|
svc.finish(map_config(fac, move |_| {
|
||||||
AppConfig::new(true, host.clone(), addr)
|
AppConfig::new(true, host.clone(), addr, trusted_proxies.clone())
|
||||||
}))
|
}))
|
||||||
.openssl_with_config(acceptor.clone(), acceptor_config)
|
.openssl_with_config(acceptor.clone(), acceptor_config)
|
||||||
})?;
|
})?;
|
||||||
|
@ -956,6 +973,7 @@ where
|
||||||
false,
|
false,
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
||||||
socket_addr,
|
socket_addr,
|
||||||
|
c.trusted_proxies.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let fac = factory()
|
let fac = factory()
|
||||||
|
@ -1001,6 +1019,7 @@ where
|
||||||
false,
|
false,
|
||||||
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)),
|
||||||
socket_addr,
|
socket_addr,
|
||||||
|
c.trusted_proxies.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({
|
fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then({
|
||||||
|
|
|
@ -5,6 +5,8 @@ use serde::Serialize;
|
||||||
|
|
||||||
#[cfg(feature = "cookies")]
|
#[cfg(feature = "cookies")]
|
||||||
use crate::cookie::{Cookie, CookieJar};
|
use crate::cookie::{Cookie, CookieJar};
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::trusted_proxies::TrustedProxies;
|
||||||
use crate::{
|
use crate::{
|
||||||
app_service::AppInitServiceState,
|
app_service::AppInitServiceState,
|
||||||
config::AppConfig,
|
config::AppConfig,
|
||||||
|
@ -229,6 +231,13 @@ impl TestRequest {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the trusted proxies for this request.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn set_trusted_proxies(mut self, trusted_proxies: TrustedProxies) -> Self {
|
||||||
|
self.config.set_trusted_proxies(trusted_proxies);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Finalizes test request.
|
/// Finalizes test request.
|
||||||
///
|
///
|
||||||
/// This request builder will be useless after calling `finish()`.
|
/// This request builder will be useless after calling `finish()`.
|
||||||
|
|
106
actix-web/src/trusted_proxies.rs
Normal file
106
actix-web/src/trusted_proxies.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use actix_http::header::{HeaderName, FORWARDED};
|
||||||
|
use ipnet::{AddrParseError, IpNet};
|
||||||
|
|
||||||
|
/// TrustedProxies is a helper struct to manage trusted proxies and headers
|
||||||
|
///
|
||||||
|
/// This is used to determine if information from a request can be trusted or not.
|
||||||
|
///
|
||||||
|
/// By default, it trusts the following:
|
||||||
|
/// - IPV4 Loopback
|
||||||
|
/// - IPV4 Private Networks
|
||||||
|
/// - IPV6 Loopback
|
||||||
|
/// - IPV6 Private Networks
|
||||||
|
///
|
||||||
|
/// It also trusts the `FORWARDED` header by default.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// use actix_web::trusted_proxies::TrustedProxies;
|
||||||
|
/// use actix_web::{web, App, HttpResponse, HttpServer};
|
||||||
|
///
|
||||||
|
/// let mut trusted_proxies = TrustedProxies::new_local();
|
||||||
|
/// trusted_proxies.add_trusted_proxy("168.10.0.0/16").unwrap();
|
||||||
|
/// trusted_proxies.add_trusted_header("X-Forwarded-For".parse().unwrap());
|
||||||
|
///
|
||||||
|
/// HttpServer::new(|| {
|
||||||
|
/// App::new()
|
||||||
|
/// .service(web::resource("/").to(|| async { "hello world" }))
|
||||||
|
/// }).trusted_proxies(trusted_proxies);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TrustedProxies(Vec<IpNet>, Vec<HeaderName>);
|
||||||
|
|
||||||
|
impl Default for TrustedProxies {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_local()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TrustedProxies {
|
||||||
|
/// Create a new TrustedProxies instance with no trusted proxies or headers
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(vec![], vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new TrustedProxies instance with local and private networks and FORWARDED header trusted
|
||||||
|
pub fn new_local() -> Self {
|
||||||
|
Self(
|
||||||
|
vec![
|
||||||
|
// IPV4 Loopback
|
||||||
|
"127.0.0.0/8".parse().unwrap(),
|
||||||
|
// IPV4 Private Networks
|
||||||
|
"10.0.0.0/8".parse().unwrap(),
|
||||||
|
"172.16.0.0/12".parse().unwrap(),
|
||||||
|
"192.168.0.0/16".parse().unwrap(),
|
||||||
|
// IPV6 Loopback
|
||||||
|
"::1/128".parse().unwrap(),
|
||||||
|
// IPV6 Private network
|
||||||
|
"fd00::/8".parse().unwrap(),
|
||||||
|
],
|
||||||
|
vec![FORWARDED],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a trusted header to the list of trusted headers
|
||||||
|
pub fn add_trusted_header(&mut self, header: HeaderName) {
|
||||||
|
self.1.push(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a trusted proxy to the list of trusted proxies
|
||||||
|
///
|
||||||
|
/// proxy can be an IP address or a CIDR
|
||||||
|
pub fn add_trusted_proxy(&mut self, proxy: &str) -> Result<(), AddrParseError> {
|
||||||
|
match proxy.parse() {
|
||||||
|
Ok(v) => {
|
||||||
|
self.0.push(v);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => match proxy.parse::<IpAddr>() {
|
||||||
|
Ok(v) => {
|
||||||
|
self.0.push(IpNet::from(v));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a remote address is trusted given the list of trusted proxies
|
||||||
|
pub fn trust_ip(&self, remote_addr: &IpAddr) -> bool {
|
||||||
|
for proxy in &self.0 {
|
||||||
|
if proxy.contains(remote_addr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a header is trusted
|
||||||
|
pub fn trust_header(&self, header: &HeaderName) -> bool {
|
||||||
|
self.1.contains(header)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue