mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-12 19:56:48 +00:00
feat: Introduce accepted_ip_sources as a multiple options variant of request_ip_source
The current `request_ip_source` setting only allows a single option. While it does fall back to the peer IP if the header is missing as of !2003, which likely covers a lot of regular use, only allowing a single option limits the deployment options available to more advanced deployments. Setups where internal and external traffic use different reverse proxies will end up with the wrong IP and the implicitness of the fallback allows for situations where the used IP is not the IP expected. By introducing a setting that allows multiple options to be set, this limitation is resolved and it becomes possible to have client IP resolution behind different reverse proxies and also making it possible to decide if and/or what the fallback should be. If set, options are evaluated in order. If all fail, the request fails.
This commit is contained in:
1 parent
480e975877
commit
b9c6d5956f
9 files changed
+244
-132
No files matched your search
Generated
+1
-13
@@ -313,17 +313,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-client-ip"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8ba1af5b620232acf37f2eb6d22151ea465491e0b4c25f552d1990f64ec5a67"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"client-ip",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.5.6"
|
||||
@@ -896,10 +885,10 @@ dependencies = [
|
||||
"assign",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"axum-extra",
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"client-ip",
|
||||
"conduwuit_core",
|
||||
"conduwuit_macros",
|
||||
"conduwuit_service",
|
||||
@@ -1041,7 +1030,6 @@ version = "26.6.2"
|
||||
dependencies = [
|
||||
"assign",
|
||||
"axum",
|
||||
"axum-client-ip",
|
||||
"axum-server",
|
||||
"axum-server-dual-protocol",
|
||||
"bytes",
|
||||
|
||||
+2
-2
@@ -115,8 +115,8 @@ git = "https://github.com/vinchona/axum-server-dual-protocol.git"
|
||||
rev = "ca6db055254255b74238673ce4135698e347d71c" # feat!: bump axum_server to 0.8.0
|
||||
default-features = false
|
||||
|
||||
[workspace.dependencies.axum-client-ip]
|
||||
version = "1.3"
|
||||
[workspace.dependencies.client-ip]
|
||||
version = "0.2"
|
||||
|
||||
[workspace.dependencies.tower]
|
||||
version = "0.5.2"
|
||||
|
||||
+33
-2
@@ -290,7 +290,7 @@
|
||||
#
|
||||
#ip_lookup_strategy = 5
|
||||
|
||||
# The source to use for discovering the real connecting client IP.
|
||||
# The preferred source to use for getting the real client IP.
|
||||
#
|
||||
# Takes any of the following options:
|
||||
#
|
||||
@@ -301,16 +301,47 @@
|
||||
# "true_client_ip" - `True-Client-Ip` header
|
||||
# "x_envoy_external_address" - `X-Envoy-External-Address` header
|
||||
# "x_real_ip" - `X-Real-Ip` header
|
||||
# "direct" - the directly connected IP address (fallback for other
|
||||
# options)
|
||||
#
|
||||
# Only set this if you are certain only your reverse proxy
|
||||
# will send the expected header. There is no "is the connecting IP allowed
|
||||
# to set this header" check; if the header selected is present, it is
|
||||
# used.
|
||||
#
|
||||
# Defaults to the IP address actually making the connection.
|
||||
# Ignored if `accepted_ip_sources` is set.
|
||||
#
|
||||
# If not set set, `direct` is used. Additionally, `direct` is used
|
||||
# as a fallback if the chosen header is not found.
|
||||
#
|
||||
#request_ip_source = false
|
||||
|
||||
# The accepted source(s) for the real client IP.
|
||||
#
|
||||
# Takes any combination of the following options:
|
||||
#
|
||||
# "cf_connecting_ip" - `Cf-Connecting-Ip` header
|
||||
# "cloudfront_viewer_address" - `CloudFront-Viewer-Address` header
|
||||
# "fly_client_ip" - `Fly-Client-IP` header
|
||||
# "x_forwarded_for" - rightmost value of the `X-Forwarded-For` header
|
||||
# "true_client_ip" - `True-Client-Ip` header
|
||||
# "x_envoy_external_address" - `X-Envoy-External-Address` header
|
||||
# "x_real_ip" - `X-Real-Ip` header
|
||||
# "direct" - the directly connected IP address
|
||||
#
|
||||
# Only set this if you are certain only your reverse proxy
|
||||
# will send the expected header. There is no "is the connecting IP allowed
|
||||
# to set this header" check; if the header selected is present, it is
|
||||
# used.
|
||||
#
|
||||
# One of the configured sources must be available or the request is
|
||||
# rejected. If you want a fallback (including `direct`!), you have to
|
||||
# configure it. Priority is based on configuration order.
|
||||
#
|
||||
# If not set, `request_ip_source` configuration is used.
|
||||
#
|
||||
#accepted_ip_sources = []
|
||||
|
||||
# Max request size for file uploads in bytes. Defaults to 20MB.
|
||||
# Also limits incoming federated media.
|
||||
#
|
||||
|
||||
+1
-1
@@ -64,11 +64,11 @@ zstd_compression = [
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
axum-client-ip.workspace = true
|
||||
axum-extra.workspace = true
|
||||
axum.workspace = true
|
||||
base64.workspace = true
|
||||
bytes.workspace = true
|
||||
client-ip.workspace = true
|
||||
conduwuit-core.workspace = true
|
||||
conduwuit-macros.workspace = true
|
||||
conduwuit-service.workspace = true
|
||||
|
||||
+165
-88
@@ -1,135 +1,212 @@
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
use core::net::{IpAddr, SocketAddr};
|
||||
use std::ops::Deref;
|
||||
|
||||
use axum::{
|
||||
extract::{ConnectInfo, FromRequestParts},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use conduwuit::{debug_info, debug_warn};
|
||||
use http::{HeaderMap, StatusCode, request::Parts};
|
||||
use service::Services;
|
||||
|
||||
use axum::extract::{ConnectInfo, FromRequestParts};
|
||||
use axum_client_ip::ClientIp as SourcedClientIp;
|
||||
use conduwuit::debug_warn;
|
||||
use http::request::Parts;
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ClientIpError {
|
||||
Header(client_ip::Error),
|
||||
Direct,
|
||||
}
|
||||
|
||||
const UNKNOWN_IP: IpAddr = IpAddr::V4(Ipv4Addr::UNSPECIFIED);
|
||||
impl IntoResponse for ClientIpError {
|
||||
fn into_response(self) -> Response {
|
||||
let text = match self {
|
||||
| Self::Header(e) => format!("{e}"),
|
||||
| Self::Direct => "Failed to extract IP from ConnectionInfo".to_owned(),
|
||||
};
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, text).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
/// [`ClientIp`] extractor that falls back to the connection peer address
|
||||
/// instead of rejecting the request when `request_ip_source` can't be resolved.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct ClientIp(pub IpAddr);
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ClientIp(pub IpAddr);
|
||||
|
||||
type PartsToIpFn = Box<dyn Fn(&Parts) -> Result<ClientIp, ClientIpError>>;
|
||||
|
||||
impl ClientIp {
|
||||
fn from_header<T>(func: T) -> PartsToIpFn
|
||||
where
|
||||
T: Fn(&HeaderMap) -> Result<IpAddr, client_ip::Error> + 'static,
|
||||
{
|
||||
Box::new(move |parts: &Parts| {
|
||||
func(&parts.headers)
|
||||
.map(Self)
|
||||
.map_err(ClientIpError::Header)
|
||||
})
|
||||
}
|
||||
|
||||
fn from_connection_info(parts: &Parts) -> Result<Self, ClientIpError> {
|
||||
parts
|
||||
.extensions
|
||||
.get::<ConnectInfo<SocketAddr>>()
|
||||
.ok_or_else(|| ClientIpError::Direct)
|
||||
.map(|ConnectInfo(addr)| Self(addr.ip()))
|
||||
}
|
||||
|
||||
fn for_source(source: &str) -> Option<PartsToIpFn> {
|
||||
match source {
|
||||
| "cf_connecting_ip" => Some(Self::from_header(client_ip::cf_connecting_ip)),
|
||||
| "cloudfront_viewer_address" =>
|
||||
Some(Self::from_header(client_ip::cloudfront_viewer_address)),
|
||||
| "fly_client_ip" => Some(Self::from_header(client_ip::fly_client_ip)),
|
||||
| "x_forwarded_for" => Some(Self::from_header(client_ip::rightmost_x_forwarded_for)),
|
||||
| "true_client_ip" => Some(Self::from_header(client_ip::true_client_ip)),
|
||||
| "x_envoy_external_address" =>
|
||||
Some(Self::from_header(client_ip::x_envoy_external_address)),
|
||||
| "x_real_ip" => Some(Self::from_header(client_ip::x_real_ip)),
|
||||
| "direct" => Some(Box::new(Self::from_connection_info)),
|
||||
| s => {
|
||||
debug_warn!("Invalid client IP source option supplied, skipping: {s}");
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn for_config(
|
||||
parts: &Parts,
|
||||
accepted_ip_sources: &[String],
|
||||
request_ip_source: Option<&String>,
|
||||
) -> Result<Self, ClientIpError> {
|
||||
accepted_ip_sources
|
||||
.iter()
|
||||
.filter_map(|s| Self::for_source(s))
|
||||
.map(|f| f(parts))
|
||||
.reduce(Result::or)
|
||||
.or_else(|| {
|
||||
debug_info!(
|
||||
"No (valid) options set in `accepted_ip_sources`; falling back to \
|
||||
`request_ip_source`"
|
||||
);
|
||||
request_ip_source
|
||||
.and_then(|s| Self::for_source(s))
|
||||
.map(|f| f(parts).or_else(|_| Self::from_connection_info(parts)))
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
debug_info!(
|
||||
"No (valid) options set in `accepted_ip_sources` or `request_ip_source`; \
|
||||
using peer address"
|
||||
);
|
||||
Self::from_connection_info(parts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for ClientIp
|
||||
where
|
||||
S: Sync,
|
||||
S: Deref<Target = Services> + Sync,
|
||||
{
|
||||
type Rejection = Infallible;
|
||||
type Rejection = ClientIpError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let ip = match SourcedClientIp::from_request_parts(parts, state).await {
|
||||
| Ok(SourcedClientIp(ip)) => ip,
|
||||
| Err(rejection) => {
|
||||
debug_warn!(
|
||||
%rejection,
|
||||
"Could not resolve client IP from request_ip_source; using peer address"
|
||||
);
|
||||
parts
|
||||
.extensions
|
||||
.get::<ConnectInfo<SocketAddr>>()
|
||||
.map_or(UNKNOWN_IP, |ConnectInfo(addr)| addr.ip())
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Self(ip))
|
||||
Self::for_config(
|
||||
parts,
|
||||
&state.config.accepted_ip_sources,
|
||||
state.config.request_ip_source.as_ref(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use axum_client_ip::ClientIpSource;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
use super::*;
|
||||
|
||||
const PEER: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 7)), 8448);
|
||||
const PEER_V6: SocketAddr =
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0x2001, 0xDB8, 0, 0, 0, 0, 0, 1)), 8448);
|
||||
const SOCKET_V4: IpAddr = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 1));
|
||||
const HEADER_V4: IpAddr = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1));
|
||||
|
||||
fn parts(
|
||||
source: Option<ClientIpSource>,
|
||||
peer: Option<SocketAddr>,
|
||||
headers: &[(&str, &str)],
|
||||
) -> Parts {
|
||||
const SOCKET_V6: IpAddr = IpAddr::V6(Ipv6Addr::new(0x2001, 0xDB8, 0, 0, 0, 0, 0, 1));
|
||||
const HEADER_V6: IpAddr = IpAddr::V6(Ipv6Addr::new(0x2001, 0xDB8, 0, 0, 0, 0, 0, 2));
|
||||
|
||||
fn parts(peer: Option<IpAddr>, headers: &[(&str, &str)]) -> Parts {
|
||||
let mut builder = http::Request::builder();
|
||||
for (name, value) in headers {
|
||||
builder = builder.header(*name, *value);
|
||||
}
|
||||
|
||||
let (mut parts, ()) = builder.body(()).unwrap().into_parts();
|
||||
if let Some(source) = source {
|
||||
parts.extensions.insert(source);
|
||||
}
|
||||
if let Some(peer) = peer {
|
||||
parts.extensions.insert(ConnectInfo(peer));
|
||||
parts
|
||||
.extensions
|
||||
.insert(ConnectInfo(SocketAddr::new(peer, 8448)));
|
||||
}
|
||||
|
||||
parts
|
||||
}
|
||||
|
||||
async fn extract(mut parts: Parts) -> IpAddr {
|
||||
let ClientIp(ip) = ClientIp::from_request_parts(&mut parts, &()).await.unwrap();
|
||||
ip
|
||||
fn extract(
|
||||
parts: &Parts,
|
||||
accepted_ip_sources: &[String],
|
||||
request_ip_source: Option<&String>,
|
||||
) -> Result<IpAddr, ClientIpError> {
|
||||
ClientIp::for_config(parts, accepted_ip_sources, request_ip_source).map(|ClientIp(ip)| ip)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_from_configured_source() {
|
||||
let parts =
|
||||
parts(Some(ClientIpSource::XRealIp), Some(PEER), &[("x-real-ip", "203.0.113.5")]);
|
||||
async fn resolves_from_configured_source_ipv4() {
|
||||
let parts = parts(Some(SOCKET_V4), &[("x-real-ip", &HEADER_V4.to_string())]);
|
||||
|
||||
assert_eq!(extract(parts).await, IpAddr::V4(Ipv4Addr::new(203, 0, 113, 5)));
|
||||
assert_eq!(extract(&parts, &["x_real_ip".to_owned()], None), Ok(HEADER_V4));
|
||||
assert_eq!(extract(&parts, &[], Some(&"x_real_ip".to_owned())), Ok(HEADER_V4));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolves_ipv6_from_configured_source() {
|
||||
let parts =
|
||||
parts(Some(ClientIpSource::XRealIp), Some(PEER), &[("x-real-ip", "2001:db8::2")]);
|
||||
async fn resolves_from_configured_source_ipv6() {
|
||||
let parts = parts(Some(SOCKET_V6), &[("x-real-ip", &HEADER_V6.to_string())]);
|
||||
|
||||
assert_eq!(extract(&parts, &["x_real_ip".to_owned()], None), Ok(HEADER_V6));
|
||||
assert_eq!(extract(&parts, &[], Some(&"x_real_ip".to_owned())), Ok(HEADER_V6));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn accepted_ip_sources_no_fall_back_to_peer_when_header_missing() {
|
||||
let parts = parts(Some(SOCKET_V4), &[]);
|
||||
|
||||
extract(&parts, &["x_real_ip".to_owned()], None).unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_ip_source_falls_back_to_peer_when_header_missing() {
|
||||
let parts = parts(Some(SOCKET_V4), &[]);
|
||||
|
||||
assert_eq!(extract(&parts, &[], Some(&"x_real_ip".to_owned())), Ok(SOCKET_V4));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn accepted_ip_sources_no_fall_back_to_peer_when_header_unparsable() {
|
||||
let parts = parts(Some(SOCKET_V4), &[("x-real-ip", "not-an-ip")]);
|
||||
|
||||
extract(&parts, &["x_real_ip".to_owned()], None).unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_ip_source_falls_back_to_peer_when_header_unparsable() {
|
||||
let parts = parts(Some(SOCKET_V4), &[("x-real-ip", "not-an-ip")]);
|
||||
|
||||
assert_eq!(extract(&parts, &[], Some(&"x_real_ip".to_owned())), Ok(SOCKET_V4));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn accepted_ip_sources_falls_back_when_configured() {
|
||||
let parts = parts(Some(SOCKET_V4), &[]);
|
||||
|
||||
assert_eq!(
|
||||
extract(parts).await,
|
||||
IpAddr::V6(Ipv6Addr::new(0x2001, 0xDB8, 0, 0, 0, 0, 0, 2))
|
||||
extract(&parts, &["x_real_ip".to_owned(), "direct".to_owned()], None),
|
||||
Ok(SOCKET_V4)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn falls_back_to_peer_when_header_missing() {
|
||||
let parts = parts(Some(ClientIpSource::XRealIp), Some(PEER), &[]);
|
||||
async fn falls_back_to_peer_no_configuration() {
|
||||
let parts = parts(Some(SOCKET_V4), &[("x-real-ip", &HEADER_V4.to_string())]);
|
||||
|
||||
assert_eq!(extract(parts).await, PEER.ip());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn falls_back_to_ipv6_peer_when_header_missing() {
|
||||
let parts = parts(Some(ClientIpSource::XRealIp), Some(PEER_V6), &[]);
|
||||
|
||||
assert_eq!(extract(parts).await, PEER_V6.ip());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn falls_back_to_peer_when_header_unparsable() {
|
||||
let parts =
|
||||
parts(Some(ClientIpSource::XRealIp), Some(PEER), &[("x-real-ip", "not-an-ip")]);
|
||||
|
||||
assert_eq!(extract(parts).await, PEER.ip());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn falls_back_to_peer_when_source_unset() {
|
||||
let parts = parts(None, Some(PEER), &[("x-real-ip", "203.0.113.5")]);
|
||||
|
||||
assert_eq!(extract(parts).await, PEER.ip());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn falls_back_to_unspecified_without_peer() {
|
||||
let parts = parts(Some(ClientIpSource::XRealIp), None, &[]);
|
||||
|
||||
assert_eq!(extract(parts).await, UNKNOWN_IP);
|
||||
assert_eq!(extract(&parts, &[], None), Ok(SOCKET_V4));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ pub mod client;
|
||||
pub mod router;
|
||||
pub mod server;
|
||||
|
||||
pub(crate) mod client_ip;
|
||||
pub mod client_ip;
|
||||
|
||||
pub mod admin;
|
||||
|
||||
|
||||
+35
-2
@@ -370,7 +370,7 @@ pub struct Config {
|
||||
#[serde(default = "default_ip_lookup_strategy")]
|
||||
pub ip_lookup_strategy: u8,
|
||||
|
||||
/// The source to use for discovering the real connecting client IP.
|
||||
/// The preferred source to use for getting the real client IP.
|
||||
///
|
||||
/// Takes any of the following options:
|
||||
///
|
||||
@@ -381,16 +381,49 @@ pub struct Config {
|
||||
/// "true_client_ip" - `True-Client-Ip` header
|
||||
/// "x_envoy_external_address" - `X-Envoy-External-Address` header
|
||||
/// "x_real_ip" - `X-Real-Ip` header
|
||||
/// "direct" - the directly connected IP address (fallback for other
|
||||
/// options)
|
||||
///
|
||||
/// Only set this if you are certain only your reverse proxy
|
||||
/// will send the expected header. There is no "is the connecting IP allowed
|
||||
/// to set this header" check; if the header selected is present, it is
|
||||
/// used.
|
||||
///
|
||||
/// Defaults to the IP address actually making the connection.
|
||||
/// Ignored if `accepted_ip_sources` is set.
|
||||
///
|
||||
/// If not set set, `direct` is used. Additionally, `direct` is used
|
||||
/// as a fallback if the chosen header is not found.
|
||||
#[serde(default)]
|
||||
pub request_ip_source: Option<String>,
|
||||
|
||||
/// The accepted source(s) for the real client IP.
|
||||
///
|
||||
/// Takes any combination of the following options:
|
||||
///
|
||||
/// "cf_connecting_ip" - `Cf-Connecting-Ip` header
|
||||
/// "cloudfront_viewer_address" - `CloudFront-Viewer-Address` header
|
||||
/// "fly_client_ip" - `Fly-Client-IP` header
|
||||
/// "x_forwarded_for" - rightmost value of the `X-Forwarded-For` header
|
||||
/// "true_client_ip" - `True-Client-Ip` header
|
||||
/// "x_envoy_external_address" - `X-Envoy-External-Address` header
|
||||
/// "x_real_ip" - `X-Real-Ip` header
|
||||
/// "direct" - the directly connected IP address
|
||||
///
|
||||
/// Only set this if you are certain only your reverse proxy
|
||||
/// will send the expected header. There is no "is the connecting IP allowed
|
||||
/// to set this header" check; if the header selected is present, it is
|
||||
/// used.
|
||||
///
|
||||
/// One of the configured sources must be available or the request is
|
||||
/// rejected. If you want a fallback (including `direct`!), you have to
|
||||
/// configure it. Priority is based on configuration order.
|
||||
///
|
||||
/// If not set, `request_ip_source` configuration is used.
|
||||
///
|
||||
/// default: []
|
||||
#[serde(default)]
|
||||
pub accepted_ip_sources: Vec<String>,
|
||||
|
||||
/// Max request size for file uploads in bytes. Defaults to 20MB.
|
||||
/// Also limits incoming federated media.
|
||||
///
|
||||
|
||||
@@ -91,7 +91,6 @@ zstd_compression = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
axum-client-ip.workspace = true
|
||||
axum-server-dual-protocol.workspace = true
|
||||
axum-server-dual-protocol.optional = true
|
||||
axum-server.workspace = true
|
||||
|
||||
+6
-22
@@ -1,11 +1,11 @@
|
||||
use std::{any::Any, sync::Arc, time::Duration};
|
||||
|
||||
use axum::{
|
||||
Router, extract,
|
||||
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
||||
Router,
|
||||
extract::{self, DefaultBodyLimit, FromRequestParts, MatchedPath, State},
|
||||
};
|
||||
use axum_client_ip::{ClientIp, ClientIpSource};
|
||||
use conduwuit::{Result, Server, debug, error};
|
||||
use conduwuit_api::client_ip::ClientIp;
|
||||
use conduwuit_service::{Services, state::Guard};
|
||||
use http::{
|
||||
HeaderValue, Method, StatusCode,
|
||||
@@ -47,26 +47,9 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
|
||||
))]
|
||||
let layers = layers.layer(compression_layer(server));
|
||||
|
||||
let client_ip_layer = match services
|
||||
.config
|
||||
.request_ip_source
|
||||
.as_ref()
|
||||
.map(AsRef::as_ref)
|
||||
{
|
||||
| Some("cf_connecting_ip") => ClientIpSource::CfConnectingIp,
|
||||
| Some("cloudfront_viewer_address") => ClientIpSource::CloudFrontViewerAddress,
|
||||
| Some("fly_client_ip") => ClientIpSource::FlyClientIp,
|
||||
| Some("x_forwarded_for") => ClientIpSource::RightmostXForwardedFor,
|
||||
| Some("true_client_ip") => ClientIpSource::TrueClientIp,
|
||||
| Some("x_envoy_external_address") => ClientIpSource::XEnvoyExternalAddress,
|
||||
| Some("x_real_ip") => ClientIpSource::XRealIp,
|
||||
| None | Some(_) => ClientIpSource::ConnectInfo,
|
||||
};
|
||||
|
||||
let services_ = services.clone();
|
||||
let layers = layers
|
||||
.layer(SetSensitiveHeadersLayer::new([header::AUTHORIZATION]))
|
||||
.layer(client_ip_layer.into_extension())
|
||||
.layer(
|
||||
TraceLayer::new_for_http()
|
||||
.make_span_with(tracing_span::<_>)
|
||||
@@ -74,7 +57,7 @@ pub(crate) fn build(services: &Arc<Services>) -> Result<(Router, Guard)> {
|
||||
.on_request(DefaultOnRequest::new().level(tracing::Level::TRACE))
|
||||
.on_response(DefaultOnResponse::new().level(tracing::Level::DEBUG)),
|
||||
)
|
||||
.layer(axum::middleware::from_fn(request_ip))
|
||||
.layer(axum::middleware::from_fn_with_state(Arc::clone(services), request_ip))
|
||||
.layer(axum::middleware::from_fn_with_state(Arc::clone(services), request::handle))
|
||||
.layer(ResponseBodyTimeoutLayer::new(Duration::from_secs(
|
||||
server.config.client_response_timeout,
|
||||
@@ -238,11 +221,12 @@ fn tracing_span<T>(request: &http::Request<T>) -> tracing::Span {
|
||||
|
||||
/// Annotates the tracing span with the client IP
|
||||
async fn request_ip(
|
||||
State(services): State<Arc<Services>>,
|
||||
request: extract::Request,
|
||||
next: axum::middleware::Next,
|
||||
) -> axum::response::Response {
|
||||
let (mut parts, body) = request.into_parts();
|
||||
if let Ok(ip) = ClientIp::from_request_parts(&mut parts, &()).await {
|
||||
if let Ok(ip) = ClientIp::from_request_parts(&mut parts, &services).await {
|
||||
let span = tracing::Span::current();
|
||||
span.record("ip", ip.0.to_string());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user