From 134db396cf4f3b288d3174d169c9b60f515de645 Mon Sep 17 00:00:00 2001 From: dctaf <155950283+dctaf@users.noreply.github.com> Date: Sun, 2 Feb 2025 03:51:00 -0800 Subject: [PATCH] feat: optional log from `X-Real-IP` header via new `--log-x-real-ip` option (#521) * feat(logger): --log-x-real-ip option. * docs: Updates for --log-x-real-ip. --- .../configuration/command-line-arguments.md | 5 ++-- .../configuration/environment-variables.md | 5 +++- docs/content/features/logging.md | 27 ++++++++++++++++--- src/handler.rs | 3 +++ src/log_addr.rs | 23 +++++++++++++--- src/server.rs | 4 +++ src/settings/cli.rs | 12 +++++++++ src/settings/file.rs | 3 +++ src/settings/mod.rs | 5 ++++ src/testing.rs | 1 + 10 files changed, 79 insertions(+), 9 deletions(-) diff --git a/docs/content/configuration/command-line-arguments.md b/docs/content/configuration/command-line-arguments.md index 515dfd40..c0e126bb 100644 --- a/docs/content/configuration/command-line-arguments.md +++ b/docs/content/configuration/command-line-arguments.md @@ -9,7 +9,6 @@ The server can be configured via the following command-line arguments. ``` $ static-web-server -h - A cross-platform, high-performance and asynchronous web server for static files-serving. Usage: static-web-server [OPTIONS] [COMMAND] @@ -85,8 +84,10 @@ Options: Server TOML configuration file path [env: SERVER_CONFIG_FILE=] [default: ./config.toml] --log-remote-address [] Log incoming requests information along with its remote address if available using the `info` log level [env: SERVER_LOG_REMOTE_ADDRESS=] [default: false] [possible values: true, false] + --log-x-real-ip [] + Log the X-Real-IP header for remote IP information [env: SERVER_LOG_X_REAL_IP=] [default: false] [possible values: true, false] --log-forwarded-for [] - Log real IP from X-Forwarded-For header [env: SERVER_LOG_FORWARDED_FOR] [default: false] [possible values: true, false] + Log the X-Forwarded-For header for remote IP information [env: SERVER_LOG_FORWARDED_FOR=] [default: false] [possible values: true, false] --trusted-proxies A comma separated list of IP addresses to accept the X-Forwarded-For header from. Empty means trust all IPs [env: SERVER_TRUSTED_PROXIES] [default: ""] --redirect-trailing-slash [] diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md index 605fa080..dc886b3c 100644 --- a/docs/content/configuration/environment-variables.md +++ b/docs/content/configuration/environment-variables.md @@ -30,8 +30,11 @@ Specify a logging level in lowercase. Possible values are `error`, `warn`, `info ### SERVER_LOG_REMOTE_ADDRESS Log incoming request information along with its Remote Address (IP) if available using the `info` log level. Default `false`. +### SERVER_LOG_X_REAL_IP +Log the X-Real-IP header if available using the `info` log level. Default `false`. + ### SERVER_LOG_FORWARDED_FOR -Log real IP from X-Forwarded-For header if available using the `info` log level. Default `false` +Log the X-Forwarded-For header if available using the `info` log level. Default `false`. ### SERVER_TRUSTED_PROXIES A comma separated list of IP addresses to accept the X-Forwarded-For header from. An empty string means trust all IPs. Default `""` diff --git a/docs/content/features/logging.md b/docs/content/features/logging.md index 41202661..db3e75d9 100644 --- a/docs/content/features/logging.md +++ b/docs/content/features/logging.md @@ -13,6 +13,8 @@ static-web-server \ --log-level "trace" ``` +> Note: The log format is not well defined and is subject to change. + ## Log Remote Addresses SWS provides *Remote Address (IP)* logging for every request via an `INFO` log level. @@ -42,9 +44,28 @@ INFO static_web_server::info: log requests with remote IP addresses: enabled=tru INFO static_web_server::handler: incoming request: method=GET uri=/ remote_addr=192.168.1.126:57625 INFO static_web_server::handler: incoming request: method=GET uri=/favicon.ico remote_addr=192.168.1.126:57625 ``` -## Log Real Remote IP -When used behind a reverse proxy the reported `remote_addr` indicates the proxies IP address and port, not the clients real IP. +## Logging Client IP from X-Real-IP header + +Some upstream proxies will report the client's real IP address in the `X-Real-IP` header. + +To enable logging of the X-Real-IP header, enable the `--log-x-real-ip` option or the equivalent [SERVER_LOG_X_REAL_IP](../configuration/environment-variables.md#server_log_x_real_ip) environment variable. + +When enabled, the log entries will look like: + +```log +INFO static_web_server::handler: incoming request: method=GET uri=/ x_real_ip=203.0.113.195 +``` + +If the value of the `X-Real-IP` header does not parse as an IP address, no value will be logged. + +To restrict the logging to only requests that originate from trusted proxy IPs, you can use the `--trusted-proxies` option, or the equivalent [SERVER_TRUSTED_PROXIES](../configuration/environment-variables.md#server_trusted_proxies) env. This should be a list of IPs, separated by commas. An empty list (the default) indicates that all IPs should be trusted. + +## Logging Client IP from X-Forwarded-For header + +> Note: This header should only be trusted when you know your upstream is handling X-Forwarded-For securely and when using the `--trusted-proxies` option. + +When used behind a reverse proxy the reported `remote_addr` indicates the proxies IP address and port, not the client's real IP. The Proxy server can be configured to provide the [X-Forwarded-For header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For), containing a comma-separated list of IP addresses, starting with the *real remote client IP*, and all following intermediate proxies (if any). @@ -52,7 +73,7 @@ To enable logging of the real remote IP, enable the `--log-forwarded-for` option Since the content of the `X-Forwarded-For` header can be changed by all proxies in the chain, the remote IP address reported may not be trusted. -To restrict the logging to only trusted proxy IPs, you can use the `--trusted-proxies` option, or the equivalent [SERVER_TRUSTED_PROXIES](../configuration/environment-variables.md#server_trusted_proxies) env. This should be a list of IPs, separated by commas. An empty list (the default) indicates that all IPs should be trusted. +To restrict the logging to only requests that originate from trusted proxy IPs, you can use the `--trusted-proxies` option, or the equivalent [SERVER_TRUSTED_PROXIES](../configuration/environment-variables.md#server_trusted_proxies) env. This should be a list of IPs, separated by commas. An empty list (the default) indicates that all IPs should be trusted. Command used for the following examples: ```sh diff --git a/src/handler.rs b/src/handler.rs index 79b1e09b..24aade74 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -102,6 +102,8 @@ pub struct RequestHandlerOpts { pub index_files: Vec, /// Log remote address feature. pub log_remote_address: bool, + /// Log the X-Real-IP header. + pub log_x_real_ip: bool, /// Log the X-Forwarded-For header. pub log_forwarded_for: bool, /// Trusted IPs for remote addresses. @@ -161,6 +163,7 @@ impl Default for RequestHandlerOpts { basic_auth: String::new(), index_files: vec!["index.html".into()], log_remote_address: false, + log_x_real_ip: false, log_forwarded_for: false, trusted_proxies: Vec::new(), redirect_trailing_slash: true, diff --git a/src/log_addr.rs b/src/log_addr.rs index 27174c92..f6bc388a 100644 --- a/src/log_addr.rs +++ b/src/log_addr.rs @@ -22,7 +22,11 @@ pub(crate) fn init(enabled: bool, handler_opts: &mut RequestHandlerOpts) { server_info!("log requests with remote IP addresses: enabled={enabled}"); server_info!( - "log X-Forwarded-For real remote IP addresses: enabled={}", + "log X-Real-IP header: enabled={}", + handler_opts.log_forwarded_for + ); + server_info!( + "log X-Forwarded-For header: enabled={}", handler_opts.log_forwarded_for ); server_info!("trusted IPs for X-Forwarded-For: {trusted}"); @@ -41,18 +45,31 @@ pub(crate) fn pre_process( remote_addrs.push_str(format!(" remote_addr={addr}").as_str()); } } - if opts.log_forwarded_for + if opts.log_x_real_ip && (opts.trusted_proxies.is_empty() || remote_addr.is_some_and(|addr| opts.trusted_proxies.contains(&addr.ip()))) { if let Some(real_ip) = req + .headers() + .get("X-Real-IP") + .and_then(|h| h.to_str().ok()) + .and_then(|s| s.trim().parse::().ok()) + { + remote_addrs.push_str(format!(" x_real_ip={real_ip}").as_str()); + } + } + if opts.log_forwarded_for + && (opts.trusted_proxies.is_empty() + || remote_addr.is_some_and(|addr| opts.trusted_proxies.contains(&addr.ip()))) + { + if let Some(forwarded_for) = req .headers() .get("X-Forwarded-For") .and_then(|h| h.to_str().ok()) .and_then(|s| s.split(',').next()) .and_then(|s| s.trim().parse::().ok()) { - remote_addrs.push_str(format!(" real_remote_ip={real_ip}").as_str()); + remote_addrs.push_str(format!(" real_remote_ip={forwarded_for}").as_str()); } } diff --git a/src/server.rs b/src/server.rs index 209375ee..eaa5c26e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -225,6 +225,9 @@ impl Server { // Log remote address option let log_remote_address = general.log_remote_address; + // Log the X-Real-IP header. + let log_x_real_ip = general.log_x_real_ip; + // Log the X-Forwarded-For header. let log_forwarded_for = general.log_forwarded_for; @@ -267,6 +270,7 @@ impl Server { page404: page404.clone(), page50x: page50x.clone(), log_remote_address, + log_x_real_ip, log_forwarded_for, trusted_proxies, redirect_trailing_slash, diff --git a/src/settings/cli.rs b/src/settings/cli.rs index 80cc9df3..d4ea5059 100644 --- a/src/settings/cli.rs +++ b/src/settings/cli.rs @@ -414,6 +414,18 @@ pub struct General { /// Log incoming requests information along with its remote address if available using the `info` log level. pub log_remote_address: bool, + #[arg( + long, + default_value = "false", + default_missing_value("true"), + num_args(0..=1), + require_equals(false), + action = clap::ArgAction::Set, + env = "SERVER_LOG_X_REAL_IP", + )] + /// Log the X-Real-IP header for remote IP information. + pub log_x_real_ip: bool, + #[arg( long, default_value = "false", diff --git a/src/settings/file.rs b/src/settings/file.rs index 98f0154e..180c7d34 100644 --- a/src/settings/file.rs +++ b/src/settings/file.rs @@ -357,6 +357,9 @@ pub struct General { /// Log remote address feature. pub log_remote_address: Option, + /// Log the X-Real-IP header. + pub log_x_real_ip: Option, + /// Log the X-Forwarded-For header. pub log_forwarded_for: Option, diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 6a7353a4..554cccbe 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -197,6 +197,7 @@ impl Settings { let mut page_fallback = opts.page_fallback; let mut log_remote_address = opts.log_remote_address; + let mut log_x_real_ip = opts.log_x_real_ip; let mut log_forwarded_for = opts.log_forwarded_for; let mut trusted_proxies = opts.trusted_proxies; let mut redirect_trailing_slash = opts.redirect_trailing_slash; @@ -365,6 +366,9 @@ impl Settings { if let Some(v) = general.log_remote_address { log_remote_address = v } + if let Some(v) = general.log_x_real_ip { + log_x_real_ip = v + } if let Some(v) = general.log_forwarded_for { log_forwarded_for = v } @@ -663,6 +667,7 @@ impl Settings { #[cfg(feature = "fallback-page")] page_fallback, log_remote_address, + log_x_real_ip, log_forwarded_for, trusted_proxies, redirect_trailing_slash, diff --git a/src/testing.rs b/src/testing.rs index 77b2751c..d92c45ce 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -94,6 +94,7 @@ pub mod fixtures { #[cfg(feature = "basic-auth")] basic_auth: general.basic_auth, log_remote_address: general.log_remote_address, + log_x_real_ip: general.log_x_real_ip, log_forwarded_for: general.log_forwarded_for, trusted_proxies: general.trusted_proxies, redirect_trailing_slash: general.redirect_trailing_slash,