This commit is contained in:
houseme
2026-01-05 20:34:30 +08:00
parent b78f0d0bf1
commit d72fef5ce6
45 changed files with 474 additions and 1781 deletions

29
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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()
}
}

View 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.

View 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;

View File

@@ -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
}
}

View 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.

View 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.

View 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.

View 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.

View 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;

View File

@@ -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::*;

View File

@@ -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();

View 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.

View File

@@ -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::*;

View 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.

View 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.

View 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;

View 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.

View File

@@ -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)
}

View File

@@ -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::*;

View 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.

View 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;

View 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() {}

View File

@@ -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
}
}

View File

@@ -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())
}

View 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.

View 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;

View 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.

View File

@@ -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");
}
}

View File

@@ -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,
}
}
}

View 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.

View 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.

View 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.

View 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;

View 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.

View 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.

View 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;

View 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.

View 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.

View 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.

View 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.

View 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.

View 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.

View 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.