From 4631ffed1a8e6c2cdce9b2194dbf83514ce7902a Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 5 Feb 2026 09:51:12 -0800 Subject: [PATCH 1/3] Add configurable TLS certificate verification and fix port handling - Add `Proxy.certificate_check` setting (defaults to true for secure production) - Add `compute_host_header()` to properly format Host header with non-standard ports - Extend `ensure_origin_backend()` with certificate_check parameter - Include cert setting in backend name to avoid reusing backends with different settings - Add comprehensive tests for port preservation in proxy signing and HTML rewriting - Update all call sites to pass certificate_check=true (secure default) This fixes an issue where backends behind reverse proxies would generate URLs without the port when the Host header didn't include it. --- crates/common/src/backend.rs | 131 ++++++++++++++---- crates/common/src/creative.rs | 42 +++++- crates/common/src/fastly_storage.rs | 2 +- .../common/src/integrations/adserver_mock.rs | 4 +- crates/common/src/integrations/aps.rs | 4 +- crates/common/src/integrations/didomi.rs | 2 +- crates/common/src/integrations/lockr.rs | 4 +- crates/common/src/integrations/permutive.rs | 12 +- crates/common/src/integrations/prebid.rs | 4 +- crates/common/src/proxy.rs | 68 ++++++++- crates/common/src/publisher.rs | 2 +- crates/common/src/settings.rs | 23 +++ 12 files changed, 254 insertions(+), 44 deletions(-) diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs index 798131f4..2dbedb02 100644 --- a/crates/common/src/backend.rs +++ b/crates/common/src/backend.rs @@ -6,19 +6,42 @@ use url::Url; use crate::error::TrustedServerError; +/// Compute the Host header value for a backend request. +/// +/// For standard ports (443 for HTTPS, 80 for HTTP), returns just the hostname. +/// For non-standard ports, returns "hostname:port" to ensure backends that +/// generate URLs based on the Host header include the port. +/// +/// This fixes the issue where backends behind reverse proxies (like Caddy) +/// would generate URLs without the port when the Host header didn't include it. +#[inline] +fn compute_host_header(scheme: &str, host: &str, port: u16) -> String { + let is_https = scheme.eq_ignore_ascii_case("https"); + let default_port = if is_https { 443 } else { 80 }; + if port != default_port { + format!("{}:{}", host, port) + } else { + host.to_string() + } +} + /// Ensure a dynamic backend exists for the given origin and return its name. /// /// The backend name is derived from the scheme and `host[:port]` to avoid collisions across /// http/https or different ports. If a backend with the derived name already exists, /// this function logs and reuses it. /// -/// # Errors +/// # Arguments /// -/// Returns an error if the host is empty or if backend creation fails (except for `NameInUse` which reuses the existing backend). +/// * `scheme` - The URL scheme ("http" or "https") +/// * `host` - The hostname +/// * `port` - Optional port number +/// * `certificate_check` - If true, enables TLS certificate verification (default for production) pub fn ensure_origin_backend( scheme: &str, host: &str, port: Option, + certificate_check: bool, ) -> Result> { if host.is_empty() { return Err(Report::new(TrustedServerError::Proxy { @@ -36,20 +59,33 @@ pub fn ensure_origin_backend( let host_with_port = format!("{}:{}", host, target_port); // Name: iframe___ (sanitize '.' and ':') + // Include cert setting in name to avoid reusing a backend with different cert settings let name_base = format!("{}_{}_{}", scheme, host, target_port); - let backend_name = format!("backend_{}", name_base.replace(['.', ':'], "_")); + let cert_suffix = if certificate_check { "" } else { "_nocert" }; + let backend_name = format!( + "backend_{}{}", + name_base.replace(['.', ':'], "_"), + cert_suffix + ); + + let host_header = compute_host_header(scheme, host, target_port); // Target base is host[:port]; SSL is enabled only for https scheme let mut builder = Backend::builder(&backend_name, &host_with_port) - .override_host(host) + .override_host(&host_header) .connect_timeout(Duration::from_secs(1)) .first_byte_timeout(Duration::from_secs(15)) .between_bytes_timeout(Duration::from_secs(10)); if scheme.eq_ignore_ascii_case("https") { - builder = builder - .enable_ssl() - .sni_hostname(host) - .check_certificate(host); + builder = builder.enable_ssl().sni_hostname(host); + if certificate_check { + builder = builder.check_certificate(host); + } else { + log::warn!( + "INSECURE: certificate check disabled for backend: {}", + backend_name + ); + } log::info!("enable ssl for backend: {}", backend_name); } @@ -79,14 +115,10 @@ pub fn ensure_origin_backend( } } -/// Ensures a dynamic backend exists for the given origin URL. -/// -/// Parses the URL and delegates to `ensure_origin_backend` to create or reuse a backend. -/// -/// # Errors -/// -/// Returns an error if the URL cannot be parsed or lacks a host, or if backend creation fails. -pub fn ensure_backend_from_url(origin_url: &str) -> Result> { +pub fn ensure_backend_from_url( + origin_url: &str, + certificate_check: bool, +) -> Result> { let parsed_url = Url::parse(origin_url).change_context(TrustedServerError::Proxy { message: format!("Invalid origin_url: {}", origin_url), })?; @@ -99,22 +131,69 @@ pub fn ensure_backend_from_url(origin_url: &str) -> Result String { // Image src + data-src element!("img", |el| { if let Some(src) = el.get_attribute("src") { + log::debug!("creative rewrite: img src input = {}", src); if let Some(p) = proxy_if_abs(settings, &src) { + log::debug!("creative rewrite: img src output = {}", p); let _ = el.set_attribute("src", &p); } } @@ -639,6 +642,43 @@ mod tests { assert_eq!(to_abs(&settings, "mailto:test@example.com"), None); } + #[test] + fn to_abs_preserves_port_in_protocol_relative() { + let settings = crate::test_support::tests::create_test_settings(); + // Protocol-relative URL with explicit port should preserve the port + assert_eq!( + to_abs(&settings, "//cdn.example.com:8080/asset.js"), + Some("https://cdn.example.com:8080/asset.js".to_string()) + ); + assert_eq!( + to_abs(&settings, "//cdn.example.com:9443/img.png"), + Some("https://cdn.example.com:9443/img.png".to_string()) + ); + } + + #[test] + fn rewrite_creative_preserves_non_standard_port() { + // Verify creative rewriting preserves non-standard ports in URLs + let settings = crate::test_support::tests::create_test_settings(); + let html = r#" + + + + + + + +"#; + let out = rewrite_creative_html(&settings, html); + + // Port 9443 should be preserved (URL-encoded as %3A9443) + assert!( + out.contains("cdn.example.com%3A9443"), + "Port 9443 should be preserved in rewritten URLs: {}", + out + ); + } + #[test] fn rewrite_style_urls_handles_absolute_and_relative() { let settings = crate::test_support::tests::create_test_settings(); diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index a2466baf..8b7f18fa 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -120,7 +120,7 @@ impl FastlyApiClient { store_name: &str, key_name: &str, ) -> Result> { - let backend_name = ensure_backend_from_url("https://api.fastly.com")?; + let backend_name = ensure_backend_from_url("https://api.fastly.com", true)?; let secret_store = FastlySecretStore::new(store_name); let api_key = secret_store.get(key_name)?; diff --git a/crates/common/src/integrations/adserver_mock.rs b/crates/common/src/integrations/adserver_mock.rs index 253a7a88..44b16cba 100644 --- a/crates/common/src/integrations/adserver_mock.rs +++ b/crates/common/src/integrations/adserver_mock.rs @@ -277,7 +277,7 @@ impl AuctionProvider for AdServerMockProvider { })?; // Send async - let backend_name = ensure_backend_from_url(&self.config.endpoint).change_context( + let backend_name = ensure_backend_from_url(&self.config.endpoint, true).change_context( TrustedServerError::Auction { message: format!( "Failed to resolve backend for mediation endpoint: {}", @@ -340,7 +340,7 @@ impl AuctionProvider for AdServerMockProvider { } fn backend_name(&self) -> Option { - ensure_backend_from_url(&self.config.endpoint).ok() + ensure_backend_from_url(&self.config.endpoint, true).ok() } } diff --git a/crates/common/src/integrations/aps.rs b/crates/common/src/integrations/aps.rs index 02d946f8..1957938d 100644 --- a/crates/common/src/integrations/aps.rs +++ b/crates/common/src/integrations/aps.rs @@ -453,7 +453,7 @@ impl AuctionProvider for ApsAuctionProvider { })?; // Send request asynchronously - let backend_name = ensure_backend_from_url(&self.config.endpoint).change_context( + let backend_name = ensure_backend_from_url(&self.config.endpoint, true).change_context( TrustedServerError::Auction { message: format!( "Failed to resolve backend for APS endpoint: {}", @@ -518,7 +518,7 @@ impl AuctionProvider for ApsAuctionProvider { } fn backend_name(&self) -> Option { - ensure_backend_from_url(&self.config.endpoint).ok() + ensure_backend_from_url(&self.config.endpoint, true).ok() } } diff --git a/crates/common/src/integrations/didomi.rs b/crates/common/src/integrations/didomi.rs index 457a39bf..6dd17a53 100644 --- a/crates/common/src/integrations/didomi.rs +++ b/crates/common/src/integrations/didomi.rs @@ -203,7 +203,7 @@ impl IntegrationProxy for DidomiIntegration { let target_url = self .build_target_url(base_origin, consent_path, req.get_query_str()) .change_context(Self::error("Failed to build Didomi target URL"))?; - let backend_name = ensure_backend_from_url(base_origin) + let backend_name = ensure_backend_from_url(base_origin, true) .change_context(Self::error("Failed to configure Didomi backend"))?; let mut proxy_req = Request::new(req.get_method().clone(), &target_url); diff --git a/crates/common/src/integrations/lockr.rs b/crates/common/src/integrations/lockr.rs index 83c10dfa..715b4b95 100644 --- a/crates/common/src/integrations/lockr.rs +++ b/crates/common/src/integrations/lockr.rs @@ -148,7 +148,7 @@ impl LockrIntegration { lockr_req.set_header(header::USER_AGENT, "TrustedServer/1.0"); lockr_req.set_header(header::ACCEPT, "application/javascript, */*"); - let backend_name = ensure_backend_from_url(sdk_url) + let backend_name = ensure_backend_from_url(sdk_url, true) .change_context(Self::error("Failed to determine backend for SDK fetch"))?; let mut lockr_response = @@ -242,7 +242,7 @@ impl LockrIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.api_endpoint) + let backend_name = ensure_backend_from_url(&self.config.api_endpoint, true) .change_context(Self::error("Failed to determine backend for API proxy"))?; let response = match target_req.send(backend_name) { diff --git a/crates/common/src/integrations/permutive.rs b/crates/common/src/integrations/permutive.rs index 96e7630b..4c9e7701 100644 --- a/crates/common/src/integrations/permutive.rs +++ b/crates/common/src/integrations/permutive.rs @@ -118,7 +118,7 @@ impl PermutiveIntegration { permutive_req.set_header(header::USER_AGENT, "TrustedServer/1.0"); permutive_req.set_header(header::ACCEPT, "application/javascript, */*"); - let backend_name = ensure_backend_from_url(&sdk_url) + let backend_name = ensure_backend_from_url(&sdk_url, true) .change_context(Self::error("Failed to determine backend for SDK fetch"))?; let mut permutive_response = @@ -208,7 +208,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.api_endpoint) + let backend_name = ensure_backend_from_url(&self.config.api_endpoint, true) .change_context(Self::error("Failed to determine backend for API proxy"))?; let response = target_req @@ -277,7 +277,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.secure_signals_endpoint) + let backend_name = ensure_backend_from_url(&self.config.secure_signals_endpoint, true) .change_context(Self::error( "Failed to determine backend for Secure Signals proxy", ))?; @@ -342,7 +342,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url("https://events.permutive.app") + let backend_name = ensure_backend_from_url("https://events.permutive.app", true) .change_context(Self::error("Failed to determine backend for Events proxy"))?; let response = target_req @@ -405,7 +405,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url("https://sync.permutive.com") + let backend_name = ensure_backend_from_url("https://sync.permutive.com", true) .change_context(Self::error("Failed to determine backend for Sync proxy"))?; let response = target_req @@ -460,7 +460,7 @@ impl PermutiveIntegration { self.copy_request_headers(&req, &mut target_req); // Get backend and forward - let backend_name = ensure_backend_from_url("https://cdn.permutive.com") + let backend_name = ensure_backend_from_url("https://cdn.permutive.com", true) .change_context(Self::error("Failed to determine backend for CDN proxy"))?; let response = target_req diff --git a/crates/common/src/integrations/prebid.rs b/crates/common/src/integrations/prebid.rs index 8432fdec..439444a6 100644 --- a/crates/common/src/integrations/prebid.rs +++ b/crates/common/src/integrations/prebid.rs @@ -647,7 +647,7 @@ impl AuctionProvider for PrebidAuctionProvider { })?; // Send request asynchronously - let backend_name = ensure_backend_from_url(&self.config.server_url)?; + let backend_name = ensure_backend_from_url(&self.config.server_url, true)?; let pending = pbs_req .send_async(backend_name) @@ -724,7 +724,7 @@ impl AuctionProvider for PrebidAuctionProvider { } fn backend_name(&self) -> Option { - ensure_backend_from_url(&self.config.server_url).ok() + ensure_backend_from_url(&self.config.server_url, true).ok() } } diff --git a/crates/common/src/proxy.rs b/crates/common/src/proxy.rs index 6ae49856..b595b471 100644 --- a/crates/common/src/proxy.rs +++ b/crates/common/src/proxy.rs @@ -479,7 +479,12 @@ async fn proxy_with_redirects( })); } - let backend_name = crate::backend::ensure_origin_backend(&scheme, host, parsed_url.port())?; + let backend_name = crate::backend::ensure_origin_backend( + &scheme, + host, + parsed_url.port(), + settings.proxy.certificate_check, + )?; let mut proxy_req = Request::new(current_method.clone(), ¤t_url); copy_proxy_forward_headers(req, &mut proxy_req); @@ -1175,6 +1180,28 @@ mod tests { assert_eq!(err.current_context().status_code(), StatusCode::BAD_GATEWAY); } + #[tokio::test] + async fn proxy_sign_preserves_non_standard_port() { + let settings = create_test_settings(); + // Test with non-standard port (e.g., 9443) + let body = serde_json::json!({ + "url": "https://cdn.example.com:9443/img/300x250.svg", + }); + let mut req = Request::new(Method::POST, "https://edge.example/first-party/sign"); + req.set_body(body.to_string()); + let mut resp = handle_first_party_proxy_sign(&settings, req) + .await + .expect("sign ok"); + assert_eq!(resp.get_status(), StatusCode::OK); + let json = resp.take_body_str(); + // Port 9443 should be preserved (URL-encoded as %3A9443) + assert!( + json.contains("%3A9443"), + "Port should be preserved in signed URL: {}", + json + ); + } + #[test] fn proxy_request_config_supports_streaming_and_headers() { let cfg = ProxyRequestConfig::new("https://example.com/asset") @@ -1553,6 +1580,45 @@ mod tests { assert_eq!(ct, "text/css; charset=utf-8"); } + #[test] + fn html_response_rewrite_preserves_non_standard_port() { + // Verify that HTML rewriting preserves non-standard ports in sub-resource URLs. + // This is the core test for the port preservation fix. + let settings = create_test_settings(); + + let html = r#" + + + + + + + +"#; + + let beresp = Response::from_status(StatusCode::OK) + .with_header(header::CONTENT_TYPE, "text/html; charset=utf-8") + .with_body(html); + + let req = Request::new(Method::GET, "https://edge.example/first-party/proxy"); + let mut out = finalize( + &settings, + &req, + "https://cdn.example.com:9443/creatives/300x250.html", + beresp, + ) + .expect("finalize should succeed"); + + let body = out.take_body_str(); + + // Port 9443 should be preserved (URL-encoded as %3A9443) + assert!( + body.contains("cdn.example.com%3A9443"), + "Port 9443 should be preserved in rewritten URLs. Body:\n{}", + body + ); + } + #[test] fn image_accept_sets_generic_content_type_when_missing() { let settings = create_test_settings(); diff --git a/crates/common/src/publisher.rs b/crates/common/src/publisher.rs index 7d8f92d5..d3ce08f9 100644 --- a/crates/common/src/publisher.rs +++ b/crates/common/src/publisher.rs @@ -216,7 +216,7 @@ pub fn handle_publisher_request( has_synthetic_cookie ); - let backend_name = ensure_backend_from_url(&settings.publisher.origin_url)?; + let backend_name = ensure_backend_from_url(&settings.publisher.origin_url, true)?; let origin_host = settings.publisher.origin_host(); log::debug!( diff --git a/crates/common/src/settings.rs b/crates/common/src/settings.rs index 84bbd9a3..4ddd7646 100644 --- a/crates/common/src/settings.rs +++ b/crates/common/src/settings.rs @@ -277,6 +277,27 @@ fn default_request_signing_enabled() -> bool { false } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Proxy { + /// Enable TLS certificate verification when proxying to HTTPS origins. + /// Defaults to true for secure production use. + /// Set to false for local development with self-signed certificates. + #[serde(default = "default_certificate_check")] + pub certificate_check: bool, +} + +fn default_certificate_check() -> bool { + true +} + +impl Default for Proxy { + fn default() -> Self { + Self { + certificate_check: default_certificate_check(), + } + } +} + #[derive(Debug, Default, Clone, Deserialize, Serialize, Validate)] pub struct Settings { #[validate(nested)] @@ -297,6 +318,8 @@ pub struct Settings { pub rewrite: Rewrite, #[serde(default)] pub auction: AuctionConfig, + #[serde(default)] + pub proxy: Proxy, } #[allow(unused)] From d9cffe41697075ac74b3030cbeb000ba77fe364d Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:14:03 -0800 Subject: [PATCH 2/3] Fixed port handling for certificate verification in the backend and proxy components. Updated related settings and documentation. --- .env.dev | 4 ++ crates/common/src/backend.rs | 91 ++++++++++++++++++++++------------ crates/common/src/creative.rs | 9 ++-- crates/common/src/proxy.rs | 11 ++-- crates/common/src/publisher.rs | 5 +- crates/common/src/settings.rs | 4 ++ trusted-server.toml | 6 +++ 7 files changed, 89 insertions(+), 41 deletions(-) diff --git a/.env.dev b/.env.dev index 4272f2fe..a637f6ab 100644 --- a/.env.dev +++ b/.env.dev @@ -4,3 +4,7 @@ TRUSTED_SERVER__PUBLISHER__ORIGIN_URL=http://localhost:9090 # [synthetic] TRUSTED_SERVER__SYNTHETIC__COUNTER_STORE=counter_store TRUSTED_SERVER__SYNTHETIC__OPID_STORE=opid_store + +# [proxy] +# Disable TLS certificate verification for local dev with self-signed certs +# TRUSTED_SERVER__PROXY__CERTIFICATE_CHECK=false diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs index 2dbedb02..3cc28f46 100644 --- a/crates/common/src/backend.rs +++ b/crates/common/src/backend.rs @@ -6,6 +6,16 @@ use url::Url; use crate::error::TrustedServerError; +/// Returns the default port for the given scheme (443 for HTTPS, 80 for HTTP). +#[inline] +fn default_port_for_scheme(scheme: &str) -> u16 { + if scheme.eq_ignore_ascii_case("https") { + 443 + } else { + 80 + } +} + /// Compute the Host header value for a backend request. /// /// For standard ports (443 for HTTPS, 80 for HTTP), returns just the hostname. @@ -16,9 +26,7 @@ use crate::error::TrustedServerError; /// would generate URLs without the port when the Host header didn't include it. #[inline] fn compute_host_header(scheme: &str, host: &str, port: u16) -> String { - let is_https = scheme.eq_ignore_ascii_case("https"); - let default_port = if is_https { 443 } else { 80 }; - if port != default_port { + if port != default_port_for_scheme(scheme) { format!("{}:{}", host, port) } else { host.to_string() @@ -37,6 +45,11 @@ fn compute_host_header(scheme: &str, host: &str, port: u16) -> String { /// * `host` - The hostname /// * `port` - Optional port number /// * `certificate_check` - If true, enables TLS certificate verification (default for production) +/// +/// # Errors +/// +/// Returns an error if the host is empty or if backend creation fails +/// (except for `NameInUse` which reuses the existing backend). pub fn ensure_origin_backend( scheme: &str, host: &str, @@ -49,12 +62,7 @@ pub fn ensure_origin_backend( })); } - let is_https = scheme.eq_ignore_ascii_case("https"); - let target_port = match (port, is_https) { - (Some(p), _) => p, - (None, true) => 443, - (None, false) => 80, - }; + let target_port = port.unwrap_or_else(|| default_port_for_scheme(scheme)); let host_with_port = format!("{}:{}", host, target_port); @@ -115,6 +123,13 @@ pub fn ensure_origin_backend( } } +/// Ensures a dynamic backend exists for the given origin URL. +/// +/// Parses the URL and delegates to [`ensure_origin_backend`] to create or reuse a backend. +/// +/// # Errors +/// +/// Returns an error if the URL cannot be parsed or lacks a host, or if backend creation fails. pub fn ensure_backend_from_url( origin_url: &str, certificate_check: bool, @@ -141,83 +156,97 @@ mod tests { // Tests for compute_host_header - the fix for port preservation in Host header #[test] fn host_header_includes_port_for_non_standard_https() { - // Non-standard port 9443 should be included in Host header assert_eq!( compute_host_header("https", "cdn.example.com", 9443), - "cdn.example.com:9443" + "cdn.example.com:9443", + "should include non-standard HTTPS port 9443 in Host header" ); assert_eq!( compute_host_header("https", "cdn.example.com", 8443), - "cdn.example.com:8443" + "cdn.example.com:8443", + "should include non-standard HTTPS port 8443 in Host header" ); } #[test] fn host_header_excludes_port_for_standard_https() { - // Standard port 443 should NOT be included assert_eq!( compute_host_header("https", "cdn.example.com", 443), - "cdn.example.com" + "cdn.example.com", + "should omit standard HTTPS port 443 from Host header" ); } #[test] fn host_header_includes_port_for_non_standard_http() { - // Non-standard port 8080 should be included assert_eq!( compute_host_header("http", "cdn.example.com", 8080), - "cdn.example.com:8080" + "cdn.example.com:8080", + "should include non-standard HTTP port 8080 in Host header" ); } #[test] fn host_header_excludes_port_for_standard_http() { - // Standard port 80 should NOT be included assert_eq!( compute_host_header("http", "cdn.example.com", 80), - "cdn.example.com" + "cdn.example.com", + "should omit standard HTTP port 80 from Host header" ); } #[test] fn returns_name_for_https_with_cert_check() { - let name = ensure_origin_backend("https", "origin.example.com", None, true).unwrap(); + let name = ensure_origin_backend("https", "origin.example.com", None, true) + .expect("should create backend for valid HTTPS origin"); assert_eq!(name, "backend_https_origin_example_com_443"); } #[test] fn returns_name_for_https_without_cert_check() { - let name = ensure_origin_backend("https", "origin.example.com", None, false).unwrap(); + let name = ensure_origin_backend("https", "origin.example.com", None, false) + .expect("should create backend with cert check disabled"); assert_eq!(name, "backend_https_origin_example_com_443_nocert"); } #[test] fn returns_name_for_http_with_port_and_sanitizes() { - let name = ensure_origin_backend("http", "api.test-site.org", Some(8080), true).unwrap(); + let name = ensure_origin_backend("http", "api.test-site.org", Some(8080), true) + .expect("should create backend for HTTP origin with explicit port"); assert_eq!(name, "backend_http_api_test-site_org_8080"); - // Explicitly check that ':' was replaced with '_' - assert!(name.ends_with("_8080")); + assert!( + name.ends_with("_8080"), + "should sanitize ':' to '_' in backend name" + ); } #[test] fn returns_name_for_http_without_port_defaults_to_80() { - let name = ensure_origin_backend("http", "example.org", None, true).unwrap(); + let name = ensure_origin_backend("http", "example.org", None, true) + .expect("should create backend defaulting to port 80 for HTTP"); assert_eq!(name, "backend_http_example_org_80"); } #[test] fn error_on_missing_host() { - let err = ensure_origin_backend("https", "", None, true) - .err() - .unwrap(); + let err = + ensure_origin_backend("https", "", None, true).expect_err("should reject empty host"); let msg = err.to_string(); - assert!(msg.contains("missing host")); + assert!( + msg.contains("missing host"), + "should report missing host in error message" + ); } #[test] fn second_call_reuses_existing_backend() { - let first = ensure_origin_backend("https", "reuse.example.com", None, true).unwrap(); - let second = ensure_origin_backend("https", "reuse.example.com", None, true).unwrap(); - assert_eq!(first, second); + let first = ensure_origin_backend("https", "reuse.example.com", None, true) + .expect("should create backend on first call"); + let second = ensure_origin_backend("https", "reuse.example.com", None, true) + .expect("should reuse backend on second call"); + assert_eq!( + first, second, + "should return same backend name on repeat call" + ); } } diff --git a/crates/common/src/creative.rs b/crates/common/src/creative.rs index 157130cc..3a6d01de 100644 --- a/crates/common/src/creative.rs +++ b/crates/common/src/creative.rs @@ -332,9 +332,7 @@ pub fn rewrite_creative_html(settings: &Settings, markup: &str) -> String { // Image src + data-src element!("img", |el| { if let Some(src) = el.get_attribute("src") { - log::debug!("creative rewrite: img src input = {}", src); if let Some(p) = proxy_if_abs(settings, &src) { - log::debug!("creative rewrite: img src output = {}", p); let _ = el.set_attribute("src", &p); } } @@ -645,14 +643,15 @@ mod tests { #[test] fn to_abs_preserves_port_in_protocol_relative() { let settings = crate::test_support::tests::create_test_settings(); - // Protocol-relative URL with explicit port should preserve the port assert_eq!( to_abs(&settings, "//cdn.example.com:8080/asset.js"), - Some("https://cdn.example.com:8080/asset.js".to_string()) + Some("https://cdn.example.com:8080/asset.js".to_string()), + "should preserve port 8080 in protocol-relative URL" ); assert_eq!( to_abs(&settings, "//cdn.example.com:9443/img.png"), - Some("https://cdn.example.com:9443/img.png".to_string()) + Some("https://cdn.example.com:9443/img.png".to_string()), + "should preserve port 9443 in protocol-relative URL" ); } diff --git a/crates/common/src/proxy.rs b/crates/common/src/proxy.rs index b595b471..db784313 100644 --- a/crates/common/src/proxy.rs +++ b/crates/common/src/proxy.rs @@ -1183,7 +1183,6 @@ mod tests { #[tokio::test] async fn proxy_sign_preserves_non_standard_port() { let settings = create_test_settings(); - // Test with non-standard port (e.g., 9443) let body = serde_json::json!({ "url": "https://cdn.example.com:9443/img/300x250.svg", }); @@ -1191,8 +1190,12 @@ mod tests { req.set_body(body.to_string()); let mut resp = handle_first_party_proxy_sign(&settings, req) .await - .expect("sign ok"); - assert_eq!(resp.get_status(), StatusCode::OK); + .expect("should sign URL with non-standard port"); + assert_eq!( + resp.get_status(), + StatusCode::OK, + "should return 200 for valid sign request" + ); let json = resp.take_body_str(); // Port 9443 should be preserved (URL-encoded as %3A9443) assert!( @@ -1607,7 +1610,7 @@ mod tests { "https://cdn.example.com:9443/creatives/300x250.html", beresp, ) - .expect("finalize should succeed"); + .expect("should finalize HTML response with non-standard port URL"); let body = out.take_body_str(); diff --git a/crates/common/src/publisher.rs b/crates/common/src/publisher.rs index d3ce08f9..e0e2ea8f 100644 --- a/crates/common/src/publisher.rs +++ b/crates/common/src/publisher.rs @@ -216,7 +216,10 @@ pub fn handle_publisher_request( has_synthetic_cookie ); - let backend_name = ensure_backend_from_url(&settings.publisher.origin_url, true)?; + let backend_name = ensure_backend_from_url( + &settings.publisher.origin_url, + settings.proxy.certificate_check, + )?; let origin_host = settings.publisher.origin_host(); log::debug!( diff --git a/crates/common/src/settings.rs b/crates/common/src/settings.rs index 4ddd7646..b302c40e 100644 --- a/crates/common/src/settings.rs +++ b/crates/common/src/settings.rs @@ -348,6 +348,10 @@ impl Settings { return Err(Report::new(TrustedServerError::InsecureSecretKey)); } + if !settings.proxy.certificate_check { + log::warn!("INSECURE: proxy.certificate_check is disabled — TLS certificates will NOT be verified"); + } + Ok(settings) } diff --git a/trusted-server.toml b/trusted-server.toml index 2e22c06c..ee4f84b4 100644 --- a/trusted-server.toml +++ b/trusted-server.toml @@ -84,6 +84,12 @@ rewrite_sdk = true # ] +# Proxy configuration +# [proxy] +# Enable TLS certificate verification when proxying to HTTPS origins. +# Defaults to true. Set to false only for local development with self-signed certificates. +# certificate_check = true + [auction] enabled = true providers = ["prebid"] From 511141cd7f1ee122d37bbe62583ae1559980bb8c Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:34:18 -0800 Subject: [PATCH 3/3] Added BackendConfig struct to hold backend configuration, including cert verification port. Updated provider and proxy to use the new config struct. Updated tests to reflect changes. --- crates/common/src/auction/provider.rs | 2 +- crates/common/src/backend.rs | 276 ++++++++++-------- crates/common/src/fastly_storage.rs | 4 +- .../common/src/integrations/adserver_mock.rs | 6 +- crates/common/src/integrations/aps.rs | 6 +- crates/common/src/integrations/didomi.rs | 4 +- crates/common/src/integrations/lockr.rs | 6 +- crates/common/src/integrations/permutive.rs | 14 +- crates/common/src/integrations/prebid.rs | 6 +- crates/common/src/proxy.rs | 10 +- crates/common/src/publisher.rs | 4 +- 11 files changed, 192 insertions(+), 146 deletions(-) diff --git a/crates/common/src/auction/provider.rs b/crates/common/src/auction/provider.rs index 827b33fb..d6068b89 100644 --- a/crates/common/src/auction/provider.rs +++ b/crates/common/src/auction/provider.rs @@ -62,7 +62,7 @@ pub trait AuctionProvider: Send + Sync { /// /// This is used by the orchestrator to correlate responses with providers /// when using `select()` to wait for multiple concurrent requests. - /// The backend name should match what `ensure_backend_from_url()` returns + /// The backend name should match what `BackendConfig::from_url()` returns /// for this provider's endpoint. fn backend_name(&self) -> Option { None diff --git a/crates/common/src/backend.rs b/crates/common/src/backend.rs index 2190d0e1..25f61688 100644 --- a/crates/common/src/backend.rs +++ b/crates/common/src/backend.rs @@ -33,128 +33,167 @@ fn compute_host_header(scheme: &str, host: &str, port: u16) -> String { } } -/// Ensure a dynamic backend exists for the given origin and return its name. +/// Configuration for creating a dynamic Fastly backend. /// -/// The backend name is derived from the scheme and `host[:port]` to avoid collisions across -/// http/https or different ports. If a backend with the derived name already exists, -/// this function logs and reuses it. -/// -/// # Arguments -/// -/// * `scheme` - The URL scheme ("http" or "https") -/// * `host` - The hostname -/// * `port` - Optional port number -/// * `certificate_check` - If true, enables TLS certificate verification (default for production) -/// -/// # Errors -/// -/// Returns an error if the host is empty or if backend creation fails -/// (except for `NameInUse` which reuses the existing backend). -pub fn ensure_origin_backend( - scheme: &str, - host: &str, +/// Uses the builder pattern so that new options can be added without changing +/// existing call sites — fields carry sensible defaults. +pub struct BackendConfig<'a> { + scheme: &'a str, + host: &'a str, port: Option, certificate_check: bool, -) -> Result> { - if host.is_empty() { - return Err(Report::new(TrustedServerError::Proxy { - message: "missing host".to_string(), - })); - } - - let target_port = port.unwrap_or_else(|| default_port_for_scheme(scheme)); - - let host_with_port = format!("{}:{}", host, target_port); - - // Name: iframe___ (sanitize '.' and ':') - // Include cert setting in name to avoid reusing a backend with different cert settings - let name_base = format!("{}_{}_{}", scheme, host, target_port); - let cert_suffix = if certificate_check { "" } else { "_nocert" }; - let backend_name = format!( - "backend_{}{}", - name_base.replace(['.', ':'], "_"), - cert_suffix - ); - - let host_header = compute_host_header(scheme, host, target_port); - - // Target base is host[:port]; SSL is enabled only for https scheme - let mut builder = Backend::builder(&backend_name, &host_with_port) - .override_host(&host_header) - .connect_timeout(Duration::from_secs(1)) - .first_byte_timeout(Duration::from_secs(15)) - .between_bytes_timeout(Duration::from_secs(10)); - if scheme.eq_ignore_ascii_case("https") { - builder = builder.enable_ssl().sni_hostname(host); - if certificate_check { - builder = builder - .enable_ssl() - .sni_hostname(host) - .check_certificate(host); - } else { - log::warn!( - "INSECURE: certificate check disabled for backend: {}", - backend_name - ); +} + +impl<'a> BackendConfig<'a> { + /// Create a new configuration with required fields and safe defaults. + /// + /// `certificate_check` defaults to `true`. + #[must_use] + pub fn new(scheme: &'a str, host: &'a str) -> Self { + Self { + scheme, + host, + port: None, + certificate_check: true, } - log::info!("enable ssl for backend: {}", backend_name); } - match builder.finish() { - Ok(_) => { - log::info!( - "created dynamic backend: {} -> {}", - backend_name, - host_with_port - ); - Ok(backend_name) + /// Set the port for the backend. When `None`, the default port for the + /// scheme is used (443 for HTTPS, 80 for HTTP). + #[must_use] + pub fn port(mut self, port: Option) -> Self { + self.port = port; + self + } + + /// Control TLS certificate verification. Defaults to `true`. + #[must_use] + pub fn certificate_check(mut self, check: bool) -> Self { + self.certificate_check = check; + self + } + + /// Ensure a dynamic backend exists for this configuration and return its name. + /// + /// The backend name is derived from the scheme, host, port, and certificate + /// setting to avoid collisions. If a backend with the derived name already + /// exists, this function logs and reuses it. + /// + /// # Errors + /// + /// Returns an error if the host is empty or if backend creation fails + /// (except for `NameInUse` which reuses the existing backend). + pub fn ensure(self) -> Result> { + if self.host.is_empty() { + return Err(Report::new(TrustedServerError::Proxy { + message: "missing host".to_string(), + })); } - Err(e) => { - let msg = e.to_string(); - if msg.contains("NameInUse") || msg.contains("already in use") { - log::info!("reusing existing dynamic backend: {}", backend_name); - Ok(backend_name) + + let target_port = self + .port + .unwrap_or_else(|| default_port_for_scheme(self.scheme)); + + let host_with_port = format!("{}:{}", self.host, target_port); + + // Include cert setting in name to avoid reusing a backend with different cert settings + let name_base = format!("{}_{}_{}", self.scheme, self.host, target_port); + let cert_suffix = if self.certificate_check { + "" + } else { + "_nocert" + }; + let backend_name = format!( + "backend_{}{}", + name_base.replace(['.', ':'], "_"), + cert_suffix + ); + + let host_header = compute_host_header(self.scheme, self.host, target_port); + + // Target base is host[:port]; SSL is enabled only for https scheme + let mut builder = Backend::builder(&backend_name, &host_with_port) + .override_host(&host_header) + .connect_timeout(Duration::from_secs(1)) + .first_byte_timeout(Duration::from_secs(15)) + .between_bytes_timeout(Duration::from_secs(10)); + if self.scheme.eq_ignore_ascii_case("https") { + builder = builder.enable_ssl().sni_hostname(self.host); + if self.certificate_check { + builder = builder + .enable_ssl() + .sni_hostname(self.host) + .check_certificate(self.host); } else { - Err(Report::new(TrustedServerError::Proxy { - message: format!( - "dynamic backend creation failed ({} -> {}): {}", - backend_name, host_with_port, msg - ), - })) + log::warn!( + "INSECURE: certificate check disabled for backend: {}", + backend_name + ); + } + log::info!("enable ssl for backend: {}", backend_name); + } + + match builder.finish() { + Ok(_) => { + log::info!( + "created dynamic backend: {} -> {}", + backend_name, + host_with_port + ); + Ok(backend_name) + } + Err(e) => { + let msg = e.to_string(); + if msg.contains("NameInUse") || msg.contains("already in use") { + log::info!("reusing existing dynamic backend: {}", backend_name); + Ok(backend_name) + } else { + Err(Report::new(TrustedServerError::Proxy { + message: format!( + "dynamic backend creation failed ({} -> {}): {}", + backend_name, host_with_port, msg + ), + })) + } } } } -} -/// Ensures a dynamic backend exists for the given origin URL. -/// -/// Parses the URL and delegates to [`ensure_origin_backend`] to create or reuse a backend. -/// -/// # Errors -/// -/// Returns an error if the URL cannot be parsed or lacks a host, or if backend creation fails. -pub fn ensure_backend_from_url( - origin_url: &str, - certificate_check: bool, -) -> Result> { - let parsed_url = Url::parse(origin_url).change_context(TrustedServerError::Proxy { - message: format!("Invalid origin_url: {}", origin_url), - })?; - - let scheme = parsed_url.scheme(); - let host = parsed_url.host_str().ok_or_else(|| { - Report::new(TrustedServerError::Proxy { - message: "Missing host in origin_url".to_string(), - }) - })?; - let port = parsed_url.port(); - - ensure_origin_backend(scheme, host, port, certificate_check) + /// Parse an origin URL and ensure a dynamic backend exists for it. + /// + /// This is a convenience constructor that parses the URL, extracts scheme, + /// host, and port, then calls [`ensure`](Self::ensure). + /// + /// # Errors + /// + /// Returns an error if the URL cannot be parsed or lacks a host, or if + /// backend creation fails. + pub fn from_url( + origin_url: &str, + certificate_check: bool, + ) -> Result> { + let parsed_url = Url::parse(origin_url).change_context(TrustedServerError::Proxy { + message: format!("Invalid origin_url: {}", origin_url), + })?; + + let scheme = parsed_url.scheme(); + let host = parsed_url.host_str().ok_or_else(|| { + Report::new(TrustedServerError::Proxy { + message: "Missing host in origin_url".to_string(), + }) + })?; + let port = parsed_url.port(); + + BackendConfig::new(scheme, host) + .port(port) + .certificate_check(certificate_check) + .ensure() + } } #[cfg(test)] mod tests { - use super::{compute_host_header, ensure_origin_backend}; + use super::{compute_host_header, BackendConfig}; // Tests for compute_host_header - the fix for port preservation in Host header #[test] @@ -200,21 +239,26 @@ mod tests { #[test] fn returns_name_for_https_with_cert_check() { - let name = ensure_origin_backend("https", "origin.example.com", None, true) + let name = BackendConfig::new("https", "origin.example.com") + .ensure() .expect("should create backend for valid HTTPS origin"); assert_eq!(name, "backend_https_origin_example_com_443"); } #[test] fn returns_name_for_https_without_cert_check() { - let name = ensure_origin_backend("https", "origin.example.com", None, false) + let name = BackendConfig::new("https", "origin.example.com") + .certificate_check(false) + .ensure() .expect("should create backend with cert check disabled"); assert_eq!(name, "backend_https_origin_example_com_443_nocert"); } #[test] fn returns_name_for_http_with_port_and_sanitizes() { - let name = ensure_origin_backend("http", "api.test-site.org", Some(8080), true) + let name = BackendConfig::new("http", "api.test-site.org") + .port(Some(8080)) + .ensure() .expect("should create backend for HTTP origin with explicit port"); assert_eq!(name, "backend_http_api_test-site_org_8080"); assert!( @@ -225,15 +269,17 @@ mod tests { #[test] fn returns_name_for_http_without_port_defaults_to_80() { - let name = ensure_origin_backend("http", "example.org", None, true) + let name = BackendConfig::new("http", "example.org") + .ensure() .expect("should create backend defaulting to port 80 for HTTP"); assert_eq!(name, "backend_http_example_org_80"); } #[test] fn error_on_missing_host() { - let err = - ensure_origin_backend("https", "", None, true).expect_err("should reject empty host"); + let err = BackendConfig::new("https", "") + .ensure() + .expect_err("should reject empty host"); let msg = err.to_string(); assert!( msg.contains("missing host"), @@ -243,9 +289,11 @@ mod tests { #[test] fn second_call_reuses_existing_backend() { - let first = ensure_origin_backend("https", "reuse.example.com", None, true) + let first = BackendConfig::new("https", "reuse.example.com") + .ensure() .expect("should create backend on first call"); - let second = ensure_origin_backend("https", "reuse.example.com", None, true) + let second = BackendConfig::new("https", "reuse.example.com") + .ensure() .expect("should reuse backend on second call"); assert_eq!( first, second, diff --git a/crates/common/src/fastly_storage.rs b/crates/common/src/fastly_storage.rs index 8b7f18fa..bedd2495 100644 --- a/crates/common/src/fastly_storage.rs +++ b/crates/common/src/fastly_storage.rs @@ -4,7 +4,7 @@ use error_stack::{Report, ResultExt}; use fastly::{ConfigStore, Request, Response, SecretStore}; use http::StatusCode; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; const FASTLY_API_HOST: &str = "https://api.fastly.com"; @@ -120,7 +120,7 @@ impl FastlyApiClient { store_name: &str, key_name: &str, ) -> Result> { - let backend_name = ensure_backend_from_url("https://api.fastly.com", true)?; + let backend_name = BackendConfig::from_url("https://api.fastly.com", true)?; let secret_store = FastlySecretStore::new(store_name); let api_key = secret_store.get(key_name)?; diff --git a/crates/common/src/integrations/adserver_mock.rs b/crates/common/src/integrations/adserver_mock.rs index 234efb39..b6d1e656 100644 --- a/crates/common/src/integrations/adserver_mock.rs +++ b/crates/common/src/integrations/adserver_mock.rs @@ -17,7 +17,7 @@ use crate::auction::provider::AuctionProvider; use crate::auction::types::{ AuctionContext, AuctionRequest, AuctionResponse, Bid, BidStatus, MediaType, }; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; use crate::settings::{IntegrationConfig, Settings}; @@ -277,7 +277,7 @@ impl AuctionProvider for AdServerMockProvider { })?; // Send async - let backend_name = ensure_backend_from_url(&self.config.endpoint, true).change_context( + let backend_name = BackendConfig::from_url(&self.config.endpoint, true).change_context( TrustedServerError::Auction { message: format!( "Failed to resolve backend for mediation endpoint: {}", @@ -340,7 +340,7 @@ impl AuctionProvider for AdServerMockProvider { } fn backend_name(&self) -> Option { - ensure_backend_from_url(&self.config.endpoint, true).ok() + BackendConfig::from_url(&self.config.endpoint, true).ok() } } diff --git a/crates/common/src/integrations/aps.rs b/crates/common/src/integrations/aps.rs index 1957938d..bdd9c25b 100644 --- a/crates/common/src/integrations/aps.rs +++ b/crates/common/src/integrations/aps.rs @@ -12,7 +12,7 @@ use validator::Validate; use crate::auction::provider::AuctionProvider; use crate::auction::types::{AuctionContext, AuctionRequest, AuctionResponse, Bid, MediaType}; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; use crate::settings::IntegrationConfig; @@ -453,7 +453,7 @@ impl AuctionProvider for ApsAuctionProvider { })?; // Send request asynchronously - let backend_name = ensure_backend_from_url(&self.config.endpoint, true).change_context( + let backend_name = BackendConfig::from_url(&self.config.endpoint, true).change_context( TrustedServerError::Auction { message: format!( "Failed to resolve backend for APS endpoint: {}", @@ -518,7 +518,7 @@ impl AuctionProvider for ApsAuctionProvider { } fn backend_name(&self) -> Option { - ensure_backend_from_url(&self.config.endpoint, true).ok() + BackendConfig::from_url(&self.config.endpoint, true).ok() } } diff --git a/crates/common/src/integrations/didomi.rs b/crates/common/src/integrations/didomi.rs index 09c9a4d9..b6f70567 100644 --- a/crates/common/src/integrations/didomi.rs +++ b/crates/common/src/integrations/didomi.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use url::Url; use validator::Validate; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; use crate::integrations::{IntegrationEndpoint, IntegrationProxy, IntegrationRegistration}; use crate::settings::{IntegrationConfig, Settings}; @@ -203,7 +203,7 @@ impl IntegrationProxy for DidomiIntegration { let target_url = self .build_target_url(base_origin, consent_path, req.get_query_str()) .change_context(Self::error("Failed to build Didomi target URL"))?; - let backend_name = ensure_backend_from_url(base_origin, true) + let backend_name = BackendConfig::from_url(base_origin, true) .change_context(Self::error("Failed to configure Didomi backend"))?; let mut proxy_req = Request::new(req.get_method().clone(), &target_url); diff --git a/crates/common/src/integrations/lockr.rs b/crates/common/src/integrations/lockr.rs index 009ccb64..1147e842 100644 --- a/crates/common/src/integrations/lockr.rs +++ b/crates/common/src/integrations/lockr.rs @@ -23,7 +23,7 @@ use regex::Regex; use serde::Deserialize; use validator::Validate; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; use crate::integrations::{ AttributeRewriteAction, IntegrationAttributeContext, IntegrationAttributeRewriter, @@ -148,7 +148,7 @@ impl LockrIntegration { lockr_req.set_header(header::USER_AGENT, "TrustedServer/1.0"); lockr_req.set_header(header::ACCEPT, "application/javascript, */*"); - let backend_name = ensure_backend_from_url(sdk_url, true) + let backend_name = BackendConfig::from_url(sdk_url, true) .change_context(Self::error("Failed to determine backend for SDK fetch"))?; let mut lockr_response = @@ -242,7 +242,7 @@ impl LockrIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.api_endpoint, true) + let backend_name = BackendConfig::from_url(&self.config.api_endpoint, true) .change_context(Self::error("Failed to determine backend for API proxy"))?; let response = match target_req.send(backend_name) { diff --git a/crates/common/src/integrations/permutive.rs b/crates/common/src/integrations/permutive.rs index 4c9e7701..dccb11db 100644 --- a/crates/common/src/integrations/permutive.rs +++ b/crates/common/src/integrations/permutive.rs @@ -12,7 +12,7 @@ use fastly::{Request, Response}; use serde::Deserialize; use validator::Validate; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; use crate::integrations::{ AttributeRewriteAction, IntegrationAttributeContext, IntegrationAttributeRewriter, @@ -118,7 +118,7 @@ impl PermutiveIntegration { permutive_req.set_header(header::USER_AGENT, "TrustedServer/1.0"); permutive_req.set_header(header::ACCEPT, "application/javascript, */*"); - let backend_name = ensure_backend_from_url(&sdk_url, true) + let backend_name = BackendConfig::from_url(&sdk_url, true) .change_context(Self::error("Failed to determine backend for SDK fetch"))?; let mut permutive_response = @@ -208,7 +208,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.api_endpoint, true) + let backend_name = BackendConfig::from_url(&self.config.api_endpoint, true) .change_context(Self::error("Failed to determine backend for API proxy"))?; let response = target_req @@ -277,7 +277,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url(&self.config.secure_signals_endpoint, true) + let backend_name = BackendConfig::from_url(&self.config.secure_signals_endpoint, true) .change_context(Self::error( "Failed to determine backend for Secure Signals proxy", ))?; @@ -342,7 +342,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url("https://events.permutive.app", true) + let backend_name = BackendConfig::from_url("https://events.permutive.app", true) .change_context(Self::error("Failed to determine backend for Events proxy"))?; let response = target_req @@ -405,7 +405,7 @@ impl PermutiveIntegration { } // Get backend and forward - let backend_name = ensure_backend_from_url("https://sync.permutive.com", true) + let backend_name = BackendConfig::from_url("https://sync.permutive.com", true) .change_context(Self::error("Failed to determine backend for Sync proxy"))?; let response = target_req @@ -460,7 +460,7 @@ impl PermutiveIntegration { self.copy_request_headers(&req, &mut target_req); // Get backend and forward - let backend_name = ensure_backend_from_url("https://cdn.permutive.com", true) + let backend_name = BackendConfig::from_url("https://cdn.permutive.com", true) .change_context(Self::error("Failed to determine backend for CDN proxy"))?; let response = target_req diff --git a/crates/common/src/integrations/prebid.rs b/crates/common/src/integrations/prebid.rs index 50b3028d..63132813 100644 --- a/crates/common/src/integrations/prebid.rs +++ b/crates/common/src/integrations/prebid.rs @@ -14,7 +14,7 @@ use crate::auction::provider::AuctionProvider; use crate::auction::types::{ AuctionContext, AuctionRequest, AuctionResponse, Bid as AuctionBid, MediaType, }; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::error::TrustedServerError; use crate::http_util::RequestInfo; use crate::integrations::{ @@ -647,7 +647,7 @@ impl AuctionProvider for PrebidAuctionProvider { })?; // Send request asynchronously - let backend_name = ensure_backend_from_url(&self.config.server_url, true)?; + let backend_name = BackendConfig::from_url(&self.config.server_url, true)?; let pending = pbs_req .send_async(backend_name) @@ -724,7 +724,7 @@ impl AuctionProvider for PrebidAuctionProvider { } fn backend_name(&self) -> Option { - ensure_backend_from_url(&self.config.server_url, true).ok() + BackendConfig::from_url(&self.config.server_url, true).ok() } } diff --git a/crates/common/src/proxy.rs b/crates/common/src/proxy.rs index caa690cc..5c2f0b97 100644 --- a/crates/common/src/proxy.rs +++ b/crates/common/src/proxy.rs @@ -479,12 +479,10 @@ async fn proxy_with_redirects( })); } - let backend_name = crate::backend::ensure_origin_backend( - &scheme, - host, - parsed_url.port(), - settings.proxy.certificate_check, - )?; + let backend_name = crate::backend::BackendConfig::new(&scheme, host) + .port(parsed_url.port()) + .certificate_check(settings.proxy.certificate_check) + .ensure()?; let mut proxy_req = Request::new(current_method.clone(), ¤t_url); copy_proxy_forward_headers(req, &mut proxy_req); diff --git a/crates/common/src/publisher.rs b/crates/common/src/publisher.rs index e0e2ea8f..fb160f3d 100644 --- a/crates/common/src/publisher.rs +++ b/crates/common/src/publisher.rs @@ -2,7 +2,7 @@ use error_stack::{Report, ResultExt}; use fastly::http::{header, StatusCode}; use fastly::{Body, Request, Response}; -use crate::backend::ensure_backend_from_url; +use crate::backend::BackendConfig; use crate::http_util::{serve_static_with_etag, RequestInfo}; use crate::constants::{COOKIE_SYNTHETIC_ID, HEADER_X_COMPRESS_HINT, HEADER_X_SYNTHETIC_ID}; @@ -216,7 +216,7 @@ pub fn handle_publisher_request( has_synthetic_cookie ); - let backend_name = ensure_backend_from_url( + let backend_name = BackendConfig::from_url( &settings.publisher.origin_url, settings.proxy.certificate_check, )?;