cloudflare/pingora

Question: Any production ready code for TLS certificate auto update to TlsSettings::intermediate?

Closed this issue · 5 comments

I would like to have production ready code for TLS certificate update? I currently use below code to add TLS certificate but it will expire every 60 days. Then I need to restart server in every 60 days. Any solution for this?

    let cert_path = format!("{}/certs/cert.pem", env!("CARGO_MANIFEST_DIR"));
    let key_path = format!("{}/certs/key.pem", env!("CARGO_MANIFEST_DIR"));

    let mut tls_settings = TlsSettings::intermediate(&cert_path, &key_path).unwrap();
    tls_settings.enable_h2(); // This work HTTP/2.0 and HTTP/1.1 (Check it)
    lb.add_tls_with_settings("0.0.0.0:6189", None, tls_settings);

I use the following logic to renew the certificate regularly.

#[async_trait]
impl pingora::listeners::TlsAccept for GlobalCertificate {
    async fn certificate_callback(&self, ssl: &mut SslRef) {
        // get certificate from ArcSwap<AHashMap<String, Arc<TlsCertificate>>>
    }
}

Use background service to regularly update ArcSwap.

@vicanso thank you for your code. is this full code correct method?

use std::collections::HashMap;
use std::sync::Arc;
use ahash::AHashMap;
use arc_swap::ArcSwap;
use pingora::listeners::{TlsAccept, TlsCertificate};
use openssl::ssl::SslRef;
use async_trait::async_trait;

pub struct GlobalCertificate {
    pub cert_map: Arc<ArcSwap<AHashMap<String, Arc<TlsCertificate>>>>,
}


#[async_trait]
impl TlsAccept for GlobalCertificate {
    async fn certificate_callback(&self, ssl: &mut SslRef) {
        if let Some(server_name) = ssl.servername(|_| true) {
            let certs = self.cert_map.load();
            if let Some(cert) = certs.get(server_name) {
                ssl.set_certificate(&cert.cert).ok();
                ssl.set_private_key(&cert.key).ok();
            }
        }
    }
}


async fn renew_certificates(cert_store: Arc<ArcSwap<AHashMap<String, Arc<TlsCertificate>>>>) {
    loop {
        // 1. Fetch or renew certificates using ACME or custom logic
        // 2. Parse and create new TlsCertificate
        // 3. Replace old map with updated one

        let mut new_map = AHashMap::new();
        let domains = vec!["example.com", "www.example.com"];

        for domain in domains {
            // Load from disk, generate or renew certificate here
            let cert = TlsCertificate::load_pem("cert.pem", "key.pem").unwrap();
            new_map.insert(domain.to_string(), Arc::new(cert));
        }

        // Swap in the new map atomically
        cert_store.store(Arc::new(new_map));

        // Sleep until next renewal (e.g., every 12h)
        tokio::time::sleep(std::time::Duration::from_secs(43200)).await;
    }
}


#[tokio::main]
async fn main() {
    let cert_store = Arc::new(ArcSwap::from_pointee(AHashMap::new()));

    // Start auto-renewal task
    let certs_clone = cert_store.clone();
    tokio::spawn(async move {
        renew_certificates(certs_clone).await;
    });

    let tls_acceptor = GlobalCertificate {
        cert_map: cert_store.clone(),
    };

    // Use `tls_acceptor` in your Pingora server setup
}

@vicanso How did you insert TLS certificate to Pingora server?

I use pingora background serivce, not tokio::span. https://github.com/cloudflare/pingora/blob/main/pingora-core/src/services/background.rs#L89.

And new tls settings from GlobalCertificate: https://github.com/vicanso/pingap/blob/main/src/proxy/server.rs#L397

This seems like a good feature, but out of scope for the pingora core project. I think we would have to appeal to the community to provide this feature - https://github.com/vicanso/pingap - Seems to be the place to look.