mirror of
https://github.com/awfufu/traudit
synced 2026-03-01 05:29:44 +08:00
feat: support injecting real IP into X-Forwarded-For header via 'add_xff_header' config
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2495,6 +2495,7 @@ dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"clickhouse",
|
||||
"http",
|
||||
"httparse",
|
||||
"ipnet",
|
||||
"libc",
|
||||
|
||||
@@ -12,6 +12,7 @@ tokio = { version = "1", features = ["full"] }
|
||||
clickhouse = { version = "0.14", features = ["time"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
http = "1"
|
||||
socket2 = "0.6"
|
||||
libc = "0.2"
|
||||
tracing = "0.1"
|
||||
|
||||
@@ -102,6 +102,8 @@ pub struct BindEntry {
|
||||
#[serde(default = "default_socket_mode", deserialize_with = "deserialize_mode")]
|
||||
pub mode: u32,
|
||||
pub tls: Option<TlsConfig>,
|
||||
#[serde(default)]
|
||||
pub add_xff_header: bool,
|
||||
pub real_ip: Option<RealIpConfig>,
|
||||
}
|
||||
|
||||
@@ -227,6 +229,19 @@ impl Config {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 3: Check for XFF loop
|
||||
if bind.add_xff_header {
|
||||
if let Some(real_ip) = &bind.real_ip {
|
||||
if real_ip.source == RealIpSource::Xff {
|
||||
anyhow::bail!(
|
||||
"Service '{}' bind '{}' has 'add_xff_header: true' but 'real_ip.from' is 'xff'. This is not allowed as it would duplicate the IP.",
|
||||
service.name,
|
||||
bind.addr
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct TrauditProxy {
|
||||
pub service_config: ServiceConfig,
|
||||
pub listen_addr: String,
|
||||
pub real_ip: Option<crate::config::RealIpConfig>,
|
||||
pub add_xff_header: bool,
|
||||
}
|
||||
|
||||
pub struct HttpContext {
|
||||
@@ -117,6 +118,30 @@ impl ProxyHttp for TrauditProxy {
|
||||
|
||||
ctx.src_ip = resolved_ip;
|
||||
|
||||
// 1.5. Inject X-Forwarded-For if configured
|
||||
if self.add_xff_header {
|
||||
let src_ip_str = resolved_ip.to_string();
|
||||
|
||||
// Collect existing headers to avoid double borrow
|
||||
let mut owned_values: Vec<String> = session
|
||||
.req_header()
|
||||
.headers
|
||||
.get_all("X-Forwarded-For")
|
||||
.iter()
|
||||
.filter_map(|v| v.to_str().ok().map(|s| s.to_string()))
|
||||
.collect();
|
||||
|
||||
owned_values.push(src_ip_str);
|
||||
let new_val = owned_values.join(", ");
|
||||
|
||||
if let Ok(valid_header) = http::header::HeaderValue::from_str(&new_val) {
|
||||
// insert_header replaces all existing headers with this name
|
||||
let _ = session
|
||||
.req_header_mut()
|
||||
.insert_header("X-Forwarded-For", valid_header);
|
||||
}
|
||||
}
|
||||
|
||||
// Log connection info
|
||||
let src_fmt = resolved_ip.to_string();
|
||||
let physical_fmt = peer_addr.to_string();
|
||||
|
||||
@@ -172,6 +172,7 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
|
||||
service_config: service_config.clone(),
|
||||
listen_addr: bind_addr.clone(),
|
||||
real_ip: real_ip_config.clone(),
|
||||
add_xff_header: bind.add_xff_header,
|
||||
};
|
||||
let mut service_obj = http_proxy_service(&conf, inner_proxy);
|
||||
let app = unsafe {
|
||||
@@ -223,6 +224,7 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
|
||||
service_config: svc_config.clone(),
|
||||
listen_addr: bind.addr.clone(),
|
||||
real_ip,
|
||||
add_xff_header: bind.add_xff_header,
|
||||
};
|
||||
|
||||
let mut service = http_proxy_service(&server.configuration, proxy);
|
||||
|
||||
@@ -105,3 +105,30 @@ fn test_trusted_proxies_mixed_formats() {
|
||||
assert!(!config.is_trusted("10.0.1.1".parse().unwrap())); // Outside /24
|
||||
assert!(!config.is_trusted("2001:db9::1".parse().unwrap())); // Outside /32
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_xff_loop() {
|
||||
let config_str = r#"
|
||||
database:
|
||||
type: clickhouse
|
||||
dsn: "http://127.0.0.1:8123"
|
||||
services:
|
||||
- name: "loop-service"
|
||||
type: "http"
|
||||
forward_to: "127.0.0.1:8080"
|
||||
binds:
|
||||
- addr: "0.0.0.0:443"
|
||||
proxy: v2
|
||||
add_xff_header: true
|
||||
real_ip:
|
||||
from: "xff"
|
||||
"#;
|
||||
let mut file = tempfile::NamedTempFile::new().unwrap();
|
||||
write!(file, "{}", config_str).unwrap();
|
||||
let path = file.path().to_path_buf();
|
||||
|
||||
let res = Config::load(&path).await;
|
||||
assert!(res.is_err());
|
||||
let err = res.err().unwrap().to_string();
|
||||
assert!(err.contains("duplicate the IP"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user