fix(config): default trusted proxies to loopback-only (#4508)

This commit is contained in:
Zhengchao An
2026-07-09 01:06:11 +08:00
committed by GitHub
parent 5bbbe0008e
commit d1245ca33c
3 changed files with 70 additions and 9 deletions
+4 -2
View File
@@ -53,8 +53,10 @@ pub const DEFAULT_TRUSTED_PROXY_LOG_FAILED_VALIDATIONS: bool = true;
// ==================== Trusted Proxy Networks ====================
/// Environment variable for the list of trusted proxy networks (comma-separated IP/CIDR).
pub const ENV_TRUSTED_PROXY_PROXIES: &str = "RUSTFS_TRUSTED_PROXY_NETWORKS";
/// Default trusted networks include localhost and common private ranges.
pub const DEFAULT_TRUSTED_PROXY_PROXIES: &str = "127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fd00::/8";
/// Default trusted networks are loopback-only. Private/RFC1918 ranges are not
/// trusted by default: an operator must explicitly add the specific proxy
/// addresses in front of this server to have forwarding headers honored.
pub const DEFAULT_TRUSTED_PROXY_PROXIES: &str = "127.0.0.1,::1";
/// Environment variable for additional trusted proxy networks (production specific).
pub const ENV_TRUSTED_PROXY_EXTRA_PROXIES: &str = "RUSTFS_TRUSTED_PROXY_EXTRA_NETWORKS";
+1 -1
View File
@@ -32,7 +32,7 @@ The module is configured primarily through environment variables:
| `RUSTFS_TRUSTED_PROXY_ENABLED` | `true` | Enable the trusted proxy middleware |
| `RUSTFS_TRUSTED_PROXY_IMPLEMENTATION` | `simple` | Select `simple` or `legacy` implementation |
| `RUSTFS_TRUSTED_PROXY_VALIDATION_MODE` | `hop_by_hop` | Validation strategy (`strict`, `lenient`, `hop_by_hop`) |
| `RUSTFS_TRUSTED_PROXY_NETWORKS` | `127.0.0.1,::1,...` | Comma-separated list of trusted CIDR ranges |
| `RUSTFS_TRUSTED_PROXY_NETWORKS` | `127.0.0.1,::1` | Comma-separated list of trusted CIDR ranges (loopback-only by default) |
| `RUSTFS_TRUSTED_PROXY_MAX_HOPS` | `10` | Maximum allowed proxy hops |
| `RUSTFS_TRUSTED_PROXY_CACHE_CAPACITY` | `10000` | Max entries in the validation cache |
| `RUSTFS_TRUSTED_PROXY_METRICS_ENABLED` | `true` | Enable Prometheus metrics collection |
@@ -12,12 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use axum::http::HeaderMap;
use rustfs_config::{
DEFAULT_TRUSTED_PROXY_PROXIES, ENV_TRUSTED_PROXY_MAX_HOPS, ENV_TRUSTED_PROXY_PROXIES, ENV_TRUSTED_PROXY_VALIDATION_MODE,
DEFAULT_TRUSTED_PROXY_PROXIES, ENV_TRUSTED_PROXY_EXTRA_PROXIES, ENV_TRUSTED_PROXY_IPS, ENV_TRUSTED_PROXY_MAX_HOPS,
ENV_TRUSTED_PROXY_PROXIES, ENV_TRUSTED_PROXY_VALIDATION_MODE,
};
use rustfs_trusted_proxies::{ConfigLoader, TrustedProxy, TrustedProxyConfig, ValidationMode};
use rustfs_trusted_proxies::{ConfigLoader, ProxyValidator, TrustedProxy, TrustedProxyConfig, ValidationMode};
use serial_test::serial;
use std::net::IpAddr;
use std::net::{IpAddr, SocketAddr};
#[test]
#[serial]
@@ -127,8 +129,65 @@ fn test_private_network_check() {
#[test]
fn test_default_values() {
assert_eq!(
DEFAULT_TRUSTED_PROXY_PROXIES,
"127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fd00::/8"
// The default trusted set is loopback-only. Private/RFC1918 ranges must not
// be trusted unless an operator explicitly adds them.
assert_eq!(DEFAULT_TRUSTED_PROXY_PROXIES, "127.0.0.1,::1");
}
#[test]
#[serial]
fn test_default_config_does_not_trust_private_peer() {
// Under the DEFAULT configuration the trusted set is loopback-only, so a
// peer from a private range must not be treated as a trusted proxy.
temp_env::with_vars_unset(
vec![
ENV_TRUSTED_PROXY_PROXIES,
ENV_TRUSTED_PROXY_EXTRA_PROXIES,
ENV_TRUSTED_PROXY_IPS,
],
|| {
let config = ConfigLoader::from_env_or_default().proxy;
let loopback = SocketAddr::new(IpAddr::from([127, 0, 0, 1]), 9000);
assert!(config.is_trusted(&loopback), "loopback must remain trusted by default");
let private_peer = SocketAddr::new(IpAddr::from([10, 1, 2, 3]), 9000);
assert!(
!config.is_trusted(&private_peer),
"private-range peer must not be trusted under the default config"
);
},
);
}
#[test]
#[serial]
fn test_default_config_ignores_spoofed_forwarded_from_private_peer() {
// A peer from a private range (10.1.2.3) sending a spoofed X-Forwarded-For
// must NOT override the socket peer, because private ranges are no longer
// trusted under the default configuration.
temp_env::with_vars_unset(
vec![
ENV_TRUSTED_PROXY_PROXIES,
ENV_TRUSTED_PROXY_EXTRA_PROXIES,
ENV_TRUSTED_PROXY_IPS,
],
|| {
let config = ConfigLoader::from_env_or_default().proxy;
let validator = ProxyValidator::new(config, None);
let peer_addr = Some(SocketAddr::new(IpAddr::from([10, 1, 2, 3]), 9000));
let mut headers = HeaderMap::new();
headers.insert("x-forwarded-for", "203.0.113.10".parse().unwrap());
let info = validator.validate_request(peer_addr, &headers).unwrap();
assert!(!info.is_from_trusted_proxy, "private peer must not be a trusted proxy by default");
assert_eq!(
info.real_ip,
IpAddr::from([10, 1, 2, 3]),
"spoofed X-Forwarded-For must not override the socket peer"
);
},
);
}