Files
rustfs/crates/trusted-proxies/src/config/loader.rs

309 lines
14 KiB
Rust

// 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.
//! Configuration loader for environment variables and files.
use crate::{
AppConfig, CacheConfig, CloudConfig, ConfigError, MonitoringConfig, TrustedProxy, TrustedProxyConfig, ValidationMode,
parse_ip_list_from_env, parse_string_list_from_env,
};
use ipnetwork::IpNetwork;
use rustfs_config::{
DEFAULT_TRUSTED_PROXIES_LOG_LEVEL, DEFAULT_TRUSTED_PROXY_CACHE_CAPACITY, DEFAULT_TRUSTED_PROXY_CACHE_CLEANUP_INTERVAL,
DEFAULT_TRUSTED_PROXY_CACHE_TTL_SECONDS, DEFAULT_TRUSTED_PROXY_CHAIN_CONTINUITY_CHECK,
DEFAULT_TRUSTED_PROXY_CLOUD_METADATA_ENABLED, DEFAULT_TRUSTED_PROXY_CLOUD_METADATA_TIMEOUT,
DEFAULT_TRUSTED_PROXY_CLOUD_PROVIDER_FORCE, DEFAULT_TRUSTED_PROXY_CLOUDFLARE_IPS_ENABLED,
DEFAULT_TRUSTED_PROXY_ENABLE_RFC7239, DEFAULT_TRUSTED_PROXY_EXTRA_PROXIES, DEFAULT_TRUSTED_PROXY_IPS,
DEFAULT_TRUSTED_PROXY_LOG_FAILED_VALIDATIONS, DEFAULT_TRUSTED_PROXY_MAX_HOPS, DEFAULT_TRUSTED_PROXY_METRICS_ENABLED,
DEFAULT_TRUSTED_PROXY_PRIVATE_NETWORKS, DEFAULT_TRUSTED_PROXY_PROXIES, DEFAULT_TRUSTED_PROXY_STRUCTURED_LOGGING,
DEFAULT_TRUSTED_PROXY_TRACING_ENABLED, DEFAULT_TRUSTED_PROXY_VALIDATION_MODE, ENV_TRUSTED_PROXIES_LOG_LEVEL,
ENV_TRUSTED_PROXY_CACHE_CAPACITY, ENV_TRUSTED_PROXY_CACHE_CLEANUP_INTERVAL, ENV_TRUSTED_PROXY_CACHE_TTL_SECONDS,
ENV_TRUSTED_PROXY_CHAIN_CONTINUITY_CHECK, ENV_TRUSTED_PROXY_CLOUD_METADATA_ENABLED, ENV_TRUSTED_PROXY_CLOUD_METADATA_TIMEOUT,
ENV_TRUSTED_PROXY_CLOUD_PROVIDER_FORCE, ENV_TRUSTED_PROXY_CLOUDFLARE_IPS_ENABLED, ENV_TRUSTED_PROXY_ENABLE_RFC7239,
ENV_TRUSTED_PROXY_EXTRA_PROXIES, ENV_TRUSTED_PROXY_IPS, ENV_TRUSTED_PROXY_LOG_FAILED_VALIDATIONS, ENV_TRUSTED_PROXY_MAX_HOPS,
ENV_TRUSTED_PROXY_METRICS_ENABLED, ENV_TRUSTED_PROXY_PRIVATE_NETWORKS, ENV_TRUSTED_PROXY_PROXIES,
ENV_TRUSTED_PROXY_STRUCTURED_LOGGING, ENV_TRUSTED_PROXY_TRACING_ENABLED, ENV_TRUSTED_PROXY_VALIDATION_MODE,
};
use rustfs_utils::{get_env_bool, get_env_str, get_env_u64, get_env_usize, parse_and_resolve_address};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::str::FromStr;
use tracing::info;
/// Loader for application configuration.
#[derive(Debug, Clone)]
pub struct ConfigLoader;
impl ConfigLoader {
/// Loads the complete application configuration from environment variables.
pub fn from_env() -> Result<AppConfig, ConfigError> {
// Load proxy-specific configuration.
let proxy_config = Self::load_proxy_config()?;
// Load cache configuration.
let cache_config = Self::load_cache_config();
// Load monitoring and observability configuration.
let monitoring_config = Self::load_monitoring_config();
// Load cloud provider integration configuration.
let cloud_config = Self::load_cloud_config();
// Load server binding address.
let server_addr = Self::load_server_addr();
Ok(AppConfig::new(proxy_config, cache_config, monitoring_config, cloud_config, server_addr))
}
/// Loads trusted proxy configuration from environment variables.
fn load_proxy_config() -> Result<TrustedProxyConfig, ConfigError> {
let mut proxies = Vec::new();
// Parse base trusted proxies from environment.
let base_networks = parse_ip_list_from_env(ENV_TRUSTED_PROXY_PROXIES, DEFAULT_TRUSTED_PROXY_PROXIES)?;
for network in base_networks {
proxies.push(TrustedProxy::Cidr(network));
}
// Parse extra trusted proxies from environment.
let extra_networks = parse_ip_list_from_env(ENV_TRUSTED_PROXY_EXTRA_PROXIES, DEFAULT_TRUSTED_PROXY_EXTRA_PROXIES)?;
for network in extra_networks {
proxies.push(TrustedProxy::Cidr(network));
}
// Parse individual trusted proxy IPs.
let ip_strings = parse_string_list_from_env(ENV_TRUSTED_PROXY_IPS, DEFAULT_TRUSTED_PROXY_IPS);
for ip_str in ip_strings {
if let Ok(ip) = ip_str.parse::<IpAddr>() {
proxies.push(TrustedProxy::Single(ip));
}
}
// Determine validation mode.
let validation_mode_str = get_env_str(ENV_TRUSTED_PROXY_VALIDATION_MODE, DEFAULT_TRUSTED_PROXY_VALIDATION_MODE);
let validation_mode = ValidationMode::from_str(&validation_mode_str)?;
// Load other proxy settings.
let enable_rfc7239 = get_env_bool(ENV_TRUSTED_PROXY_ENABLE_RFC7239, DEFAULT_TRUSTED_PROXY_ENABLE_RFC7239);
let max_hops = get_env_usize(ENV_TRUSTED_PROXY_MAX_HOPS, DEFAULT_TRUSTED_PROXY_MAX_HOPS);
let enable_chain_check =
get_env_bool(ENV_TRUSTED_PROXY_CHAIN_CONTINUITY_CHECK, DEFAULT_TRUSTED_PROXY_CHAIN_CONTINUITY_CHECK);
// Load private network ranges.
let private_networks =
parse_ip_list_from_env(ENV_TRUSTED_PROXY_PRIVATE_NETWORKS, DEFAULT_TRUSTED_PROXY_PRIVATE_NETWORKS)?;
// Warn once at startup if the effective trusted set spans private/RFC1918
// (or IPv6 unique-local) ranges. This trusts every host inside those
// ranges to set forwarding headers, which is broad; operators should
// narrow it to the specific proxy addresses in front of this server.
Self::warn_on_private_trusted_networks(&proxies);
Ok(TrustedProxyConfig::new(
proxies,
validation_mode,
enable_rfc7239,
max_hops,
enable_chain_check,
private_networks,
))
}
/// Emits a one-time startup warning when the trusted proxy set includes
/// private/RFC1918 (or IPv6 unique-local) ranges.
fn warn_on_private_trusted_networks(proxies: &[TrustedProxy]) {
let private_trusted: Vec<String> = proxies
.iter()
.filter(|proxy| Self::is_private_trusted_entry(proxy))
.map(|proxy| proxy.to_string())
.collect();
if !private_trusted.is_empty() {
tracing::warn!(
event = "trusted_proxies.config",
component = "trusted_proxies",
subsystem = "config_loader",
result = "private_trust_warning",
private_networks = ?private_trusted,
"trusted proxy set includes private ranges; narrow RUSTFS_TRUSTED_PROXY_NETWORKS to the specific proxy addresses in front of this server to reduce client IP spoofing risk"
);
}
}
/// Returns true when a trusted proxy entry falls within a private/RFC1918
/// IPv4 range or an IPv6 unique-local (fc00::/7) range. Loopback and public
/// addresses are not flagged.
fn is_private_trusted_entry(proxy: &TrustedProxy) -> bool {
let ip = match proxy {
TrustedProxy::Single(ip) => *ip,
TrustedProxy::Cidr(network) => network.network(),
};
match ip {
IpAddr::V4(v4) => v4.is_private(),
IpAddr::V6(v6) => (v6.segments()[0] & 0xfe00) == 0xfc00,
}
}
/// Loads cache configuration from environment variables.
fn load_cache_config() -> CacheConfig {
CacheConfig {
capacity: get_env_usize(ENV_TRUSTED_PROXY_CACHE_CAPACITY, DEFAULT_TRUSTED_PROXY_CACHE_CAPACITY),
ttl_seconds: get_env_u64(ENV_TRUSTED_PROXY_CACHE_TTL_SECONDS, DEFAULT_TRUSTED_PROXY_CACHE_TTL_SECONDS),
cleanup_interval_seconds: get_env_u64(
ENV_TRUSTED_PROXY_CACHE_CLEANUP_INTERVAL,
DEFAULT_TRUSTED_PROXY_CACHE_CLEANUP_INTERVAL,
),
}
}
/// Loads monitoring configuration from environment variables.
fn load_monitoring_config() -> MonitoringConfig {
MonitoringConfig {
metrics_enabled: get_env_bool(ENV_TRUSTED_PROXY_METRICS_ENABLED, DEFAULT_TRUSTED_PROXY_METRICS_ENABLED),
log_level: get_env_str(ENV_TRUSTED_PROXIES_LOG_LEVEL, DEFAULT_TRUSTED_PROXIES_LOG_LEVEL),
structured_logging: get_env_bool(ENV_TRUSTED_PROXY_STRUCTURED_LOGGING, DEFAULT_TRUSTED_PROXY_STRUCTURED_LOGGING),
tracing_enabled: get_env_bool(ENV_TRUSTED_PROXY_TRACING_ENABLED, DEFAULT_TRUSTED_PROXY_TRACING_ENABLED),
log_failed_validations: get_env_bool(
ENV_TRUSTED_PROXY_LOG_FAILED_VALIDATIONS,
DEFAULT_TRUSTED_PROXY_LOG_FAILED_VALIDATIONS,
),
}
}
/// Loads cloud configuration from environment variables.
fn load_cloud_config() -> CloudConfig {
let forced_provider_str = get_env_str(ENV_TRUSTED_PROXY_CLOUD_PROVIDER_FORCE, DEFAULT_TRUSTED_PROXY_CLOUD_PROVIDER_FORCE);
let forced_provider = if forced_provider_str.is_empty() {
None
} else {
Some(forced_provider_str)
};
CloudConfig {
metadata_enabled: get_env_bool(
ENV_TRUSTED_PROXY_CLOUD_METADATA_ENABLED,
DEFAULT_TRUSTED_PROXY_CLOUD_METADATA_ENABLED,
),
metadata_timeout_seconds: get_env_u64(
ENV_TRUSTED_PROXY_CLOUD_METADATA_TIMEOUT,
DEFAULT_TRUSTED_PROXY_CLOUD_METADATA_TIMEOUT,
),
cloudflare_ips_enabled: get_env_bool(
ENV_TRUSTED_PROXY_CLOUDFLARE_IPS_ENABLED,
DEFAULT_TRUSTED_PROXY_CLOUDFLARE_IPS_ENABLED,
),
forced_provider,
}
}
/// Loads the server binding address from environment variables.
fn load_server_addr() -> SocketAddr {
let address = get_env_str("RUSTFS_ADDRESS", rustfs_config::DEFAULT_ADDRESS);
parse_and_resolve_address(&address)
.unwrap_or_else(|_| SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), rustfs_config::DEFAULT_PORT))
}
/// Loads configuration from environment, falling back to defaults on failure.
pub fn from_env_or_default() -> AppConfig {
match Self::from_env() {
Ok(config) => {
info!(
event = "trusted_proxies.config",
component = "trusted_proxies",
subsystem = "config_loader",
result = "loaded",
source = "environment",
"trusted proxies configuration loaded"
);
config
}
Err(e) => {
tracing::warn!(
event = "trusted_proxies.config",
component = "trusted_proxies",
subsystem = "config_loader",
result = "fallback",
source = "defaults",
error = %e,
"trusted proxies configuration fell back to defaults"
);
Self::default_config()
}
}
}
/// Returns a default configuration.
pub fn default_config() -> AppConfig {
let proxy_config = TrustedProxyConfig::new(
vec![
TrustedProxy::Single(IpAddr::V4(Ipv4Addr::LOCALHOST)),
TrustedProxy::Single(IpAddr::V6(Ipv6Addr::LOCALHOST)),
],
ValidationMode::HopByHop,
true,
10,
true,
DEFAULT_TRUSTED_PROXY_PRIVATE_NETWORKS
.split(',')
.filter_map(|s| s.trim().parse::<IpNetwork>().ok())
.collect(),
);
AppConfig::new(
proxy_config,
CacheConfig::default(),
MonitoringConfig::default(),
CloudConfig::default(),
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), rustfs_config::DEFAULT_PORT),
)
}
/// Prints a summary of the configuration to the log.
pub fn print_summary(config: &AppConfig) {
info!(
event = "trusted_proxies.config",
component = "trusted_proxies",
subsystem = "config_loader",
result = "summary",
server_addr = %config.server_addr,
trusted_proxy_count = config.proxy.proxies.len(),
validation_mode = config.proxy.validation_mode.as_str(),
cache_capacity = config.cache.capacity,
cache_ttl_seconds = config.cache.ttl_seconds,
cache_cleanup_interval_seconds = config.cache.cleanup_interval_seconds,
metrics_enabled = config.monitoring.metrics_enabled,
structured_logging = config.monitoring.structured_logging,
tracing_enabled = config.monitoring.tracing_enabled,
log_failed_validations = config.monitoring.log_failed_validations,
cloud_metadata_enabled = config.cloud.metadata_enabled,
cloud_metadata_timeout_seconds = config.cloud.metadata_timeout_seconds,
cloudflare_ips_enabled = config.cloud.cloudflare_ips_enabled,
forced_provider = config.cloud.forced_provider.as_deref().unwrap_or("none"),
"trusted proxies configuration summarized"
);
if !config.proxy.proxies.is_empty() {
tracing::debug!(
event = "trusted_proxies.config",
component = "trusted_proxies",
subsystem = "config_loader",
result = "trusted_networks",
trusted_proxy_count = config.proxy.proxies.len(),
trusted_networks = ?config.proxy.get_network_strings(),
"trusted proxies networks enumerated"
);
}
}
}