From 101675b5f84a40fdd840dae412b7395d8dc6adff Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 16 Feb 2026 13:29:45 +0000 Subject: [PATCH] fix(proxy): use TLS to backends for terminate-and-reencrypt routes --- changelog.md | 7 ++ .../rustproxy-http/src/proxy_service.rs | 10 ++- rust/crates/rustproxy/tests/common/mod.rs | 70 +++++++++++++++++++ .../rustproxy/tests/integration_http_proxy.rs | 12 ++-- ts/00_commitinfo_data.ts | 2 +- 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index b77dcfa..642dd75 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-02-16 - 25.7.1 - fix(proxy) +use TLS to backends for terminate-and-reencrypt routes + +- Set upstream.use_tls = true when a route's TLS mode is TerminateAndReencrypt so the proxy re-encrypts to backend servers. +- Add start_tls_http_backend test helper and update integration tests to run TLS-enabled backend servers validating re-encryption behavior. +- Make the selected upstream mutable to allow toggling the use_tls flag during request handling. + ## 2026-02-16 - 25.7.0 - feat(routes) add protocol-based route matching and ensure terminate-and-reencrypt routes HTTP through the full HTTP proxy; update docs and tests diff --git a/rust/crates/rustproxy-http/src/proxy_service.rs b/rust/crates/rustproxy-http/src/proxy_service.rs index 5cb6609..af0e956 100644 --- a/rust/crates/rustproxy-http/src/proxy_service.rs +++ b/rust/crates/rustproxy-http/src/proxy_service.rs @@ -344,7 +344,15 @@ impl HttpProxyService { } }; - let upstream = self.upstream_selector.select(target, &peer_addr, port); + let mut upstream = self.upstream_selector.select(target, &peer_addr, port); + + // If the route uses terminate-and-reencrypt, always re-encrypt to backend + if let Some(ref tls) = route_match.route.action.tls { + if tls.mode == rustproxy_config::TlsMode::TerminateAndReencrypt { + upstream.use_tls = true; + } + } + let upstream_key = format!("{}:{}", upstream.host, upstream.port); self.upstream_selector.connection_started(&upstream_key); diff --git a/rust/crates/rustproxy/tests/common/mod.rs b/rust/crates/rustproxy/tests/common/mod.rs index 80842ae..168cb00 100644 --- a/rust/crates/rustproxy/tests/common/mod.rs +++ b/rust/crates/rustproxy/tests/common/mod.rs @@ -185,6 +185,76 @@ pub async fn wait_for_port(port: u16, timeout_ms: u64) -> bool { false } +/// Start a TLS HTTP echo backend: accepts TLS, then responds with HTTP JSON +/// containing request details. Combines TLS acceptance with HTTP echo behavior. +pub async fn start_tls_http_backend( + port: u16, + backend_name: &str, + cert_pem: &str, + key_pem: &str, +) -> JoinHandle<()> { + use std::sync::Arc; + + let acceptor = rustproxy_passthrough::build_tls_acceptor(cert_pem, key_pem) + .expect("Failed to build TLS acceptor"); + let acceptor = Arc::new(acceptor); + let name = backend_name.to_string(); + + let listener = TcpListener::bind(format!("127.0.0.1:{}", port)) + .await + .unwrap_or_else(|_| panic!("Failed to bind TLS HTTP backend on port {}", port)); + + tokio::spawn(async move { + loop { + let (stream, _) = match listener.accept().await { + Ok(conn) => conn, + Err(_) => break, + }; + let acc = acceptor.clone(); + let backend = name.clone(); + tokio::spawn(async move { + let mut tls_stream = match acc.accept(stream).await { + Ok(s) => s, + Err(_) => return, + }; + + let mut buf = vec![0u8; 16384]; + let n = match tls_stream.read(&mut buf).await { + Ok(0) | Err(_) => return, + Ok(n) => n, + }; + let req_str = String::from_utf8_lossy(&buf[..n]); + + // Parse first line: METHOD PATH HTTP/x.x + let first_line = req_str.lines().next().unwrap_or(""); + let parts: Vec<&str> = first_line.split_whitespace().collect(); + let method = parts.first().copied().unwrap_or("UNKNOWN"); + let path = parts.get(1).copied().unwrap_or("/"); + + // Extract Host header + let host = req_str + .lines() + .find(|l| l.to_lowercase().starts_with("host:")) + .map(|l| l[5..].trim()) + .unwrap_or("unknown"); + + let body = format!( + r#"{{"method":"{}","path":"{}","host":"{}","backend":"{}"}}"#, + method, path, host, backend + ); + + let response = format!( + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", + body.len(), + body, + ); + let _ = tls_stream.write_all(response.as_bytes()).await; + let _ = tls_stream.shutdown().await; + }); + } + }) +} + /// Helper to create a minimal route config for testing. pub fn make_test_route( port: u16, diff --git a/rust/crates/rustproxy/tests/integration_http_proxy.rs b/rust/crates/rustproxy/tests/integration_http_proxy.rs index 03c2966..968dcd4 100644 --- a/rust/crates/rustproxy/tests/integration_http_proxy.rs +++ b/rust/crates/rustproxy/tests/integration_http_proxy.rs @@ -418,13 +418,17 @@ async fn test_terminate_and_reencrypt_http_routing() { let backend2_port = next_port(); let proxy_port = next_port(); - // Start plain HTTP echo backends (proxy terminates client TLS, connects plain to backend) - let _b1 = start_http_echo_backend(backend1_port, "alpha").await; - let _b2 = start_http_echo_backend(backend2_port, "beta").await; - let (cert1, key1) = generate_self_signed_cert("alpha.example.com"); let (cert2, key2) = generate_self_signed_cert("beta.example.com"); + // Generate separate backend certs (backends are independent TLS servers) + let (backend_cert1, backend_key1) = generate_self_signed_cert("localhost"); + let (backend_cert2, backend_key2) = generate_self_signed_cert("localhost"); + + // Start TLS HTTP echo backends (proxy re-encrypts to these) + let _b1 = start_tls_http_backend(backend1_port, "alpha", &backend_cert1, &backend_key1).await; + let _b2 = start_tls_http_backend(backend2_port, "beta", &backend_cert2, &backend_key2).await; + // Create terminate-and-reencrypt routes let mut route1 = make_tls_terminate_route( proxy_port, "alpha.example.com", "127.0.0.1", backend1_port, &cert1, &key1, diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 19a02d1..f96f589 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '25.7.0', + version: '25.7.1', description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' }