TLS Certificate Renewal
Certificate Issuance Flow
The following happens when a certificate is issued with Let's Encrypt:
- Generate a new private key (usually)
- Create a new ACME order
- Prove domain control (challenge)
- Finalize the order with a CSR
- Download the new certificate
The CertManager is responsible for this.
An implementation of the CertStore trait determines where certificates are stored.
TLS Automation
When tls_automation is configured in snakeway.hcl, Snakeway starts a background CertManager task.
On startup (and periodically thereafter), the CertManager checks each ACME-managed domain. If a certificate is
missing or due to expire within renew_within_days, it initiates the ACME renewal flow described above.
The renewed certificate is stored and immediately made available to new TLS handshakes without requiring a server restart or reload.
SNI
The SNI handling is a bit tricky.
Pingora has a mechanism, via its TlsAccept trait, for hooking into the TLS handshake.
Snakeway uses this feature to achieve two things:
- Load ACME-generated certs based on SNI.
- Make the SNI available to the request pipeline via Pingora's
SslDigest.
impl TlsAccept for SnakewayTlsAccept {
async fn certificate_callback(&self, ssl: &mut TlsRef) {
match &self.cert_mode {
CertMode::Manual => {
// Do nothing. Cert already configured in settings file.
}
CertMode::Acme(state) => {
// Perform dynamic lookup and install cert based on SNI
acme_lookup_and_set_cert(state, ssl).await
}
}
}
async fn handshake_complete_callback(
&self,
ssl: &TlsRef,
) -> Option<Arc<dyn Any + Send + Sync>> {
// Extract SNI.
let hostname = extract_sni(ssl).filter(|s| !s.is_empty())?;
let hostname = Arc::new(DownstreamSni(hostname.clone()));
Some(hostname)
}
}
The tricky part is extracting the SNI in the HttpProxy implementation.
let maybe_sni = & ssl_digest.extension.get::<DownstreamSni>();
This is an important note because this data crosses the boundary from L4 to L7 automagically via Pingora.