diff --git a/src/api/mod.rs b/src/api/mod.rs index 5f4861e..850fb0e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -224,13 +224,11 @@ async fn handle( "Source IP is not allowed", ), )), - ApiGrayAction::Ok200 => Ok( - Response::builder() - .status(StatusCode::OK) - .header("content-type", "text/html; charset=utf-8") - .body(Full::new(Bytes::new())) - .unwrap(), - ), + ApiGrayAction::Ok200 => Ok(Response::builder() + .status(StatusCode::OK) + .header("content-type", "text/html; charset=utf-8") + .body(Full::new(Bytes::new())) + .unwrap()), ApiGrayAction::Drop => Err(IoError::new( ErrorKind::ConnectionAborted, "api request dropped by gray_action=drop", @@ -259,11 +257,16 @@ async fn handle( let method = req.method().clone(); let path = req.uri().path().to_string(); + let normalized_path = if path.len() > 1 { + path.trim_end_matches('/') + } else { + path.as_str() + }; let query = req.uri().query().map(str::to_string); let body_limit = api_cfg.request_body_limit_bytes; let result: Result>, ApiFailure> = async { - match (method.as_str(), path.as_str()) { + match (method.as_str(), normalized_path) { ("GET", "/v1/health") => { let revision = current_revision(&shared.config_path).await?; let data = HealthData { @@ -446,7 +449,7 @@ async fn handle( Ok(success_response(status, data, revision)) } _ => { - if let Some(user) = path.strip_prefix("/v1/users/") + if let Some(user) = normalized_path.strip_prefix("/v1/users/") && !user.is_empty() && !user.contains('/') { @@ -615,6 +618,12 @@ async fn handle( ), )); } + debug!( + method = method.as_str(), + path = %path, + normalized_path = %normalized_path, + "API route not found" + ); Ok(error_response( request_id, ApiFailure::new(StatusCode::NOT_FOUND, "not_found", "Route not found"), diff --git a/src/config/load.rs b/src/config/load.rs index abe4072..e5c8202 100644 --- a/src/config/load.rs +++ b/src/config/load.rs @@ -384,9 +384,7 @@ impl ProxyConfig { // Backward compatibility: legacy top-level beobachten* keys. // Prefer `[general].*` when both are present. let mut legacy_beobachten_applied = false; - if !beobachten_is_explicit - && let Some(value) = legacy_top_level_beobachten.as_ref() - { + if !beobachten_is_explicit && let Some(value) = legacy_top_level_beobachten.as_ref() { let parsed = value.as_bool().ok_or_else(|| { ProxyError::Config("beobachten (top-level) must be a boolean".to_string()) })?; @@ -433,9 +431,7 @@ impl ProxyConfig { legacy_beobachten_applied = true; } if legacy_beobachten_applied { - warn!( - "top-level beobachten* keys are deprecated; use general.beobachten* instead" - ); + warn!("top-level beobachten* keys are deprecated; use general.beobachten* instead"); } let legacy_nat_stun = config.general.middle_proxy_nat_stun.take(); diff --git a/src/maestro/listeners.rs b/src/maestro/listeners.rs index 034fbef..796eb9e 100644 --- a/src/maestro/listeners.rs +++ b/src/maestro/listeners.rs @@ -120,11 +120,7 @@ pub(crate) async fn bind_listeners( if config.general.links.public_host.is_none() && !config.general.links.show.is_empty() { - let link_port = config - .general - .links - .public_port - .unwrap_or(listener_port); + let link_port = config.general.links.public_port.unwrap_or(listener_port); print_proxy_links(&public_host, link_port, config); } diff --git a/src/maestro/mod.rs b/src/maestro/mod.rs index 5f3cb69..f141331 100644 --- a/src/maestro/mod.rs +++ b/src/maestro/mod.rs @@ -83,7 +83,9 @@ pub async fn run() -> std::result::Result<(), Box> { // Shared maestro startup and main loop. `drop_after_bind` runs on Unix after listeners are bound // (for privilege drop); it is a no-op on other platforms. -async fn run_telemt_core(drop_after_bind: impl FnOnce()) -> std::result::Result<(), Box> { +async fn run_telemt_core( + drop_after_bind: impl FnOnce(), +) -> std::result::Result<(), Box> { let process_started_at = Instant::now(); let process_started_at_epoch_secs = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -819,11 +821,7 @@ async fn run_inner( run_telemt_core(|| { if user.is_some() || group.is_some() { - if let Err(e) = drop_privileges( - user.as_deref(), - group.as_deref(), - _pid_file.as_ref(), - ) { + if let Err(e) = drop_privileges(user.as_deref(), group.as_deref(), _pid_file.as_ref()) { error!(error = %e, "Failed to drop privileges"); std::process::exit(1); } diff --git a/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs b/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs index 2781102..09ec626 100644 --- a/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs +++ b/src/proxy/tests/client_masking_redteam_expected_fail_tests.rs @@ -238,8 +238,8 @@ async fn redteam_03_masking_duration_must_be_less_than_1ms_when_backend_down() { enabled: true, scopes: String::new(), selected_scope: String::new(), - ipv4: None, - ipv6: None, + ipv4: None, + ipv6: None, }], 1, 1, @@ -482,8 +482,8 @@ async fn measure_invalid_probe_duration_ms(delay_ms: u64, tls_len: u16, body_sen enabled: true, scopes: String::new(), selected_scope: String::new(), - ipv4: None, - ipv6: None, + ipv4: None, + ipv6: None, }], 1, 1, @@ -559,8 +559,8 @@ async fn capture_forwarded_probe_len(tls_len: u16, body_sent: usize) -> usize { enabled: true, scopes: String::new(), selected_scope: String::new(), - ipv4: None, - ipv6: None, + ipv4: None, + ipv6: None, }], 1, 1, diff --git a/src/proxy/tests/client_security_tests.rs b/src/proxy/tests/client_security_tests.rs index c5b971b..85af766 100644 --- a/src/proxy/tests/client_security_tests.rs +++ b/src/proxy/tests/client_security_tests.rs @@ -835,8 +835,8 @@ async fn proxy_protocol_header_from_untrusted_peer_range_is_rejected_under_load( enabled: true, scopes: String::new(), selected_scope: String::new(), - ipv4: None, - ipv6: None, + ipv4: None, + ipv6: None, }], 1, 1, @@ -2403,8 +2403,8 @@ async fn burst_invalid_tls_probes_are_masked_verbatim() { enabled: true, scopes: String::new(), selected_scope: String::new(), - ipv4: None, - ipv6: None, + ipv4: None, + ipv6: None, }], 1, 1, diff --git a/src/transport/upstream.rs b/src/transport/upstream.rs index 9aaca76..6e4f196 100644 --- a/src/transport/upstream.rs +++ b/src/transport/upstream.rs @@ -816,7 +816,11 @@ impl UpstreamManager { .and_then(UpstreamState::dc_array_idx) .map(|dc_array_idx| state.dc_ip_pref[dc_array_idx]) .unwrap_or(IpPreference::Unknown); - (state.config.clone(), Some(state.bind_rr.clone()), dc_preference) + ( + state.config.clone(), + Some(state.bind_rr.clone()), + dc_preference, + ) }; if let Some(s) = scope { @@ -854,7 +858,11 @@ impl UpstreamManager { .and_then(UpstreamState::dc_array_idx) .map(|dc_array_idx| state.dc_ip_pref[dc_array_idx]) .unwrap_or(IpPreference::Unknown); - (state.config.clone(), Some(state.bind_rr.clone()), dc_preference) + ( + state.config.clone(), + Some(state.bind_rr.clone()), + dc_preference, + ) }; // Set scope for configuration copy @@ -1753,9 +1761,8 @@ impl UpstreamManager { } let rotation_key = (i, group.dc_idx, is_primary); - let start_idx = - *endpoint_rotation.entry(rotation_key).or_insert(0) - % filtered_endpoints.len(); + let start_idx = *endpoint_rotation.entry(rotation_key).or_insert(0) + % filtered_endpoints.len(); let mut next_idx = (start_idx + 1) % filtered_endpoints.len(); for step in 0..filtered_endpoints.len() { @@ -2034,8 +2041,8 @@ mod tests { enabled: true, scopes: String::new(), selected_scope: String::new(), - ipv4: None, - ipv6: None, + ipv4: None, + ipv6: None, }], 1, 100,