mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-05 21:07:43 +00:00
309 lines
14 KiB
Rust
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"
|
|
);
|
|
}
|
|
}
|
|
}
|