mirror of
https://github.com/rustfs/rustfs.git
synced 2026-01-17 09:40:32 +00:00
init
This commit is contained in:
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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<IpAddr>,
|
||||
/// Proxy identification
|
||||
pub by_proxy: Option<IpAddr>,
|
||||
/// protocol
|
||||
pub proto: Option<String>,
|
||||
/// Host
|
||||
pub host: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<IpAddr>,
|
||||
/// Trusted Proxy Chain section
|
||||
pub trusted_proxy_chain: Vec<IpAddr>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// Optimized CIDR matcher
|
||||
pub struct CidrMatcher {
|
||||
ipv4_networks: Vec<IpNetwork>,
|
||||
ipv6_networks: Vec<IpNetwork>,
|
||||
single_ips: HashSet<IpAddr>,
|
||||
}
|
||||
|
||||
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::<IpNetwork>() {
|
||||
config.allowed_private_nets.push(network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
/// Dynamic configuration updates
|
||||
pub struct DynamicConfigManager {
|
||||
current_config: Arc<std::sync::RwLock<MultiProxyConfig>>,
|
||||
config_watcher: tokio::sync::watch::Sender<MultiProxyConfig>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
13
crates/trusted-proxies/src/api/handlers.rs
Normal file
13
crates/trusted-proxies/src/api/handlers.rs
Normal file
@@ -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.
|
||||
15
crates/trusted-proxies/src/api/mod.rs
Normal file
15
crates/trusted-proxies/src/api/mod.rs
Normal file
@@ -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;
|
||||
@@ -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<IpAddr, bool>,
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
13
crates/trusted-proxies/src/cloud/detector.rs
Normal file
13
crates/trusted-proxies/src/cloud/detector.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/cloud/metadata/aws.rs
Normal file
13
crates/trusted-proxies/src/cloud/metadata/aws.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/cloud/metadata/azure.rs
Normal file
13
crates/trusted-proxies/src/cloud/metadata/azure.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/cloud/metadata/gcp.rs
Normal file
13
crates/trusted-proxies/src/cloud/metadata/gcp.rs
Normal file
@@ -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.
|
||||
17
crates/trusted-proxies/src/cloud/metadata/mod.rs
Normal file
17
crates/trusted-proxies/src/cloud/metadata/mod.rs
Normal file
@@ -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;
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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();
|
||||
13
crates/trusted-proxies/src/config/loader.rs
Normal file
13
crates/trusted-proxies/src/config/loader.rs
Normal file
@@ -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.
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/trusted-proxies/src/config/types.rs
Normal file
13
crates/trusted-proxies/src/config/types.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/error/config.rs
Normal file
13
crates/trusted-proxies/src/error/config.rs
Normal file
@@ -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.
|
||||
16
crates/trusted-proxies/src/error/mod.rs
Normal file
16
crates/trusted-proxies/src/error/mod.rs
Normal file
@@ -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;
|
||||
13
crates/trusted-proxies/src/error/proxy.rs
Normal file
13
crates/trusted-proxies/src/error/proxy.rs
Normal file
@@ -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.
|
||||
@@ -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<RwLock<TrustedProxiesConfig>> =
|
||||
LazyLock::new(|| RwLock::new(TrustedProxiesConfig::default()));
|
||||
|
||||
static TRUSTED_PROXIES_CONFIG_ENABLE: LazyLock<RwLock<bool>> = 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<TrustedProxiesConfig> {
|
||||
// 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<TrustedProxiesConfig> {
|
||||
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::<Vec<&str>>();
|
||||
|
||||
TrustedProxiesConfig::from_strs(&proxies)
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
13
crates/trusted-proxies/src/logging/middleware.rs
Normal file
13
crates/trusted-proxies/src/logging/middleware.rs
Normal file
@@ -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.
|
||||
15
crates/trusted-proxies/src/logging/mod.rs
Normal file
15
crates/trusted-proxies/src/logging/mod.rs
Normal file
@@ -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;
|
||||
15
crates/trusted-proxies/src/main.rs
Normal file
15
crates/trusted-proxies/src/main.rs
Normal file
@@ -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() {}
|
||||
@@ -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<ProxyChainAnalysis, ProxyError> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<String>,
|
||||
pub forwarded_proto: Option<String>,
|
||||
pub is_from_trusted_proxy: bool,
|
||||
pub proxy_ip: Option<IpAddr>,
|
||||
pub proxy_chain_analysis: Option<crate::ProxyChainAnalysis>,
|
||||
}
|
||||
|
||||
/// Trusted agent middleware layer
|
||||
#[derive(Clone)]
|
||||
pub struct TrustedProxiesLayer {
|
||||
config: TrustedProxiesConfig,
|
||||
}
|
||||
|
||||
impl TrustedProxiesLayer {
|
||||
pub fn new(config: TrustedProxiesConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for TrustedProxiesLayer {
|
||||
type Service = TrustedProxiesMiddleware<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
TrustedProxiesMiddleware {
|
||||
inner,
|
||||
config: self.config.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trusted agent middleware service
|
||||
#[derive(Clone)]
|
||||
pub struct TrustedProxiesMiddleware<S> {
|
||||
inner: S,
|
||||
config: TrustedProxiesConfig,
|
||||
}
|
||||
|
||||
impl<S> Service<Request> for TrustedProxiesMiddleware<S>
|
||||
where
|
||||
S: Service<Request, Response = Response> + 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<Result<(), Self::Error>> {
|
||||
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::<SocketAddr>().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::<SocketAddr>().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<SocketAddr>, 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<IpAddr> {
|
||||
// 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())
|
||||
}
|
||||
13
crates/trusted-proxies/src/middleware/layer.rs
Normal file
13
crates/trusted-proxies/src/middleware/layer.rs
Normal file
@@ -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.
|
||||
16
crates/trusted-proxies/src/middleware/mod.rs
Normal file
16
crates/trusted-proxies/src/middleware/mod.rs
Normal file
@@ -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;
|
||||
13
crates/trusted-proxies/src/middleware/service.rs
Normal file
13
crates/trusted-proxies/src/middleware/service.rs
Normal file
@@ -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.
|
||||
@@ -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<IpAddr> {
|
||||
// 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<IpAddr> = 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<IpAddr>,
|
||||
/// A collection of known private networks
|
||||
private_nets_cache: Vec<IpNetwork>,
|
||||
}
|
||||
|
||||
impl MultiProxyProcessor {
|
||||
/// Create a new processor
|
||||
pub fn new(base_config: TrustedProxiesConfig, advanced_config: Option<MultiProxyConfig>) -> 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<ProxyChainAnalysis, ProxyError> {
|
||||
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<ProxyChainAnalysis, ProxyError> {
|
||||
// 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<IpAddr>, 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<IpAddr>, 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<IpAddr>, 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<ProxyChainAnalysis, ProxyError> {
|
||||
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<ForwardedHeader> {
|
||||
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<Vec<IpAddr>> {
|
||||
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<Vec<IpAddr>> {
|
||||
let xff_value = headers.get("x-forwarded-for")?.to_str().ok()?;
|
||||
|
||||
let ips: Vec<IpAddr> = 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<IpAddr> {
|
||||
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<TrustedProxiesConfig>,
|
||||
advanced_config: MultiProxyConfig,
|
||||
ip_cache: std::sync::Mutex<IpValidationCache>,
|
||||
cidr_matcher: CidrMatcher,
|
||||
}
|
||||
|
||||
impl OptimizedMultiProxyProcessor {
|
||||
pub fn new(base_config: TrustedProxiesConfig, advanced_config: Option<MultiProxyConfig>) -> 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::<Vec<_>>().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");
|
||||
}
|
||||
}
|
||||
@@ -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<Self> {
|
||||
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<TrustedProxy>,
|
||||
}
|
||||
|
||||
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<TrustedProxy>) -> 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<Self> {
|
||||
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<String> {
|
||||
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<Self, ConfigError> {
|
||||
// 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::<IpNetwork>() {
|
||||
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<Vec<String>, 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<String>,
|
||||
/// Original request agreement (if from a trusted agent)
|
||||
pub forwarded_proto: Option<String>,
|
||||
/// Whether the request comes from a trusted agent
|
||||
pub is_from_trusted_proxy: bool,
|
||||
/// Direct Connected Proxy IP (if Proxyed)
|
||||
pub proxy_ip: Option<IpAddr>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
forwarded_proto: Option<String>,
|
||||
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<IpNetwork>,
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
13
crates/trusted-proxies/src/proxy/cache.rs
Normal file
13
crates/trusted-proxies/src/proxy/cache.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/proxy/chain.rs
Normal file
13
crates/trusted-proxies/src/proxy/chain.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/proxy/metrics.rs
Normal file
13
crates/trusted-proxies/src/proxy/metrics.rs
Normal file
@@ -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.
|
||||
17
crates/trusted-proxies/src/proxy/mod.rs
Normal file
17
crates/trusted-proxies/src/proxy/mod.rs
Normal file
@@ -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;
|
||||
13
crates/trusted-proxies/src/proxy/validator.rs
Normal file
13
crates/trusted-proxies/src/proxy/validator.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/src/utils/ip.rs
Normal file
13
crates/trusted-proxies/src/utils/ip.rs
Normal file
@@ -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.
|
||||
16
crates/trusted-proxies/src/utils/mod.rs
Normal file
16
crates/trusted-proxies/src/utils/mod.rs
Normal file
@@ -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;
|
||||
13
crates/trusted-proxies/src/utils/validation.rs
Normal file
13
crates/trusted-proxies/src/utils/validation.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/test/integration/cloud_tests.rs
Normal file
13
crates/trusted-proxies/test/integration/cloud_tests.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/test/integration/mod.rs
Normal file
13
crates/trusted-proxies/test/integration/mod.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/test/integration/proxy_tests.rs
Normal file
13
crates/trusted-proxies/test/integration/proxy_tests.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/test/unit/config_tests.rs
Normal file
13
crates/trusted-proxies/test/unit/config_tests.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/test/unit/mod.rs
Normal file
13
crates/trusted-proxies/test/unit/mod.rs
Normal file
@@ -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.
|
||||
13
crates/trusted-proxies/test/unit/validator_tests.rs
Normal file
13
crates/trusted-proxies/test/unit/validator_tests.rs
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user