Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions gateway/gateway.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ app_address_ns_compat = true
workers = 32
external_port = 443

# PROXY Protocol configuration
[core.proxy.proxy_protocol]
enabled = true

[core.proxy.proxy_protocol.trust_strategy]
type = "all" # Trust all sources by default (can be restricted to "ips", "cidrs", or "none")
# addresses = ["10.0.0.1", "10.0.0.2"] # Optional: specific IPs when type = "ips"
# ranges = ["10.0.0.0/8", "172.16.0.0/12"] # Optional: CIDR ranges when type = "cidrs"

[core.proxy.proxy_protocol.backend_default]
mode = "passthrough" # Default: forward if received, don't add if not received
# version = "v2" # Optional: specify v1 or v2, defaults to v2 when mode = "always"

# Optional: Override settings for specific backends
# [core.proxy.proxy_protocol.backend_overrides]
# "app1" = { mode = "never" } # Never send PROXY Protocol to this app
# "app2" = { mode = "always", version = "v1" } # Always send v1 PROXY Protocol

[core.proxy.timeouts]
# Timeout for establishing a connection to the target app.
connect = "5s"
Expand Down
143 changes: 143 additions & 0 deletions gateway/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,103 @@ pub enum TlsVersion {
Tls13,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ProxyProtocolConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default)]
pub trust_strategy: ProxyProtocolTrustStrategy,
#[serde(default)]
pub backend_default: BackendProxyMode,
pub backend_overrides: Option<std::collections::HashMap<String, BackendProxyConfig>>,
}

fn default_enabled() -> bool {
true
}

#[derive(Debug, Clone, Deserialize)]
pub struct ProxyProtocolTrustStrategy {
#[serde(rename = "type", default = "default_trust_all")]
pub strategy_type: TrustStrategyType,
pub addresses: Option<Vec<String>>,
pub ranges: Option<Vec<String>>,
}

fn default_trust_all() -> TrustStrategyType {
TrustStrategyType::All
}

impl Default for ProxyProtocolTrustStrategy {
fn default() -> Self {
Self {
strategy_type: TrustStrategyType::All,
addresses: None,
ranges: None,
}
}
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TrustStrategyType {
None,
All,
Ips,
Cidrs,
}

#[derive(Debug, Clone, Deserialize)]
pub struct BackendProxyMode {
#[serde(default = "default_passthrough_mode")]
pub mode: ForwardingMode,
pub version: Option<ProxyProtocolVersionConfig>,
}

fn default_passthrough_mode() -> ForwardingMode {
ForwardingMode::Passthrough
}

impl Default for BackendProxyMode {
fn default() -> Self {
Self {
mode: ForwardingMode::Passthrough,
version: None,
}
}
}

fn default_proxy_protocol() -> Option<ProxyProtocolConfig> {
Some(ProxyProtocolConfig {
enabled: true,
trust_strategy: ProxyProtocolTrustStrategy::default(),
backend_default: BackendProxyMode::default(),
backend_overrides: None,
})
}

#[derive(Debug, Clone, Deserialize)]
pub struct BackendProxyConfig {
pub mode: ForwardingMode,
pub version: Option<ProxyProtocolVersionConfig>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ForwardingMode {
Never,
Passthrough,
Always,
Conditional,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProxyProtocolVersionConfig {
V1,
V2,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ProxyConfig {
pub cert_chain: String,
Expand All @@ -85,6 +182,8 @@ pub struct ProxyConfig {
pub workers: usize,
pub app_address_ns_prefix: String,
pub app_address_ns_compat: bool,
#[serde(default = "default_proxy_protocol")]
pub proxy_protocol: Option<ProxyProtocolConfig>,
}

#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -271,6 +370,50 @@ mod tests {
use super::*;
use std::str::FromStr;

#[test]
fn test_proxy_protocol_defaults() {
// Test that default values work correctly
let default_config = default_proxy_protocol().unwrap();
assert!(default_config.enabled);
assert!(matches!(
default_config.trust_strategy.strategy_type,
TrustStrategyType::All
));
assert!(matches!(
default_config.backend_default.mode,
ForwardingMode::Passthrough
));
assert!(default_config.backend_overrides.is_none());
}

#[test]
fn test_proxy_protocol_struct_defaults() {
// Test that Default trait implementations work correctly
let trust_strategy = ProxyProtocolTrustStrategy::default();
assert!(matches!(
trust_strategy.strategy_type,
TrustStrategyType::All
));
assert!(trust_strategy.addresses.is_none());
assert!(trust_strategy.ranges.is_none());

let backend_mode = BackendProxyMode::default();
assert!(matches!(backend_mode.mode, ForwardingMode::Passthrough));
assert!(backend_mode.version.is_none());

// Test that default_proxy_protocol creates the expected config
let default_config = default_proxy_protocol().unwrap();
assert!(default_config.enabled);
assert!(matches!(
default_config.trust_strategy.strategy_type,
TrustStrategyType::All
));
assert!(matches!(
default_config.backend_default.mode,
ForwardingMode::Passthrough
));
}

#[test]
fn test_validate() {
// Valid configuration
Expand Down
20 changes: 20 additions & 0 deletions gateway/src/main_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ impl ProxyInner {
self.state.lock().expect("Failed to lock AppState")
}

pub fn reload_certificates(&self) -> Result<()> {
info!("Reloading TLS certificates");
// Replace the acceptor with the new one
if let Ok(mut acceptor) = self.acceptor.write() {
*acceptor = create_acceptor(&self.config.proxy, false)?;
info!("TLS certificates successfully reloaded");
} else {
bail!("Failed to acquire write lock for TLS acceptor");
}

if let Ok(mut acceptor) = self.h2_acceptor.write() {
*acceptor = create_acceptor(&self.config.proxy, true)?;
info!("TLS certificates successfully reloaded");
} else {
bail!("Failed to acquire write lock for TLS acceptor");
}

Ok(())
}

pub async fn new(config: Config, my_app_id: Option<Vec<u8>>) -> Result<Self> {
let config = Arc::new(config);
let mut state = fs::metadata(&config.state_path)
Expand Down
Loading
Loading