From 7b2ccbdd11f4b1cce662e31ff994222d1dc26458 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 16 Feb 2026 01:37:43 +0000 Subject: [PATCH] feat(rustproxy): support dynamically loaded TLS certificates via loadCertificate IPC and include them in listener TLS configs for rebuilds and hot-swap --- changelog.md | 7 +++++++ rust/crates/rustproxy/src/lib.rs | 29 +++++++++++++++++++++++++++++ ts/00_commitinfo_data.ts | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 5c4ad00..cb1b210 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-02-16 - 25.4.0 - feat(rustproxy) +support dynamically loaded TLS certificates via loadCertificate IPC and include them in listener TLS configs for rebuilds and hot-swap + +- Adds loaded_certs: HashMap to RustProxy to store certificates loaded at runtime +- Merge loaded_certs into tls_configs in rebuild and listener hot-swap paths so dynamically loaded certs are served immediately +- Persist loaded certificates on loadCertificate so future rebuilds include them + ## 2026-02-15 - 25.3.1 - fix(plugins) remove unused dependencies and simplify plugin exports diff --git a/rust/crates/rustproxy/src/lib.rs b/rust/crates/rustproxy/src/lib.rs index 7713610..75a04c6 100644 --- a/rust/crates/rustproxy/src/lib.rs +++ b/rust/crates/rustproxy/src/lib.rs @@ -77,6 +77,8 @@ pub struct RustProxy { started_at: Option, /// Shared path to a Unix domain socket for relaying socket-handler connections back to TypeScript. socket_handler_relay: Arc>>, + /// Dynamically loaded certificates (via loadCertificate IPC), independent of CertManager. + loaded_certs: HashMap, } impl RustProxy { @@ -118,6 +120,7 @@ impl RustProxy { started: false, started_at: None, socket_handler_relay: Arc::new(std::sync::RwLock::new(None)), + loaded_certs: HashMap::new(), }) } @@ -268,6 +271,13 @@ impl RustProxy { } } + // Merge dynamically loaded certs (from loadCertificate IPC) + for (d, c) in &self.loaded_certs { + if !tls_configs.contains_key(d) { + tls_configs.insert(d.clone(), c.clone()); + } + } + if !tls_configs.is_empty() { debug!("Loaded TLS certificates for {} domains", tls_configs.len()); listener.set_tls_configs(tls_configs); @@ -576,6 +586,12 @@ impl RustProxy { } } } + // Merge dynamically loaded certs (from loadCertificate IPC) + for (d, c) in &self.loaded_certs { + if !tls_configs.contains_key(d) { + tls_configs.insert(d.clone(), c.clone()); + } + } listener.set_tls_configs(tls_configs); // Add new ports @@ -786,6 +802,12 @@ impl RustProxy { cm.load_static(domain.to_string(), bundle); } + // Persist in loaded_certs so future rebuild calls include this cert + self.loaded_certs.insert(domain.to_string(), TlsCertConfig { + cert_pem: cert_pem.clone(), + key_pem: key_pem.clone(), + }); + // Hot-swap TLS config on the listener if let Some(ref mut listener) = self.listener_manager { let mut tls_configs = Self::extract_tls_configs(&self.options.routes); @@ -809,6 +831,13 @@ impl RustProxy { } } + // Merge dynamically loaded certs from previous loadCertificate calls + for (d, c) in &self.loaded_certs { + if !tls_configs.contains_key(d) { + tls_configs.insert(d.clone(), c.clone()); + } + } + listener.set_tls_configs(tls_configs); } diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index e115df4..e8fe426 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.3.1', + version: '25.4.0', 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.' }