Skip to content

Conversation

@podpress
Copy link

@podpress podpress commented Nov 6, 2025

This PR adds the ability to provide a custom rustls::ServerConfig to TlsSettings, enabling advanced RustTLS features such as:

  1. 0-RTT (TLS 1.3 early data)
  2. Custom session resumption settings
  3. Dynamic certificate resolution via ResolvesServerCert
  4. Fine-grained control over TLS parameters

Changes:

  1. Added custom_config: Option<Arc> field to TlsSettings
  2. Modified build() to use custom_config if provided, falling back to file-based cert loading
  3. Added with_server_config() constructor method

Create a new TlsSettings with a custom ServerConfig:

This allows for full control over the RustTLS ServerConfig, including 0-RTT, session resumption, and custom certificate resolvers.
@podpress podpress changed the title Update mod.rs Update mod.rs to add support for custom ServerConfig in RustTLS TlsSettings Nov 6, 2025
@podpress
Copy link
Author

podpress commented Nov 6, 2025

// Copyright 2025 Cloudflare, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::sync::Arc;

use crate::listeners::TlsAcceptCallbacks;
use crate::protocols::tls::{server::handshake, server::handshake_with_callback, TlsStream};
use log::debug;
use pingora_error::ErrorType::InternalError;
use pingora_error::{Error, OrErr, Result};
use pingora_rustls::load_certs_and_key_files;
use pingora_rustls::ServerConfig;
use pingora_rustls::{version, TlsAcceptor as RusTlsAcceptor};

use crate::protocols::{ALPN, IO};

/// The TLS settings of a listening endpoint
pub struct TlsSettings {
    alpn_protocols: Option<Vec<Vec<u8>>>,
    cert_path: String,
    key_path: String,
    custom_config: Option<Arc<ServerConfig>>,
}

pub struct Acceptor {
    pub acceptor: RusTlsAcceptor,
    callbacks: Option<TlsAcceptCallbacks>,
}

impl TlsSettings {
    /// Create a Rustls acceptor based on the current setting for certificates,
    /// keys, and protocols.
    ///
    /// _NOTE_ This function will panic if there is an error in loading
    /// certificate files or constructing the builder
    ///
    /// Todo: Return a result instead of panicking XD
    pub fn build(self) -> Acceptor {
        // Use custom config if provided, otherwise load from cert/key files
        let config = if let Some(custom_config) = self.custom_config {
            custom_config
        } else {
            let Ok(Some((certs, key))) = load_certs_and_key_files(&self.cert_path, &self.key_path)
            else {
                panic!(
                    "Failed to load provided certificates \"{}\" or key \"{}\".",
                    self.cert_path, self.key_path
                )
            };

            // TODO - Add support for client auth & custom CA support
            let mut config =
                ServerConfig::builder_with_protocol_versions(&[&version::TLS12, &version::TLS13])
                    .with_no_client_auth()
                    .with_single_cert(certs, key)
                    .explain_err(InternalError, |e| {
                        format!("Failed to create server listener config: {e}")
                    })
                    .unwrap();

            if let Some(alpn_protocols) = self.alpn_protocols {
                config.alpn_protocols = alpn_protocols;
            }

            Arc::new(config)
        };

        Acceptor {
            acceptor: RusTlsAcceptor::from(config),
            callbacks: None,
        }
    }

    /// Enable HTTP/2 support for this endpoint, which is default off.
    /// This effectively sets the ALPN to prefer HTTP/2 with HTTP/1.1 allowed
    pub fn enable_h2(&mut self) {
        self.set_alpn(ALPN::H2H1);
    }

    fn set_alpn(&mut self, alpn: ALPN) {
        self.alpn_protocols = Some(alpn.to_wire_protocols());
    }

    pub fn intermediate(cert_path: &str, key_path: &str) -> Result<Self>
    where
        Self: Sized,
    {
        Ok(TlsSettings {
            alpn_protocols: None,
            cert_path: cert_path.to_string(),
            key_path: key_path.to_string(),
            custom_config: None,
        })
    }

    /// Create a new TlsSettings with a custom ServerConfig
    ///
    /// This allows for full control over the RustTLS ServerConfig,
    /// including 0-RTT, session resumption, and custom certificate resolvers.
    pub fn with_server_config(config: Arc<ServerConfig>) -> Result<Self>
    where
        Self: Sized,
    {
        Ok(TlsSettings {
            alpn_protocols: None,
            cert_path: String::new(),
            key_path: String::new(),
            custom_config: Some(config),
        })
    }

    pub fn with_callbacks() -> Result<Self>
    where
        Self: Sized,
    {
        // TODO: verify if/how callback in handshake can be done using Rustls
        Error::e_explain(
            InternalError,
            "Certificate callbacks are not supported with feature \"rustls\".",
        )
    }
}

impl Acceptor {
    pub async fn tls_handshake<S: IO>(&self, stream: S) -> Result<TlsStream<S>> {
        debug!("new tls session");
        // TODO: be able to offload this handshake in a thread pool
        if let Some(cb) = self.callbacks.as_ref() {
            handshake_with_callback(self, stream, cb).await
        } else {
            handshake(self, stream).await
        }
    }
}

Not sure why half the PR is there, sorry, full file attatched.

@duke8253 duke8253 added the enhancement New feature or request label Nov 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants