// Copyright 2024 RustFS Team // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // 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_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, ProxyValidator, TrustedProxy, TrustedProxyConfig, ValidationMode}; use serial_test::serial; use std::net::{IpAddr, SocketAddr}; #[test] #[serial] fn test_config_loader_default() { // Clean up environment variables explicitly to ensure clean state temp_env::with_vars_unset( vec![ ENV_TRUSTED_PROXY_PROXIES, ENV_TRUSTED_PROXY_VALIDATION_MODE, ENV_TRUSTED_PROXY_MAX_HOPS, ], || { let config = ConfigLoader::from_env_or_default(); assert_eq!(config.server_addr.port(), 9000); assert!(!config.proxy.proxies.is_empty()); assert_eq!(config.proxy.validation_mode, ValidationMode::HopByHop); assert!(config.proxy.enable_rfc7239); assert_eq!(config.proxy.max_hops, 10); }, ); } #[test] #[serial] fn test_config_loader_env_vars() { // Use temp_env to ensure environment variables are cleaned up even if test fails temp_env::with_vars( vec![ (ENV_TRUSTED_PROXY_PROXIES, Some("192.168.1.0/24,10.0.0.0/8")), (ENV_TRUSTED_PROXY_VALIDATION_MODE, Some("strict")), (ENV_TRUSTED_PROXY_MAX_HOPS, Some("5")), ], || { let config = ConfigLoader::from_env(); if let Ok(config) = config { assert_eq!(config.server_addr.port(), 9000); assert_eq!(config.proxy.validation_mode, ValidationMode::Strict); assert_eq!(config.proxy.max_hops, 5); } else { panic!("Failed to load configuration from environment variables"); } }, ); } #[test] fn test_trusted_proxy_config() { let proxies = vec![ TrustedProxy::Single("192.168.1.1".parse().unwrap()), TrustedProxy::Cidr("10.0.0.0/8".parse().unwrap()), ]; let config = TrustedProxyConfig::new(proxies, ValidationMode::Strict, true, 10, true, vec![]); assert_eq!(config.proxies.len(), 2); assert_eq!(config.validation_mode, ValidationMode::Strict); assert!(config.enable_rfc7239); assert_eq!(config.max_hops, 10); assert!(config.enable_chain_continuity_check); let test_ip: IpAddr = "192.168.1.1".parse().unwrap(); let test_socket_addr = std::net::SocketAddr::new(test_ip, 8080); assert!(config.is_trusted(&test_socket_addr)); let test_ip2: IpAddr = "10.0.1.1".parse().unwrap(); let test_socket_addr2 = std::net::SocketAddr::new(test_ip2, 8080); assert!(config.is_trusted(&test_socket_addr2)); } #[test] fn test_trusted_proxy_contains() { let single_proxy = TrustedProxy::Single("192.168.1.1".parse().unwrap()); let test_ip: IpAddr = "192.168.1.1".parse().unwrap(); let test_ip2: IpAddr = "192.168.1.2".parse().unwrap(); assert!(single_proxy.contains(&test_ip)); assert!(!single_proxy.contains(&test_ip2)); let cidr_proxy = TrustedProxy::Cidr("192.168.1.0/24".parse().unwrap()); assert!(cidr_proxy.contains(&test_ip)); assert!(cidr_proxy.contains(&test_ip2)); let test_ip3: IpAddr = "192.168.2.1".parse().unwrap(); assert!(!cidr_proxy.contains(&test_ip3)); } #[test] fn test_private_network_check() { let config = TrustedProxyConfig::new( Vec::new(), ValidationMode::Lenient, true, 10, true, vec!["10.0.0.0/8".parse().unwrap(), "192.168.0.0/16".parse().unwrap()], ); let private_ip: IpAddr = "10.0.1.1".parse().unwrap(); let private_ip2: IpAddr = "192.168.1.1".parse().unwrap(); let public_ip: IpAddr = "8.8.8.8".parse().unwrap(); assert!(config.is_private_network(&private_ip)); assert!(config.is_private_network(&private_ip2)); assert!(!config.is_private_network(&public_ip)); } #[test] fn test_default_values() { // 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" ); }, ); }