From d72fef5ce62a88bba13115a88327b2bb00adaacd Mon Sep 17 00:00:00 2001 From: houseme Date: Mon, 5 Jan 2026 20:34:30 +0800 Subject: [PATCH] init --- Cargo.lock | 29 +- crates/trusted-proxies/src/advanced.rs | 201 ----- crates/trusted-proxies/src/api/handlers.rs | 13 + crates/trusted-proxies/src/api/mod.rs | 15 + crates/trusted-proxies/src/cache.rs | 51 -- crates/trusted-proxies/src/cloud/detector.rs | 13 + .../trusted-proxies/src/cloud/metadata/aws.rs | 13 + .../src/cloud/metadata/azure.rs | 13 + .../trusted-proxies/src/cloud/metadata/gcp.rs | 13 + .../trusted-proxies/src/cloud/metadata/mod.rs | 17 + crates/trusted-proxies/src/cloud/mod.rs | 4 +- .../src/cloud/{metadata.rs => ranges.rs} | 2 +- .../src/config/{proxy.rs => env.rs} | 0 crates/trusted-proxies/src/config/loader.rs | 13 + crates/trusted-proxies/src/config/mod.rs | 7 +- crates/trusted-proxies/src/config/types.rs | 13 + crates/trusted-proxies/src/error/config.rs | 13 + crates/trusted-proxies/src/error/mod.rs | 16 + crates/trusted-proxies/src/error/proxy.rs | 13 + crates/trusted-proxies/src/globals.rs | 125 --- crates/trusted-proxies/src/lib.rs | 14 +- .../trusted-proxies/src/logging/middleware.rs | 13 + crates/trusted-proxies/src/logging/mod.rs | 15 + crates/trusted-proxies/src/main.rs | 15 + crates/trusted-proxies/src/metrics.rs | 118 --- crates/trusted-proxies/src/middleware.rs | 298 -------- .../trusted-proxies/src/middleware/layer.rs | 13 + crates/trusted-proxies/src/middleware/mod.rs | 16 + .../trusted-proxies/src/middleware/service.rs | 13 + crates/trusted-proxies/src/processor.rs | 709 ------------------ crates/trusted-proxies/src/proxy.rs | 258 ------- crates/trusted-proxies/src/proxy/cache.rs | 13 + crates/trusted-proxies/src/proxy/chain.rs | 13 + crates/trusted-proxies/src/proxy/metrics.rs | 13 + crates/trusted-proxies/src/proxy/mod.rs | 17 + crates/trusted-proxies/src/proxy/validator.rs | 13 + crates/trusted-proxies/src/utils/ip.rs | 13 + crates/trusted-proxies/src/utils/mod.rs | 16 + .../trusted-proxies/src/utils/validation.rs | 13 + .../test/integration/cloud_tests.rs | 13 + .../trusted-proxies/test/integration/mod.rs | 13 + .../test/integration/proxy_tests.rs | 13 + .../trusted-proxies/test/unit/config_tests.rs | 13 + crates/trusted-proxies/test/unit/mod.rs | 13 + .../test/unit/validator_tests.rs | 13 + 45 files changed, 474 insertions(+), 1781 deletions(-) delete mode 100644 crates/trusted-proxies/src/advanced.rs create mode 100644 crates/trusted-proxies/src/api/handlers.rs create mode 100644 crates/trusted-proxies/src/api/mod.rs delete mode 100644 crates/trusted-proxies/src/cache.rs create mode 100644 crates/trusted-proxies/src/cloud/detector.rs create mode 100644 crates/trusted-proxies/src/cloud/metadata/aws.rs create mode 100644 crates/trusted-proxies/src/cloud/metadata/azure.rs create mode 100644 crates/trusted-proxies/src/cloud/metadata/gcp.rs create mode 100644 crates/trusted-proxies/src/cloud/metadata/mod.rs rename crates/trusted-proxies/src/cloud/{metadata.rs => ranges.rs} (99%) rename crates/trusted-proxies/src/config/{proxy.rs => env.rs} (100%) create mode 100644 crates/trusted-proxies/src/config/loader.rs create mode 100644 crates/trusted-proxies/src/config/types.rs create mode 100644 crates/trusted-proxies/src/error/config.rs create mode 100644 crates/trusted-proxies/src/error/mod.rs create mode 100644 crates/trusted-proxies/src/error/proxy.rs delete mode 100644 crates/trusted-proxies/src/globals.rs create mode 100644 crates/trusted-proxies/src/logging/middleware.rs create mode 100644 crates/trusted-proxies/src/logging/mod.rs create mode 100644 crates/trusted-proxies/src/main.rs delete mode 100644 crates/trusted-proxies/src/metrics.rs delete mode 100644 crates/trusted-proxies/src/middleware.rs create mode 100644 crates/trusted-proxies/src/middleware/layer.rs create mode 100644 crates/trusted-proxies/src/middleware/mod.rs create mode 100644 crates/trusted-proxies/src/middleware/service.rs delete mode 100644 crates/trusted-proxies/src/processor.rs delete mode 100644 crates/trusted-proxies/src/proxy.rs create mode 100644 crates/trusted-proxies/src/proxy/cache.rs create mode 100644 crates/trusted-proxies/src/proxy/chain.rs create mode 100644 crates/trusted-proxies/src/proxy/metrics.rs create mode 100644 crates/trusted-proxies/src/proxy/mod.rs create mode 100644 crates/trusted-proxies/src/proxy/validator.rs create mode 100644 crates/trusted-proxies/src/utils/ip.rs create mode 100644 crates/trusted-proxies/src/utils/mod.rs create mode 100644 crates/trusted-proxies/src/utils/validation.rs create mode 100644 crates/trusted-proxies/test/integration/cloud_tests.rs create mode 100644 crates/trusted-proxies/test/integration/mod.rs create mode 100644 crates/trusted-proxies/test/integration/proxy_tests.rs create mode 100644 crates/trusted-proxies/test/unit/config_tests.rs create mode 100644 crates/trusted-proxies/test/unit/mod.rs create mode 100644 crates/trusted-proxies/test/unit/validator_tests.rs diff --git a/Cargo.lock b/Cargo.lock index b772acda..b5627dba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1883,13 +1883,28 @@ dependencies = [ [[package]] name = "crc" -version = "3.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" dependencies = [ - "crc-catalog", + "crc-catalog 1.1.1", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog 2.4.0", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + [[package]] name = "crc-catalog" version = "2.4.0" @@ -1902,7 +1917,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" dependencies = [ - "crc", + "crc 3.4.0", "digest 0.10.7", "rand 0.9.2", "regex", @@ -5414,11 +5429,11 @@ dependencies = [ [[package]] name = "lzma-rust2" -version = "0.15.4" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48172246aa7c3ea28e423295dd1ca2589a24617cc4e588bb8cfe177cb2c54d95" +checksum = "7fa48f5024824ecd3e8282cc948bd46fbd095aed5a98939de0594601a59b4e2b" dependencies = [ - "crc", + "crc 2.1.0", "sha2 0.10.9", ] diff --git a/crates/trusted-proxies/src/advanced.rs b/crates/trusted-proxies/src/advanced.rs deleted file mode 100644 index c9c996d9..00000000 --- a/crates/trusted-proxies/src/advanced.rs +++ /dev/null @@ -1,201 +0,0 @@ -// 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 crate::cloud::fetch_cloud_provider_ips; -use crate::{ENV_PROXY_VALIDATION_MODE, MultiProxyConfig, TrustedProxiesConfig, TrustedProxy}; -use ipnetwork::IpNetwork; -use serde::{Deserialize, Serialize}; -use std::collections::HashSet; -use std::net::IpAddr; -use std::sync::Arc; -use thiserror::Error; - -/// Agent handling error types -#[derive(Error, Debug)] -pub enum ProxyError { - #[error("Invalid IP address format: {0}")] - InvalidIpFormat(String), - - #[error("Proxy Chain Verification Failed: {0}")] - ChainValidationFailed(String), - - #[error("Head parsing error: {0}")] - HeaderParseError(String), -} - -/// Proxy chain validation mode -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] -pub enum ValidationMode { - /// Loose mode: Accept the entire chain as long as the last agent is trusted - Lenient, - /// Strict mode: All agents in the chain are required to be trustworthy - Strict, - /// Hop Validation: Find the first untrusted agent from right to left - HopByHop, -} - -/// RFC 7239 Forwarded head parser -/// Format:Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43 -#[derive(Debug, Clone)] -pub struct ForwardedHeader { - /// Client address - pub for_client: Option, - /// Proxy identification - pub by_proxy: Option, - /// protocol - pub proto: Option, - /// Host - pub host: Option, -} - -/// Proxy chain analysis results -#[derive(Debug, Clone)] -pub struct ProxyChainAnalysis { - /// Trusted client IP (verified) - pub trusted_client_ip: IpAddr, - /// Complete proxy chain (from client to current agent) - pub full_proxy_chain: Vec, - /// Trusted Proxy Chain section - pub trusted_proxy_chain: Vec, - /// Verify the number of hops that pass - pub validated_hops: usize, - /// Whether it passes the integrity check - pub chain_integrity: bool, - /// The validation mode used - pub validation_mode_used: ValidationMode, - /// Possible safety warnings - pub security_warnings: Vec, -} - -/// Optimized CIDR matcher -pub struct CidrMatcher { - ipv4_networks: Vec, - ipv6_networks: Vec, - single_ips: HashSet, -} - -impl CidrMatcher { - pub fn new(config: &TrustedProxiesConfig) -> Self { - let mut ipv4_networks = Vec::new(); - let mut ipv6_networks = Vec::new(); - let mut single_ips = HashSet::new(); - - for proxy in &config.proxies { - match proxy { - TrustedProxy::Single(ip) => { - single_ips.insert(*ip); - } - TrustedProxy::Cidr(network) => match network { - IpNetwork::V4(_) => ipv4_networks.push(*network), - IpNetwork::V6(_) => ipv6_networks.push(*network), - }, - } - } - - // Sort by prefix length, with more specific networks taking precedence - ipv4_networks.sort_by(|a, b| b.prefix().cmp(&a.prefix())); - ipv6_networks.sort_by(|a, b| b.prefix().cmp(&a.prefix())); - - Self { - ipv4_networks, - ipv6_networks, - single_ips, - } - } - - pub fn contains(&self, ip: &IpAddr) -> bool { - // Start by checking individual IPs - if self.single_ips.contains(ip) { - return true; - } - - // Then check the CIDR range - match ip { - IpAddr::V4(ipv4) => { - for network in &self.ipv4_networks { - if let IpNetwork::V4(v4_net) = network - && v4_net.contains(*ipv4) - { - return true; - } - } - } - IpAddr::V6(ipv6) => { - for network in &self.ipv6_networks { - if let IpNetwork::V6(v6_net) = network - && v6_net.contains(*ipv6) - { - return true; - } - } - } - } - - false - } -} - -/// Environment-specific configuration loading -pub async fn load_production_config() -> MultiProxyConfig { - let mut config = MultiProxyConfig::default(); - - // Read from environment variables - if let Ok(mode_str) = std::env::var(ENV_PROXY_VALIDATION_MODE) { - config.validation_mode = match mode_str.as_str() { - "strict" => ValidationMode::Strict, - "lenient" => ValidationMode::Lenient, - _ => ValidationMode::HopByHop, - }; - } - - // Add trusted agents from cloud provider metadata - if let Ok(cloud_ips) = fetch_cloud_provider_ips().await { - for ip_range in cloud_ips { - if let Ok(network) = ip_range.parse::() { - config.allowed_private_nets.push(network); - } - } - } - - config -} - -/// Dynamic configuration updates -pub struct DynamicConfigManager { - current_config: Arc>, - config_watcher: tokio::sync::watch::Sender, -} - -impl DynamicConfigManager { - pub fn new(initial_config: MultiProxyConfig) -> Self { - let (sender, _) = tokio::sync::watch::channel(initial_config.clone()); - - Self { - current_config: Arc::new(std::sync::RwLock::new(initial_config)), - config_watcher: sender, - } - } - - pub fn update_config(&self, new_config: MultiProxyConfig) { - let mut config = self.current_config.write().unwrap(); - *config = new_config.clone(); - - // Notify all listeners - let _ = self.config_watcher.send(new_config); - } - - pub fn get_config(&self) -> MultiProxyConfig { - self.current_config.read().unwrap().clone() - } -} diff --git a/crates/trusted-proxies/src/api/handlers.rs b/crates/trusted-proxies/src/api/handlers.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/api/handlers.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/api/mod.rs b/crates/trusted-proxies/src/api/mod.rs new file mode 100644 index 00000000..83a31ce6 --- /dev/null +++ b/crates/trusted-proxies/src/api/mod.rs @@ -0,0 +1,15 @@ +// 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. + +mod handlers; diff --git a/crates/trusted-proxies/src/cache.rs b/crates/trusted-proxies/src/cache.rs deleted file mode 100644 index 588fdabe..00000000 --- a/crates/trusted-proxies/src/cache.rs +++ /dev/null @@ -1,51 +0,0 @@ -// 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 moka::future::Cache; -use std::net::IpAddr; -use std::time::Duration; - -/// High-performance IP verification cache -pub struct IpValidationCache { - /// Moka cache - cache: Cache, - /// Default cache expiration time - default_ttl: Duration, -} - -impl IpValidationCache { - pub fn new(capacity: usize, default_ttl: Duration) -> Self { - Self { - cache: Cache::builder() - .max_capacity(capacity as u64) - .time_to_live(default_ttl) - .build(), - default_ttl, - } - } - - /// Check if the IP is trusted (with cache) - pub async fn is_trusted(&self, ip: &IpAddr, validator: impl FnOnce(&IpAddr) -> bool) -> bool { - if let Some(is_trusted) = self.cache.get(ip).await { - return is_trusted; - } - - // Call the validation function - let is_trusted = validator(ip); - - // Update the cache - self.cache.insert(*ip, is_trusted).await; - is_trusted - } -} diff --git a/crates/trusted-proxies/src/cloud/detector.rs b/crates/trusted-proxies/src/cloud/detector.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/cloud/detector.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/cloud/metadata/aws.rs b/crates/trusted-proxies/src/cloud/metadata/aws.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/cloud/metadata/aws.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/cloud/metadata/azure.rs b/crates/trusted-proxies/src/cloud/metadata/azure.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/cloud/metadata/azure.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/cloud/metadata/gcp.rs b/crates/trusted-proxies/src/cloud/metadata/gcp.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/cloud/metadata/gcp.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/cloud/metadata/mod.rs b/crates/trusted-proxies/src/cloud/metadata/mod.rs new file mode 100644 index 00000000..b610d161 --- /dev/null +++ b/crates/trusted-proxies/src/cloud/metadata/mod.rs @@ -0,0 +1,17 @@ +// 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. + +mod aws; +mod azure; +mod gcp; diff --git a/crates/trusted-proxies/src/cloud/mod.rs b/crates/trusted-proxies/src/cloud/mod.rs index 6d185928..3b9b9c40 100644 --- a/crates/trusted-proxies/src/cloud/mod.rs +++ b/crates/trusted-proxies/src/cloud/mod.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod detector; mod metadata; +mod ranges; -pub use metadata::*; +pub use ranges::*; diff --git a/crates/trusted-proxies/src/cloud/metadata.rs b/crates/trusted-proxies/src/cloud/ranges.rs similarity index 99% rename from crates/trusted-proxies/src/cloud/metadata.rs rename to crates/trusted-proxies/src/cloud/ranges.rs index 80aecf6f..e455398b 100644 --- a/crates/trusted-proxies/src/cloud/metadata.rs +++ b/crates/trusted-proxies/src/cloud/ranges.rs @@ -855,7 +855,7 @@ mod tests { #[tokio::test] async fn test_cloud_metadata_fallback() { - use crate::cloud::metadata::CloudMetadataDetector; + use crate::cloud::ranges::CloudMetadataDetector; // In a test environment, the metadata service should not be available let detector = CloudMetadataDetector::new(); diff --git a/crates/trusted-proxies/src/config/proxy.rs b/crates/trusted-proxies/src/config/env.rs similarity index 100% rename from crates/trusted-proxies/src/config/proxy.rs rename to crates/trusted-proxies/src/config/env.rs diff --git a/crates/trusted-proxies/src/config/loader.rs b/crates/trusted-proxies/src/config/loader.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/config/loader.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/config/mod.rs b/crates/trusted-proxies/src/config/mod.rs index 78af5909..52da4fef 100644 --- a/crates/trusted-proxies/src/config/mod.rs +++ b/crates/trusted-proxies/src/config/mod.rs @@ -12,5 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod proxy; -pub use proxy::*; +mod env; +mod loader; +mod types; + +pub use env::*; diff --git a/crates/trusted-proxies/src/config/types.rs b/crates/trusted-proxies/src/config/types.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/config/types.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/error/config.rs b/crates/trusted-proxies/src/error/config.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/error/config.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/error/mod.rs b/crates/trusted-proxies/src/error/mod.rs new file mode 100644 index 00000000..33de8995 --- /dev/null +++ b/crates/trusted-proxies/src/error/mod.rs @@ -0,0 +1,16 @@ +// 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. + +mod config; +mod proxy; diff --git a/crates/trusted-proxies/src/error/proxy.rs b/crates/trusted-proxies/src/error/proxy.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/error/proxy.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/globals.rs b/crates/trusted-proxies/src/globals.rs deleted file mode 100644 index 39065194..00000000 --- a/crates/trusted-proxies/src/globals.rs +++ /dev/null @@ -1,125 +0,0 @@ -// 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 crate::{DEFAULT_TRUSTED_PROXIES, ENV_TRUSTED_PROXIES, TrustedProxiesConfig, parse_proxy_list}; -use std::sync::LazyLock; -use tokio::sync::RwLock; -use tracing::error; - -static TRUSTED_PROXIES_CONFIG: LazyLock> = - LazyLock::new(|| RwLock::new(TrustedProxiesConfig::default())); - -static TRUSTED_PROXIES_CONFIG_ENABLE: LazyLock> = LazyLock::new(|| RwLock::new(false)); - -/// Check if the trusted proxies configuration is enabled -/// -/// # Returns -/// A boolean indicating whether the trusted proxies configuration is enabled -pub async fn is_trusted_proxies_config_enabled() -> bool { - let guard = TRUSTED_PROXIES_CONFIG_ENABLE.read().await; - *guard -} - -/// Enable or disable the trusted proxies configuration -/// -/// # Arguments -/// * `enabled` - A boolean indicating whether to enable (true) or disable (false) the trusted proxies configuration -pub async fn set_trusted_proxies_config_enabled(enabled: bool) { - let mut guard = TRUSTED_PROXIES_CONFIG_ENABLE.write().await; - *guard = enabled; -} - -/// Get the global trusted proxies configuration -/// -/// # Returns -/// An Option containing the TrustedProxiesConfig if set, or None if not set -pub async fn get_trusted_proxies_config() -> TrustedProxiesConfig { - let guard = TRUSTED_PROXIES_CONFIG.read().await; - guard.clone() -} - -/// Set the global trusted proxies configuration -/// -/// # Arguments -/// * `config` - The TrustedProxiesConfig to set as the global configuration -pub async fn set_trusted_proxies_config(config: TrustedProxiesConfig) { - let mut guard = TRUSTED_PROXIES_CONFIG.write().await; - *guard = config; -} - -/// Initialize the trusted proxies configuration from environment variables or config file path -/// If both are provided, environment variables take precedence over the config file -/// Returns an error if loading from both sources fails -/// -/// # Returns -/// Ok(()) if the configuration is successfully loaded and set -/// -/// # Errors -/// Returns an error if loading from both environment variables and config file fails -/// -/// # Examples -/// ```no_run -/// use rustfs::proxy::config_loader::initialize; -/// -/// #[tokio::main] -/// async fn main() { -/// if let Err(e) = initialize().await { -/// eprintln!("Failed to initialize trusted proxies configuration: {:?}", e); -/// } -/// } -/// ``` -pub async fn initialize() -> anyhow::Result<()> { - match from_env() { - Ok(env_config) => { - set_trusted_proxies_config(env_config).await; - set_trusted_proxies_config_enabled(true).await; - Ok(()) - } - Err(e) => { - error!( - "Failed to load trusted proxies configuration from environment variables, falling back to config file if provided. error: {:?}", - e - ); - Err(e) - } - } -} - -/// Load configurations from environment variables -pub fn from_env() -> anyhow::Result { - // Read environment variables, e.g.: TRUSTED_PROXIES="127.0.0.1,10.0.0.0/8,::1" - let proxies_str = rustfs_utils::get_env_str(ENV_TRUSTED_PROXIES, DEFAULT_TRUSTED_PROXIES); - - let proxy_list = parse_proxy_list(&proxies_str); - - TrustedProxiesConfig::from_strs(&proxy_list) -} - -/// Loading from the configuration file -pub fn from_config_file(path: &str) -> anyhow::Result { - use serde_json; - use std::fs; - - let content = fs::read_to_string(path)?; - let config: serde_json::Value = serde_json::from_str(&content)?; - - let proxies = config["rustfs_http_trusted_proxies"] - .as_array() - .ok_or_else(|| anyhow::anyhow!("The trusted_proxies array is missing from the configuration file"))? - .iter() - .filter_map(|v| v.as_str()) - .collect::>(); - - TrustedProxiesConfig::from_strs(&proxies) -} diff --git a/crates/trusted-proxies/src/lib.rs b/crates/trusted-proxies/src/lib.rs index db22d030..f8c14a2f 100644 --- a/crates/trusted-proxies/src/lib.rs +++ b/crates/trusted-proxies/src/lib.rs @@ -12,21 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod advanced; -mod cache; +mod api; mod cloud; mod config; -mod globals; -mod metrics; +mod errors; +mod logging; mod middleware; -mod processor; mod proxy; +mod utils; -pub use advanced::*; -pub use cache::*; pub use cloud::*; pub use config::*; -pub use globals::*; -pub use middleware::*; -pub use processor::*; pub use proxy::*; diff --git a/crates/trusted-proxies/src/logging/middleware.rs b/crates/trusted-proxies/src/logging/middleware.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/logging/middleware.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/logging/mod.rs b/crates/trusted-proxies/src/logging/mod.rs new file mode 100644 index 00000000..4838978f --- /dev/null +++ b/crates/trusted-proxies/src/logging/mod.rs @@ -0,0 +1,15 @@ +// 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. + +mod middleware; diff --git a/crates/trusted-proxies/src/main.rs b/crates/trusted-proxies/src/main.rs new file mode 100644 index 00000000..b4a0b3d4 --- /dev/null +++ b/crates/trusted-proxies/src/main.rs @@ -0,0 +1,15 @@ +// 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. + +fn main() {} diff --git a/crates/trusted-proxies/src/metrics.rs b/crates/trusted-proxies/src/metrics.rs deleted file mode 100644 index 9af5b7de..00000000 --- a/crates/trusted-proxies/src/metrics.rs +++ /dev/null @@ -1,118 +0,0 @@ -// 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 crate::{MultiProxyProcessor, ProxyChainAnalysis, ProxyError}; -use metrics::{Counter, Gauge, Histogram, counter, gauge, histogram}; -use std::net::SocketAddr; -use std::time::Duration; - -/// Add cloud metadata monitoring -pub struct CloudMetadataMetrics { - fetch_success: Counter, - fetch_failure: Counter, - fetch_duration: Histogram, - ip_ranges_count: Gauge, -} - -impl CloudMetadataMetrics { - pub fn record_fetch(&self, success: bool, duration: Duration, count: usize) { - if success { - self.fetch_success.increment(1); - self.ip_ranges_count.set(count as f64); - } else { - self.fetch_failure.increment(1); - } - self.fetch_duration.record(duration.as_secs_f64()); - } -} - -/// Agents process monitoring metrics -pub struct ProxyMetrics { - /// The total number of requests processed - pub total_requests: Counter, - /// Number of requests from trusted agents - pub trusted_proxy_requests: Counter, - /// Number of requests from untrusted sources - pub untrusted_requests: Counter, - /// The number of requests that failed to validate - pub validation_failed: Counter, - /// Proxy chain length distribution - pub chain_length: Histogram, - /// Validation is time-consuming - pub validation_duration: Histogram, - /// Cache hit rate - pub cache_hit_ratio: Gauge, -} - -impl ProxyMetrics { - pub fn new() -> Self { - Self { - total_requests: counter!("proxy.total_requests", "description" => "The total number of requests processed"), - trusted_proxy_requests: counter!("proxy.trusted_requests", "description" => "Requests from trusted agents"), - untrusted_requests: counter!("proxy.untrusted_requests", "description" => "Requests from untrusted sources"), - validation_failed: counter!("proxy.validation_failed", "description" => "Validation failed requests"), - chain_length: histogram!("proxy.chain_length", "description" => "Proxy chain length distribution"), - validation_duration: histogram!("proxy.validation_duration_ms", "description" => "Validation duration in milliseconds"), - cache_hit_ratio: gauge!("proxy.cache_hit_ratio", "description" => "Cache hit rate"), - } - } - - pub fn record_request(&self, analysis: &ProxyChainAnalysis, duration_ms: f64) { - self.total_requests.increment(1); - self.chain_length.record(analysis.full_proxy_chain.len() as f64); - self.validation_duration.record(duration_ms); - - if analysis.validated_hops > 0 { - self.trusted_proxy_requests.increment(1); - } else { - self.untrusted_requests.increment(1); - } - - if !analysis.security_warnings.is_empty() { - self.validation_failed.increment(1); - } - } -} - -/// Add monitoring in the MultiProxyProcessor -pub struct MonitoredMultiProxyProcessor { - processor: MultiProxyProcessor, - metrics: ProxyMetrics, -} - -impl MonitoredMultiProxyProcessor { - pub fn process_request_with_metrics( - &self, - peer_addr: &SocketAddr, - headers: &http::HeaderMap, - ) -> Result { - let start = std::time::Instant::now(); - - let result = self.processor.process_request(peer_addr, headers); - - let duration_ms = start.elapsed().as_secs_f64() * 1000.0; - - match &result { - Ok(analysis) => { - self.metrics.record_request(analysis, duration_ms); - } - Err(_) => { - self.metrics.validation_failed.increment(1); - self.metrics.total_requests.increment(1); - } - } - - result - } -} diff --git a/crates/trusted-proxies/src/middleware.rs b/crates/trusted-proxies/src/middleware.rs deleted file mode 100644 index fb3ab07d..00000000 --- a/crates/trusted-proxies/src/middleware.rs +++ /dev/null @@ -1,298 +0,0 @@ -// 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 crate::{ClientInfo, MultiProxyConfig, MultiProxyProcessor, TrustedProxiesConfig}; -use axum::extract::Request; -use axum::response::Response; -use std::net::{IpAddr, SocketAddr}; -use std::task::{Context, Poll}; -use tower::{Layer, Service}; -use tracing::{info_span, instrument}; - -/// Enhanced client information structure -#[derive(Debug, Clone)] -pub struct EnhancedClientInfo { - pub real_ip: IpAddr, - pub forwarded_host: Option, - pub forwarded_proto: Option, - pub is_from_trusted_proxy: bool, - pub proxy_ip: Option, - pub proxy_chain_analysis: Option, -} - -/// Trusted agent middleware layer -#[derive(Clone)] -pub struct TrustedProxiesLayer { - config: TrustedProxiesConfig, -} - -impl TrustedProxiesLayer { - pub fn new(config: TrustedProxiesConfig) -> Self { - Self { config } - } -} - -impl Layer for TrustedProxiesLayer { - type Service = TrustedProxiesMiddleware; - - fn layer(&self, inner: S) -> Self::Service { - TrustedProxiesMiddleware { - inner, - config: self.config.clone(), - } - } -} - -/// Trusted agent middleware service -#[derive(Clone)] -pub struct TrustedProxiesMiddleware { - inner: S, - config: TrustedProxiesConfig, -} - -impl Service for TrustedProxiesMiddleware -where - S: Service + Clone + Send + 'static, - S::Future: Send, -{ - type Response = S::Response; - type Error = S::Error; - type Future = S::Future; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - /// Updated the call method of TrustedProxiesMiddleware - #[instrument(name = "trusted_proxy_middleware", skip_all, fields(peer_addr, trusted))] - fn call(&mut self, mut req: Request) -> Self::Future { - let span = info_span!( - "trusted_proxy_check", - peer_addr = ?req.extensions().get::().map(|a| a.to_string()) - ); - - let _guard = span.enter(); - - // Use advanced processors - let processor = MultiProxyProcessor::new(self.config.clone(), Some(MultiProxyConfig::default())); - - let peer_addr = req.extensions().get::().copied(); - let headers = req.headers(); - - match peer_addr { - Some(addr) => { - match processor.process_request(&addr, headers) { - Ok(analysis) => { - tracing::debug!( - "Proxy chain analysis completed: client_ip={}, hops={}, integrity={}", - analysis.trusted_client_ip, - analysis.validated_hops, - analysis.chain_integrity - ); - - // 创建增强的客户端信息 - let client_info = EnhancedClientInfo { - real_ip: analysis.trusted_client_ip, - forwarded_host: headers - .get("x-forwarded-host") - .and_then(|h| h.to_str().ok()) - .map(String::from), - forwarded_proto: headers - .get("x-forwarded-proto") - .and_then(|h| h.to_str().ok()) - .map(String::from), - is_from_trusted_proxy: analysis.validated_hops > 0, - proxy_ip: Some(addr.ip()), - proxy_chain_analysis: Some(analysis), - }; - - req.extensions_mut().insert(client_info.clone()); - - // Record safety warnings - if !client_info - .proxy_chain_analysis - .as_ref() - .unwrap() - .security_warnings - .is_empty() - { - tracing::warn!( - "Proxy Chain Security Warning: {:?}", - client_info.proxy_chain_analysis.as_ref().unwrap().security_warnings - ); - } - } - Err(err) => { - tracing::warn!("Proxy chain validation failed: {}", err); - - // Fallback to basic processing when validation fails - let client_info = if self.config.is_trusted(&addr) { - extract_client_info_from_headers(&req) - } else { - ClientInfo::direct(addr) - }; - - req.extensions_mut().insert(client_info); - } - } - } - None => { - tracing::warn!("The peer address cannot be obtained"); - let client_info = ClientInfo { - real_ip: std::net::Ipv4Addr::UNSPECIFIED.into(), - forwarded_host: None, - forwarded_proto: None, - is_from_trusted_proxy: false, - proxy_ip: None, - }; - req.extensions_mut().insert(client_info); - } - } - - self.inner.call(req) - } -} - -/// Handle client-side information extraction logic -fn process_client_info(peer_addr: Option, config: &TrustedProxiesConfig, req: &Request) -> ClientInfo { - match peer_addr { - Some(addr) => { - if config.is_trusted(&addr) { - // From Trusted Agent: Parse the forwarding header - extract_from_trusted_proxy(&addr, req) - } else { - // From an untrusted proxy or direct connection: Use the connection address - tracing::debug!( - "Request from untrusted proxy or direct connection: {}, ignore x-forwarded-*header", - addr.ip() - ); - ClientInfo::direct(addr) - } - } - None => { - // Unable to get peer address (shouldn't happen in theory) - tracing::warn!("Unable to get the peer address of the request"); - ClientInfo::direct(SocketAddr::from(([0, 0, 0, 0], 0))) - } - } -} - -/// Extract client information from HTTP headers -fn extract_client_info_from_headers(req: &Request) -> ClientInfo { - let headers = req.headers(); - - // Parsing X-Forwarded-For: Take the first IP (original client) - let real_ip = 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()) - .unwrap_or_else(|| { - // Fallback to X-Real-IP or Connector IP - headers - .get("x-real-ip") - .and_then(|h| h.to_str().ok()) - .and_then(|s| s.parse().ok()) - .unwrap_or(std::net::Ipv4Addr::UNSPECIFIED.into()) - }); - - let forwarded_host = headers - .get("x-forwarded-host") - .and_then(|h| h.to_str().ok()) - .map(String::from); - - let forwarded_proto = headers - .get("x-forwarded-proto") - .and_then(|h| h.to_str().ok()) - .map(String::from); - - ClientInfo { - real_ip, - forwarded_host, - forwarded_proto, - is_from_trusted_proxy: false, - proxy_ip: None, - } -} - -/// Extract client information from requests from trusted agents -fn extract_from_trusted_proxy(proxy_addr: &SocketAddr, req: &Request) -> ClientInfo { - let headers = req.headers(); - let proxy_ip = proxy_addr.ip(); - - // Resolve X-Forwarded-For Link - let real_ip = headers - .get("x-forwarded-for") - .and_then(|h| h.to_str().ok()) - .and_then(|xff| { - // Handle possible IP chains: client, proxy1, proxy2 - parse_x_forwarded_for(xff, proxy_ip) - }) - .unwrap_or_else(|| { - // Fall back to X-Real-IP or use a proxy IP - headers - .get("x-real-ip") - .and_then(|h| h.to_str().ok()) - .and_then(|s| s.parse().ok()) - .unwrap_or(proxy_ip) // Finally retreated - }); - - // Extract other forwarding heads - let forwarded_host = headers - .get("x-forwarded-host") - .and_then(|h| h.to_str().ok()) - .map(String::from); - - let forwarded_proto = headers - .get("x-forwarded-proto") - .and_then(|h| h.to_str().ok()) - .map(String::from); - - tracing::debug!( - "Extract client information from trusted proxy {}: real_ip={}, host={:?}, proto={:?}", - proxy_ip, - real_ip, - forwarded_host, - forwarded_proto - ); - - ClientInfo::from_trusted_proxy(real_ip, forwarded_host, forwarded_proto, proxy_ip) -} - -/// Parse a comma-separated list of proxies/IPs -/// Processing format: "ip1, ip2, ip3" -/// -/// #Arguments -/// * `proxies_str` - A string slice containing the comma-separated list of proxies/IPs -/// -/// #Returns -/// A vector of string slices representing the individual proxies/IPs -pub fn parse_proxy_list(proxies_str: &str) -> Vec<&str> { - proxies_str.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()).collect() -} - -/// Securely parses X-Forwarded-For heads -/// Processing format: "client, proxy1, proxy2" or "client" -fn parse_x_forwarded_for(xff: &str, _current_proxy_ip: IpAddr) -> Option { - // Split and clean up IP addresses - let ips: Vec<&str> = parse_proxy_list(xff); - - if ips.is_empty() { - return None; - } - - // Take the first IP (original client) - // Note: In a production environment, more complex logic may be required to handle multi-layer proxy chains - ips.first().and_then(|ip_str| ip_str.parse().ok()) -} diff --git a/crates/trusted-proxies/src/middleware/layer.rs b/crates/trusted-proxies/src/middleware/layer.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/middleware/layer.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/middleware/mod.rs b/crates/trusted-proxies/src/middleware/mod.rs new file mode 100644 index 00000000..4d4cdab0 --- /dev/null +++ b/crates/trusted-proxies/src/middleware/mod.rs @@ -0,0 +1,16 @@ +// 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. + +mod layer; +mod service; diff --git a/crates/trusted-proxies/src/middleware/service.rs b/crates/trusted-proxies/src/middleware/service.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/middleware/service.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/processor.rs b/crates/trusted-proxies/src/processor.rs deleted file mode 100644 index 2be594fe..00000000 --- a/crates/trusted-proxies/src/processor.rs +++ /dev/null @@ -1,709 +0,0 @@ -// 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 crate::{ - CidrMatcher, ForwardedHeader, IpValidationCache, MultiProxyConfig, ProxyChainAnalysis, ProxyError, TrustedProxiesConfig, - TrustedProxy, ValidationMode, -}; -use ipnetwork::IpNetwork; -use std::collections::HashSet; -use std::net::{IpAddr, SocketAddr}; -use std::sync::Arc; -use std::time::Duration; -use tracing::{debug, warn}; - -/// Securely handle X-Forwarded-For headers for multi-layer agents -pub struct ProxyChainProcessor { - config: TrustedProxiesConfig, -} - -impl ProxyChainProcessor { - pub fn new(config: TrustedProxiesConfig) -> Self { - Self { config } - } - - /// Parse the X-Forwarded-For chain to find the rightmost untrusted IP - /// Principle: Traverse from right to left to find the first IP that is not on the trusted list - pub fn extract_client_ip_from_chain(&self, x_forwarded_for: &str, current_proxy_ip: IpAddr) -> Option { - // Split the IP chain - let ip_chain: Vec<&str> = x_forwarded_for - .split(',') - .map(|s| s.trim()) - .filter(|s| !s.is_empty()) - .collect(); - - if ip_chain.is_empty() { - return None; - } - - // Build a complete chain: client, proxy1, proxy2, ..., current_proxy - let mut full_chain: Vec = ip_chain.iter().filter_map(|s| s.parse().ok()).collect(); - full_chain.push(current_proxy_ip); - - // Find from right to left (starting with the agent closest to us) - // Find the first untrusted IP - for ip in full_chain.iter().rev() { - if !self.is_ip_trusted(ip) { - return Some(*ip); - } - } - - // If all IPs are trusted, return the first IP of the original chain - full_chain.first().copied() - } - - /// Check if a single IP is trustworthy - fn is_ip_trusted(&self, ip: &IpAddr) -> bool { - // Simplifying the implementation here, you should actually use the full SocketAddr - // Port information needs to be considered in a production environment - use std::net::SocketAddr; - let dummy_port = 0; - let addr = SocketAddr::new(*ip, dummy_port); - self.config.is_trusted(&addr) - } -} - -/// Multi-layer proxy processor core implementation -pub struct MultiProxyProcessor { - /// Basic trusted agent configuration - base_config: TrustedProxiesConfig, - /// Advanced configuration - advanced_config: MultiProxyConfig, - /// Known collection of trusted agents (cache, accelerated lookup) - trusted_ips_cache: HashSet, - /// A collection of known private networks - private_nets_cache: Vec, -} - -impl MultiProxyProcessor { - /// Create a new processor - pub fn new(base_config: TrustedProxiesConfig, advanced_config: Option) -> Self { - let config = advanced_config.unwrap_or_default(); - - // Build IP caches to improve performance - let mut trusted_ips_cache = HashSet::new(); - for proxy in &base_config.proxies { - match proxy { - TrustedProxy::Single(ip) => { - trusted_ips_cache.insert(*ip); - } - TrustedProxy::Cidr(network) => { - // For large networks, we only cache the network itself, runtime checks - // Here we cache the prefix of the network - if network.prefix() >= 24 { - // For small networks (/24 and above), we can cache all IPs - // But for simplicity, only the network representation is cached here - } - } - } - } - - Self { - base_config, - advanced_config: config.clone(), - trusted_ips_cache, - private_nets_cache: config.allowed_private_nets.clone(), - } - } - - /// Main processing function: Extracts and verifies the client IP from the request - pub fn process_request(&self, peer_addr: &SocketAddr, headers: &http::HeaderMap) -> Result { - debug!("Start processing proxy request, peer address: {}", peer_addr); - - // 1. Check if it's from a trusted agent - if !self.base_config.is_trusted(peer_addr) { - warn!("Request from Untrusted Agent: {}", peer_addr); - return self.handle_untrusted_source(peer_addr); - } - - // 2. Try multiple head resolution strategies - let analysis = self.analyze_proxy_chain(peer_addr.ip(), headers)?; - - // 3. Perform security checks - self.perform_security_checks(&analysis)?; - - Ok(analysis) - } - - /// Analyze the core approach of proxy chains - fn analyze_proxy_chain(&self, current_proxy_ip: IpAddr, headers: &http::HeaderMap) -> Result { - // Collect all available proxy chain information - let mut proxy_chains = Vec::new(); - let mut security_warnings = Vec::new(); - - // Strategy 1: Prioritize the use of RFC 7239 Forwarded headers - if self.advanced_config.enable_rfc7239 - && let Some(forwarded) = Self::parse_forwarded_header(headers) - && let Some(rfc_chain) = Self::extract_chain_from_forwarded(&forwarded) - { - proxy_chains.push(("rfc7239", rfc_chain)); - } - - // Strategy 2: Use traditional X-Forwarded-For heads - if let Some(xff_chain) = Self::parse_x_forwarded_for(headers) { - proxy_chains.push(("xff", xff_chain)); - } - - // Strategy 3: Use X-Real-IP as a backup - if let Some(real_ip) = Self::parse_x_real_ip(headers) { - proxy_chains.push(("x-real-ip", vec![real_ip])); - } - - // If no proxy information is found - if proxy_chains.is_empty() { - debug!("No proxy header found, using direct connection IP"); - return Ok(ProxyChainAnalysis { - trusted_client_ip: current_proxy_ip, - full_proxy_chain: vec![current_proxy_ip], - trusted_proxy_chain: vec![current_proxy_ip], - validated_hops: 0, - chain_integrity: true, - validation_mode_used: ValidationMode::Lenient, - security_warnings: vec!["Agent header not used, possibly direct connection".to_string()], - }); - } - - // Choose the most reliable proxy chain (RFC 7239 preferred) - let (source, mut full_chain) = proxy_chains - .into_iter() - .max_by_key(|(source, _)| match *source { - "rfc7239" => 3, - "xff" => 2, - "x-real-ip" => 1, - _ => 0, - }) - .unwrap(); - - debug!("Use proxy chain source: {}, chain: {:?}", source, full_chain); - - // Add the current proxy IP to the end of the chain - full_chain.push(current_proxy_ip); - - // Handle the proxy chain according to the configured validation mode - let (trusted_client_ip, trusted_proxy_chain, validated_hops) = match self.advanced_config.validation_mode { - ValidationMode::Lenient => self.validate_lenient(&full_chain), - ValidationMode::Strict => self.validate_strict(&full_chain)?, - ValidationMode::HopByHop => self.validate_hop_by_hop(&full_chain)?, - }; - - // Check for chain continuity - let chain_integrity = if self.advanced_config.enable_chain_continuity_check { - self.check_chain_continuity(&full_chain, &trusted_proxy_chain) - } else { - true - }; - - // Collect safety warnings - if !chain_integrity { - security_warnings.push("Proxy chain continuity check failed".to_string()); - } - - if full_chain.len() > self.advanced_config.max_proxy_hops { - security_warnings.push(format!( - "Proxy chain length exceeds the limit:{}/{}", - full_chain.len(), - self.advanced_config.max_proxy_hops - )); - } - - Ok(ProxyChainAnalysis { - trusted_client_ip, - full_proxy_chain: full_chain, - trusted_proxy_chain, - validated_hops, - chain_integrity, - validation_mode_used: self.advanced_config.validation_mode, - security_warnings, - }) - } - - /// Permissive validation mode: As long as the last agent is trusted, the entire chain is accepted - fn validate_lenient(&self, chain: &[IpAddr]) -> (IpAddr, Vec, usize) { - if chain.is_empty() { - return (IpAddr::from([0, 0, 0, 0]), vec![], 0); - } - - // Take the first IP in the chain as the client IP - let client_ip = chain[0]; - - // Verify that the last agent is trustworthy - let last_proxy = chain.last().unwrap(); - let is_last_trusted = self.is_ip_trusted(last_proxy); - - if is_last_trusted { - (client_ip, chain.to_vec(), chain.len()) - } else { - // If the last proxy is not trusted, use the IP of the last proxy - (*last_proxy, vec![*last_proxy], 0) - } - } - - /// Strict Verification Model: All agents in the chain are required to be trustworthy - fn validate_strict(&self, chain: &[IpAddr]) -> Result<(IpAddr, Vec, usize), ProxyError> { - if chain.is_empty() { - return Ok((IpAddr::from([0, 0, 0, 0]), vec![], 0)); - } - - // Check if each agent is trustworthy - for (i, ip) in chain.iter().enumerate() { - if !self.is_ip_trusted(ip) { - return Err(ProxyError::ChainValidationFailed(format!( - "The {} agent ({}) in the chain is not trustworthy", - i + 1, - ip - ))); - } - } - - Ok(( - chain[0], // The first IP is the client - chain.to_vec(), - chain.len(), - )) - } - - /// Hop Validation Mode: Find the first untrusted agent from right to left - fn validate_hop_by_hop(&self, chain: &[IpAddr]) -> Result<(IpAddr, Vec, usize), ProxyError> { - if chain.is_empty() { - return Ok((IpAddr::from([0, 0, 0, 0]), vec![], 0)); - } - - let mut trusted_chain = Vec::new(); - let mut validated_hops = 0; - - // Traversing from right to left (starting with the agent closest to us) - for ip in chain.iter().rev() { - if self.is_ip_trusted(ip) { - trusted_chain.insert(0, *ip); - validated_hops += 1; - } else { - // Find the first untrusted agent and stop traversing - break; - } - } - - if trusted_chain.is_empty() { - // No trusted proxy, using the last IP of the chain - let last_ip = *chain.last().unwrap(); - Ok((last_ip, vec![last_ip], 0)) - } else { - // The client IP is the one that preceded the first IP of the trusted chain - // Or if the entire chain is trustworthy, it is the first IP of the original chain - let client_ip_index = chain.len().saturating_sub(trusted_chain.len()); - let client_ip = if client_ip_index > 0 { - chain[client_ip_index - 1] - } else { - chain[0] - }; - - Ok((client_ip, trusted_chain, validated_hops)) - } - } - - /// Check the continuity of the proxy chain - /// Validation rules: The path from the client to the server should be continuous - fn check_chain_continuity(&self, full_chain: &[IpAddr], trusted_chain: &[IpAddr]) -> bool { - if full_chain.len() <= 1 || trusted_chain.is_empty() { - return true; - } - - // Verify that the trusted chain is indeed the tail continuous part of the complete chain - let expected_tail = &full_chain[full_chain.len() - trusted_chain.len()..]; - expected_tail == trusted_chain - } - - /// Handle requests from untrusted sources - fn handle_untrusted_source(&self, peer_addr: &SocketAddr) -> Result { - let ip = peer_addr.ip(); - - // Check if it's a private address (it could be an internal service call) - let is_private = self.private_nets_cache.iter().any(|network| network.contains(ip)); - - let warnings = if is_private { - vec!["From an internal network but not configured as a trusted agent".to_string()] - } else { - vec!["From an untrusted public address".to_string()] - }; - - Ok(ProxyChainAnalysis { - trusted_client_ip: ip, - full_proxy_chain: vec![ip], - trusted_proxy_chain: vec![ip], - validated_hops: 0, - chain_integrity: true, - validation_mode_used: ValidationMode::Lenient, - security_warnings: warnings, - }) - } - - /// Perform security checks - fn perform_security_checks(&self, analysis: &ProxyChainAnalysis) -> Result<(), ProxyError> { - // Check the proxy chain length - if analysis.full_proxy_chain.len() > self.advanced_config.max_proxy_hops { - return Err(ProxyError::ChainValidationFailed(format!( - "The proxy chain is too long:{} > {}", - analysis.full_proxy_chain.len(), - self.advanced_config.max_proxy_hops - ))); - } - - // Check if the client IP is valid - if analysis.trusted_client_ip.is_unspecified() { - return Err(ProxyError::ChainValidationFailed( - "The client IP is an unspecified address (0.0.0.0 or::)".to_string(), - )); - } - - // Check if the loopback address (it could be a misconfiguration or an attack) - if analysis.trusted_client_ip.is_loopback() && analysis.validated_hops > 1 { - warn!( - "The client IP is a loopback address, but it goes through multiple layers of proxies: {}", - analysis.trusted_client_ip - ); - } - - // Check the multicast address - if analysis.trusted_client_ip.is_multicast() { - return Err(ProxyError::ChainValidationFailed("The client IP is the multicast address".to_string())); - } - - Ok(()) - } - - /// Check if a single IP is trustworthy - fn is_ip_trusted(&self, ip: &IpAddr) -> bool { - // Check the cache first - if self.trusted_ips_cache.contains(ip) { - return true; - } - - // Then check the CIDR range - let dummy_port = 0; - let addr = SocketAddr::new(*ip, dummy_port); - self.base_config.is_trusted(&addr) - } - - /// Parsing RFC 7239 Forwarded head - fn parse_forwarded_header(headers: &http::HeaderMap) -> Option { - let forwarded_value = headers.get("forwarded")?.to_str().ok()?; - - // Forwarded headers can have multiple values, separated by commas - // We take the first one - let first_part = forwarded_value.split(',').next()?.trim(); - - let mut result = ForwardedHeader { - for_client: None, - by_proxy: None, - proto: None, - host: None, - }; - - // Parsing key-value pairs, such as:for=192.0.2.60;proto=http;by=203.0.113.43 - for part in first_part.split(';') { - let part = part.trim(); - if let Some((key, value)) = part.split_once('=') { - match key.trim().to_lowercase().as_str() { - "for" => { - // Remove possible quotes and port numbers - let clean_value = value.trim_matches('"'); - if let Ok(ip) = clean_value.split(':').next().unwrap_or(clean_value).parse() { - result.for_client = Some(ip); - } - } - "by" => { - let clean_value = value.trim_matches('"'); - if let Ok(ip) = clean_value.split(':').next().unwrap_or(clean_value).parse() { - result.by_proxy = Some(ip); - } - } - "proto" => { - result.proto = Some(value.trim_matches('"').to_string()); - } - "host" => { - result.host = Some(value.trim_matches('"').to_string()); - } - _ => {} - } - } - } - - Some(result) - } - - /// Extract the proxy chain from the Forwarded header - fn extract_chain_from_forwarded(forwarded: &ForwardedHeader) -> Option> { - let mut chain = Vec::new(); - - // If there is a for field, it is the client IP - if let Some(client_ip) = &forwarded.for_client { - chain.push(*client_ip); - } - - // If there is a by field, it may be a proxy IP - // Note: In RFC 7239, multiple agents will have multiple Forwarded header values - // Simplify the processing here, just take one - - if chain.is_empty() { None } else { Some(chain) } - } - - /// Parsing X-Forwarded-For head - fn parse_x_forwarded_for(headers: &http::HeaderMap) -> Option> { - let xff_value = headers.get("x-forwarded-for")?.to_str().ok()?; - - let ips: Vec = xff_value - .split(',') - .map(|s| s.trim()) - .filter(|s| !s.is_empty()) - .filter_map(|s| { - // May contain the port number, take only the IP part - let ip_part = s.split(':').next().unwrap_or(s); - ip_part.parse().ok() - }) - .collect(); - - if ips.is_empty() { None } else { Some(ips) } - } - - /// Parsing the X-Real-IP head - fn parse_x_real_ip(headers: &http::HeaderMap) -> Option { - let value = headers.get("x-real-ip")?.to_str().ok()?; - value.parse().ok() - } -} - -// Update the MultiProxyProcessor to use the cache -pub struct OptimizedMultiProxyProcessor { - base_config: Arc, - advanced_config: MultiProxyConfig, - ip_cache: std::sync::Mutex, - cidr_matcher: CidrMatcher, -} - -impl OptimizedMultiProxyProcessor { - pub fn new(base_config: TrustedProxiesConfig, advanced_config: Option) -> Self { - let config = advanced_config.unwrap_or_default(); - - // Pre-compiled CIDR matcher - let cidr_matcher = CidrMatcher::new(&base_config); - - Self { - base_config: Arc::new(base_config), - advanced_config: config, - ip_cache: std::sync::Mutex::new(IpValidationCache::new(10000, Duration::from_secs(300))), - cidr_matcher, - } - } - - /// Optimized IP trusted checks - async fn is_ip_trusted_optimized(&self, ip: &IpAddr) -> bool { - let cache = self.ip_cache.lock().unwrap(); - - cache - .is_trusted(ip, |ip| { - // Fast Path: Check individual IP caches - // Slow path: Check the CIDR range - self.cidr_matcher.contains(ip) - }) - .await - } -} - -// Unit tests -#[cfg(test)] -mod tests { - use super::*; - use crate::{MultiProxyProcessor, ProxyChainAnalysis, TrustedProxiesConfig, ValidationMode}; - use axum::http::HeaderMap; - use std::net::{IpAddr, SocketAddr}; - use std::str::FromStr; - - fn create_test_processor() -> MultiProxyProcessor { - let base_config = - TrustedProxiesConfig::from_strs(&["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1"]).unwrap(); - - MultiProxyProcessor::new(base_config, None) - } - - #[test] - fn test_parse_x_forwarded_for() { - let mut headers = HeaderMap::new(); - headers.insert("X-Forwarded-For", "203.0.113.195, 198.51.100.1, 10.0.1.100".parse().unwrap()); - - let result = MultiProxyProcessor::parse_x_forwarded_for(&headers).unwrap(); - assert_eq!(result.len(), 3); - assert_eq!(result[0], IpAddr::from_str("203.0.113.195").unwrap()); - } - - #[test] - fn test_parse_x_forwarded_for_with_ports() { - let mut headers = HeaderMap::new(); - headers.insert("X-Forwarded-For", "203.0.113.195:1234, 198.51.100.1:80".parse().unwrap()); - - let result = MultiProxyProcessor::parse_x_forwarded_for(&headers).unwrap(); - assert_eq!(result.len(), 2); - assert_eq!(result[0], IpAddr::from_str("203.0.113.195").unwrap()); - } - - #[test] - fn test_parse_forwarded_header() { - let mut headers = HeaderMap::new(); - headers.insert("Forwarded", r#"for=192.0.2.60;proto=http;by=203.0.113.43"#.parse().unwrap()); - - let result = MultiProxyProcessor::parse_forwarded_header(&headers).unwrap(); - assert_eq!(result.for_client, Some(IpAddr::from_str("192.0.2.60").unwrap())); - assert_eq!(result.by_proxy, Some(IpAddr::from_str("203.0.113.43").unwrap())); - assert_eq!(result.proto, Some("http".to_string())); - } - - #[test] - fn test_validate_hop_by_hop() { - let processor = create_test_processor(); - - // Test chain: Client (public) -> Proxy 1 (private) -> Proxy 2 (private) - let chain = vec![ - IpAddr::from_str("8.8.8.8").unwrap(), // Client (not trusted) - IpAddr::from_str("10.0.1.100").unwrap(), // Agent 1 (Trusted) - IpAddr::from_str("192.168.1.50").unwrap(), // Agent 2 (Trusted) - ]; - - let result = processor.validate_hop_by_hop(&chain).unwrap(); - assert_eq!(result.0, IpAddr::from_str("8.8.8.8").unwrap()); // Client IP - assert_eq!(result.2, 2); // Verified 2 jumps - } - - #[test] - fn test_chain_continuity_check() { - let processor = create_test_processor(); - - // Complete chain - let full_chain = vec![ - IpAddr::from_str("8.8.8.8").unwrap(), - IpAddr::from_str("10.0.1.100").unwrap(), - IpAddr::from_str("192.168.1.50").unwrap(), - ]; - - // The trusted chain should be the tail continuous part of the complete chain - let trusted_chain = vec![ - IpAddr::from_str("10.0.1.100").unwrap(), - IpAddr::from_str("192.168.1.50").unwrap(), - ]; - - assert!(processor.check_chain_continuity(&full_chain, &trusted_chain)); - - // Discontinuous cases should fail - let bad_trusted_chain = vec![IpAddr::from_str("192.168.1.50").unwrap()]; - assert!(!processor.check_chain_continuity(&full_chain, &bad_trusted_chain)); - } - - #[test] - fn test_security_checks() { - let processor = create_test_processor(); - - // Create test analysis results - let analysis = ProxyChainAnalysis { - trusted_client_ip: IpAddr::from([0, 0, 0, 0]), // Invalid address - full_proxy_chain: Vec::new(), - trusted_proxy_chain: Vec::new(), - validated_hops: 0, - chain_integrity: true, - validation_mode_used: ValidationMode::Lenient, - security_warnings: Vec::new(), - }; - - // Should fail because the client IP is an unspecified address - assert!(processor.perform_security_checks(&analysis).is_err()); - } - - #[tokio::test] - async fn test_complex_proxy_chain() { - use crate::TrustedProxiesConfig; - use crate::{MultiProxyConfig, MultiProxyProcessor, ValidationMode}; - use axum::http::HeaderMap; - use std::net::SocketAddr; - - // Create a test configuration - let base_config = TrustedProxiesConfig::from_strs(&["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]).unwrap(); - - let advanced_config = MultiProxyConfig { - validation_mode: ValidationMode::HopByHop, - enable_rfc7239: true, - max_proxy_hops: 5, - ..Default::default() - }; - - let processor = MultiProxyProcessor::new(base_config, Some(advanced_config)); - - // Simulate complex proxy chains - let peer_addr = SocketAddr::from(([192, 168, 1, 100], 8080)); - - let mut headers = HeaderMap::new(); - headers.insert("X-Forwarded-For", "203.0.113.195, 10.0.1.50, 172.16.0.10, 192.168.1.1".parse().unwrap()); - headers.insert("X-Forwarded-Proto", "https".parse().unwrap()); - headers.insert("X-Forwarded-Host", "api.example.com".parse().unwrap()); - - // Test processing - let result = processor.process_request(&peer_addr, &headers).unwrap(); - - assert_eq!(result.trusted_client_ip.to_string(), "203.0.113.195"); - assert_eq!(result.validated_hops, 3); // 192.168.1.1, 172.16.0.10, 10.0.1.50 - assert!(result.chain_integrity); - - // Test RFC 7239 head - let mut rfc_headers = HeaderMap::new(); - rfc_headers.insert( - "Forwarded", - r#"for=192.0.2.60;proto=https;by=203.0.113.43,for=198.51.100.17"#.parse().unwrap(), - ); - - let rfc_result = processor.process_request(&peer_addr, &rfc_headers).unwrap(); - assert_eq!(rfc_result.trusted_client_ip.to_string(), "192.0.2.60"); - } - - #[tokio::test] - async fn test_proxy_chain_attack_scenarios() { - use crate::MultiProxyProcessor; - - let base_config = TrustedProxiesConfig::from_strs(&["10.0.0.0/8"]).unwrap(); - let processor = MultiProxyProcessor::new(base_config, None); - - // Scenario 1: Excessive Proxy Chain Attack - let peer_addr = SocketAddr::from(([10, 0, 0, 1], 80)); - let mut headers = HeaderMap::new(); - - // Create a very long chain (10 hops over the default limit) - let long_chain = (0..15).map(|i| format!("10.0.{}.1", i)).collect::>().join(", "); - - headers.insert("X-Forwarded-For", long_chain.parse().unwrap()); - - let result = processor.process_request(&peer_addr, &headers); - assert!(result.is_err()); - - // Scenario 2: IP spoofing attack - let mut attack_headers = HeaderMap::new(); - attack_headers.insert("X-Forwarded-For", "8.8.8.8".parse().unwrap()); - - let attack_result = processor.process_request(&peer_addr, &attack_headers).unwrap(); - // 8.8.8.8 should be correctly identified as the client IP (because the proxy is trusted) - assert_eq!(attack_result.trusted_client_ip.to_string(), "8.8.8.8"); - - // Scenario 3: Untrustworthy agent attempts to spoof - let untrusted_peer = SocketAddr::from(([8, 8, 8, 8], 80)); - let mut fake_headers = HeaderMap::new(); - fake_headers.insert("X-Forwarded-For", "10.0.0.100".parse().unwrap()); - - let fake_result = processor.process_request(&untrusted_peer, &fake_headers).unwrap(); - // X-Forwarded-For should be ignored and 8.8.8.8 is used as the client IP - assert_eq!(fake_result.trusted_client_ip.to_string(), "8.8.8.8"); - } -} diff --git a/crates/trusted-proxies/src/proxy.rs b/crates/trusted-proxies/src/proxy.rs deleted file mode 100644 index ab2af98f..00000000 --- a/crates/trusted-proxies/src/proxy.rs +++ /dev/null @@ -1,258 +0,0 @@ -// 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 crate::{ - CloudProviderIps, ConfigError, DEFAULT_ADDITIONAL_TRUSTED_PROXIES, DEFAULT_CLOUDFLARE_IPS_ENABLED, DEFAULT_TRUSTED_PROXIES, - ENV_ADDITIONAL_TRUSTED_PROXIES, ENV_CLOUDFLARE_IPS_ENABLED, ENV_TRUSTED_PROXIES, EnvConfigLoader, ValidationMode, - parse_proxy_list, -}; -use ipnetwork::IpNetwork; -use serde::{Deserialize, Serialize}; -use std::net::{IpAddr, SocketAddr}; -use std::str::FromStr; -use tracing::debug; - -/// Trusted Proxy Configuration: Supports a single IP or CIDR block -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum TrustedProxy { - /// A single IP address - Single(IpAddr), - /// IP address segment (CIDR notation) - Cidr(IpNetwork), -} - -impl TrustedProxy { - /// Parsing trusted agent configurations from strings - /// Support: "127.0.0.1", "10.0.0.0/8", "::1" - pub fn from_str(s: &str) -> anyhow::Result { - if s.contains('/') { - // CIDR notation - let network = IpNetwork::from_str(s).map_err(|e| anyhow::anyhow!("Invalid CIDR format '{}': {}", s, e))?; - Ok(TrustedProxy::Cidr(network)) - } else { - // Single IP - let ip = IpAddr::from_str(s).map_err(|e| anyhow::anyhow!("Invalid IP address '{}': {}", s, e))?; - Ok(TrustedProxy::Single(ip)) - } - } - - /// Check if the IP matches this configuration - pub fn contains(&self, ip: &IpAddr) -> bool { - match self { - TrustedProxy::Single(proxy_ip) => ip == proxy_ip, - TrustedProxy::Cidr(network) => network.contains(*ip), - } - } -} - -/// Trusted Agent Configuration Collection -/// -/// Supports multiple trusted agents (single IP or CIDR blocks) -/// Used to verify if a request comes from a trusted agent based on its SocketAddr -/// Refer to `TrustedProxy` for individual configurations -/// -#[derive(Debug, Clone)] -pub struct TrustedProxiesConfig { - pub proxies: Vec, -} - -impl Default for TrustedProxiesConfig { - fn default() -> Self { - // Default trusted agents: Local loopback and common private networks - let default_proxies = parse_proxy_list(DEFAULT_TRUSTED_PROXIES); - Self::from_strs(&default_proxies).unwrap_or_else(|_| Self { proxies: vec![] }) - } -} - -impl TrustedProxiesConfig { - /// Create a new configuration - /// - /// # Arguments - /// * `proxies` - A vector of TrustedProxy configurations - /// - /// # Returns - /// A new TrustedProxiesConfig instance - pub fn new(proxies: Vec) -> Self { - Self { proxies } - } - - /// Create a configuration from a string slice - /// - /// # Arguments - /// * `proxy_strs` - A slice of string slices representing trusted agents - /// - /// # Returns - /// A Result containing the TrustedProxiesConfig or an error if parsing fails - pub fn from_strs(proxy_strs: &[&str]) -> anyhow::Result { - let mut proxies = Vec::new(); - - for s in proxy_strs { - proxies.push(TrustedProxy::from_str(s)?); - } - - Ok(Self::new(proxies)) - } - - /// Check if the SocketAddr is coming from a trusted agent - /// - /// # Arguments - /// * `addr` - The SocketAddr to check - /// - /// # Returns - /// true if the address is from a trusted agent, false otherwise - pub fn is_trusted(&self, addr: &SocketAddr) -> bool { - let ip = addr.ip(); - self.proxies.iter().any(|proxy| proxy.contains(&ip)) - } - - /// Get a list of trusted agents (for debugging/logging) - /// - /// # Returns - /// A vector of strings representing the trusted agents - pub fn get_trusted_ranges(&self) -> Vec { - self.proxies - .iter() - .map(|p| match p { - TrustedProxy::Single(ip) => ip.to_string(), - TrustedProxy::Cidr(network) => network.to_string(), - }) - .collect() - } - - /// Create a configuration from an environment variable - pub fn from_env() -> Result { - // Load the underlying trusted agent - let mut proxies = EnvConfigLoader::parse_ip_list(ENV_TRUSTED_PROXIES, DEFAULT_TRUSTED_PROXIES)?; - - // Load additional trusted agents - let additional_proxies = - EnvConfigLoader::parse_ip_list(ENV_ADDITIONAL_TRUSTED_PROXIES, DEFAULT_ADDITIONAL_TRUSTED_PROXIES)?; - proxies.extend(additional_proxies); - - // Get IP ranges from cloud metadata - let cloud_ips = EnvConfigLoader::fetch_cloud_metadata_ips()?; - for ip_range in cloud_ips { - if let Ok(network) = ip_range.parse::() { - proxies.push(TrustedProxy::Cidr(network)); - debug!("Add cloud metadata IP range: {}", network); - } - } - - // If Cloudflare IP is enabled, add - if EnvConfigLoader::get_bool(ENV_CLOUDFLARE_IPS_ENABLED, DEFAULT_CLOUDFLARE_IPS_ENABLED)? { - for network in CloudProviderIps::cloudflare_ranges() { - proxies.push(TrustedProxy::Cidr(network)); - } - } - - Ok(Self { proxies }) - } - - /// Get the trusted agent scope configured in the environment variable (for debugging/logging) - pub fn get_trusted_ranges_from_env() -> Result, ConfigError> { - let config = Self::from_env()?; - Ok(config.get_trusted_ranges()) - } -} - -/// Client real information stored in the request extension -#[derive(Debug, Clone)] -pub struct ClientInfo { - /// Real client IP address (verified) - pub real_ip: IpAddr, - /// Original request hostname (if from a trusted agent) - pub forwarded_host: Option, - /// Original request agreement (if from a trusted agent) - pub forwarded_proto: Option, - /// Whether the request comes from a trusted agent - pub is_from_trusted_proxy: bool, - /// Direct Connected Proxy IP (if Proxyed) - pub proxy_ip: Option, -} - -impl ClientInfo { - /// Create client information for direct connections (no agents) - /// - /// # Arguments - /// * `addr` - The SocketAddr of the direct client connection - /// - /// # Returns - /// A new ClientInfo instance representing a direct connection - pub fn direct(addr: SocketAddr) -> Self { - Self { - real_ip: addr.ip(), - forwarded_host: None, - forwarded_proto: None, - is_from_trusted_proxy: false, - proxy_ip: None, - } - } - - /// Create client information from trusted agents - /// - /// # Arguments - /// * `real_ip` - The real client IP address - /// * `forwarded_host` - The original request hostname - /// * `forwarded_proto` - The original request agreement - /// * `proxy_ip` - The direct connected proxy IP - /// - /// # Returns - /// A new ClientInfo instance representing a proxied connection - pub fn from_trusted_proxy( - real_ip: IpAddr, - forwarded_host: Option, - forwarded_proto: Option, - proxy_ip: IpAddr, - ) -> Self { - Self { - real_ip, - forwarded_host, - forwarded_proto, - is_from_trusted_proxy: true, - proxy_ip: Some(proxy_ip), - } - } -} - -/// Multi-tier proxy processor configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MultiProxyConfig { - /// Proxy chain validation mode - pub validation_mode: ValidationMode, - /// Is RFC 7239 Forwarded header support enabled? - pub enable_rfc7239: bool, - /// Maximum proxy hop limit - pub max_proxy_hops: usize, - /// Allowed private network CIDR (for internal proxy authentication) - pub allowed_private_nets: Vec, - /// Whether to enable proxy chain continuity checking - pub enable_chain_continuity_check: bool, -} - -impl Default for MultiProxyConfig { - fn default() -> Self { - Self { - validation_mode: ValidationMode::HopByHop, - enable_rfc7239: true, - max_proxy_hops: 10, - allowed_private_nets: vec![ - IpNetwork::from_str("10.0.0.0/8").unwrap(), - IpNetwork::from_str("172.16.0.0/12").unwrap(), - IpNetwork::from_str("192.168.0.0/16").unwrap(), - IpNetwork::from_str("fd00::/8").unwrap(), - ], - enable_chain_continuity_check: true, - } - } -} diff --git a/crates/trusted-proxies/src/proxy/cache.rs b/crates/trusted-proxies/src/proxy/cache.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/proxy/cache.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/proxy/chain.rs b/crates/trusted-proxies/src/proxy/chain.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/proxy/chain.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/proxy/metrics.rs b/crates/trusted-proxies/src/proxy/metrics.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/proxy/metrics.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/proxy/mod.rs b/crates/trusted-proxies/src/proxy/mod.rs new file mode 100644 index 00000000..51e96a20 --- /dev/null +++ b/crates/trusted-proxies/src/proxy/mod.rs @@ -0,0 +1,17 @@ +// 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. + +mod cache; +mod chain; +mod metrics; diff --git a/crates/trusted-proxies/src/proxy/validator.rs b/crates/trusted-proxies/src/proxy/validator.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/proxy/validator.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/utils/ip.rs b/crates/trusted-proxies/src/utils/ip.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/utils/ip.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/src/utils/mod.rs b/crates/trusted-proxies/src/utils/mod.rs new file mode 100644 index 00000000..f087e3e2 --- /dev/null +++ b/crates/trusted-proxies/src/utils/mod.rs @@ -0,0 +1,16 @@ +// 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. + +mod ip; +mod validation; diff --git a/crates/trusted-proxies/src/utils/validation.rs b/crates/trusted-proxies/src/utils/validation.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/src/utils/validation.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/test/integration/cloud_tests.rs b/crates/trusted-proxies/test/integration/cloud_tests.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/test/integration/cloud_tests.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/test/integration/mod.rs b/crates/trusted-proxies/test/integration/mod.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/test/integration/mod.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/test/integration/proxy_tests.rs b/crates/trusted-proxies/test/integration/proxy_tests.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/test/integration/proxy_tests.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/test/unit/config_tests.rs b/crates/trusted-proxies/test/unit/config_tests.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/test/unit/config_tests.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/test/unit/mod.rs b/crates/trusted-proxies/test/unit/mod.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/test/unit/mod.rs @@ -0,0 +1,13 @@ +// 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. diff --git a/crates/trusted-proxies/test/unit/validator_tests.rs b/crates/trusted-proxies/test/unit/validator_tests.rs new file mode 100644 index 00000000..6238cfff --- /dev/null +++ b/crates/trusted-proxies/test/unit/validator_tests.rs @@ -0,0 +1,13 @@ +// 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.