use crate::config::models::JokowayConfig;
use crate::error::JokowayError;
use crate::prelude::{core::*, *};
use crate::server::context::Context;
use crate::server::proxy::JokowayProxy;
use crate::server::router::{HTTPS_PROTOCOLS, Router};
use crate::server::service::ServiceManager;
use crate::server::upstream::UpstreamManager;
use boring::pkey::PKey;
use boring::ssl::{AlpnError, SslAcceptor, SslMethod, SslVerifyMode};
use boring::x509::X509;
use jokoway_core::tls::{AlpnProtocol, contains_alpn_protocol};
use pingora::listeners::tls::TlsSettings;
use pingora::proxy::ProxyServiceBuilder;
use std::fs;
use std::path::Path;
use std::sync::Arc;
pub struct HttpsExtension;
impl HttpsExtension {
fn load_pem_pair_from_files(
cert_path: &str,
key_path: &str,
) -> Result<(X509, PKey<boring::pkey::Private>), Box<dyn std::error::Error>> {
let cert_pem = fs::read(cert_path)?;
let key_pem = fs::read(key_path)?;
let cert = X509::from_pem(&cert_pem)?;
let key = PKey::private_key_from_pem(&key_pem)?;
Ok((cert, key))
}
fn self_signed_pair(
tls: Option<&crate::config::models::TlsSettings>,
) -> Option<(X509, PKey<boring::pkey::Private>)> {
let subject_alt_names = tls
.and_then(|t| t.sans.clone())
.filter(|sans| !sans.is_empty())
.unwrap_or_else(|| vec!["localhost".to_string(), "127.0.0.1".to_string()]);
let mut params = rcgen::CertificateParams::new(subject_alt_names).ok()?;
params.distinguished_name.remove(rcgen::DnType::CommonName);
params
.distinguished_name
.push(rcgen::DnType::CommonName, "Jokoway Gateway");
let key_pair = rcgen::KeyPair::generate().ok()?;
let cert = params.self_signed(&key_pair).ok()?;
let priv_key_pem = key_pair.serialize_pem();
let cert_pem = cert.pem();
let key = PKey::private_key_from_pem(priv_key_pem.as_bytes()).ok()?;
let cert = X509::from_pem(cert_pem.as_bytes()).ok()?;
Some((cert, key))
}
}
impl JokowayExtension for HttpsExtension {
fn order(&self) -> i16 {
-100
}
fn init(
&self,
server: &mut pingora::server::Server,
app_ctx: &mut AppContext,
middlewares: &mut Vec<std::sync::Arc<dyn JokowayMiddlewareDyn>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let config = app_ctx.get::<JokowayConfig>().ok_or_else(|| {
JokowayError::Config("JokowayConfig not found in Context".to_string())
})?;
if config.https_listen.is_none() {
return Ok(());
}
let upstream_manager = app_ctx.get::<UpstreamManager>().ok_or_else(|| {
JokowayError::Config("UpstreamManager not found in Context".to_string())
})?;
let service_manager = app_ctx.get::<ServiceManager>().ok_or_else(|| {
JokowayError::Config("ServiceManager not found in Context".to_string())
})?;
let router = Router::new(service_manager, upstream_manager.clone(), &HTTPS_PROTOCOLS);
let proxy =
JokowayProxy::new(router, Arc::new(app_ctx.clone()), middlewares.clone(), true)?;
let mut ssl_acceptor = match SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()) {
Ok(acceptor) => acceptor,
Err(e) => {
log::error!("Failed to create SSL acceptor: {}", e);
return Err(Box::new(JokowayError::Tls(format!(
"Failed to create SSL acceptor: {}",
e
))));
}
};
let tls_cfg = config.tls.as_ref();
let cert_paths = match tls_cfg {
Some(tls) => match (&tls.server_cert, &tls.server_key) {
(Some(cert), Some(key)) => {
if !Path::new(cert).exists() {
return Err(Box::new(JokowayError::Tls(format!(
"Certificate file not found: {}",
cert
))));
}
if !Path::new(key).exists() {
return Err(Box::new(JokowayError::Tls(format!(
"Private key file not found: {}",
key
))));
}
Some((cert, key))
}
(Some(_), None) | (None, Some(_)) => {
log::error!("Both server_cert and server_key must be specified together");
return Err(Box::new(JokowayError::Tls(
"Both server_cert and server_key must be specified together".to_string(),
)));
}
_ => None,
},
None => None,
};
let base_pair = if let Some((cert_path, key_path)) = cert_paths {
match Self::load_pem_pair_from_files(cert_path, key_path) {
Ok(pair) => Some(pair),
Err(e) => {
log::error!("Failed to load TLS certs from config: {}", e);
return Err(Box::new(JokowayError::Tls(format!(
"Failed to load TLS certs from config: {}",
e
))));
}
}
} else {
None
};
let fallback_pair = match base_pair.clone() {
Some(pair) => Some(pair),
None => {
let generated = Self::self_signed_pair(tls_cfg);
if generated.is_some() {
log::debug!("Using self-signed certificate fallback");
}
generated
}
}
.ok_or_else(|| {
Box::new(JokowayError::Tls(
"Failed to generate self-signed certificate".to_string(),
)) as Box<dyn std::error::Error + Send + Sync>
})?;
if let Err(e) = ssl_acceptor.set_private_key(&fallback_pair.1) {
log::error!("Failed to set fallback private key: {}", e);
return Err(Box::new(JokowayError::Tls(format!(
"Failed to set fallback private key: {}",
e
))));
}
if let Err(e) = ssl_acceptor.set_certificate(&fallback_pair.0) {
log::error!("Failed to set fallback certificate: {}", e);
return Err(Box::new(JokowayError::Tls(format!(
"Failed to set fallback certificate: {}",
e
))));
}
let tls_callback = app_ctx.get::<TlsCallback>();
let fallback_pair_for_sni = Some(fallback_pair.clone());
let tls_cb_sni = tls_callback.clone();
ssl_acceptor.set_servername_callback(move |ssl_ref, alert| {
if let Some(c) = tls_cb_sni.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
match handler.servername_callback(ssl_ref, alert) {
Ok(()) => return Ok(()),
Err(_e) => {}
}
}
}
if let Some((cert, key)) = fallback_pair_for_sni.as_ref() {
let _ = ssl_ref.set_certificate(cert);
let _ = ssl_ref.set_private_key(key);
}
Ok(())
});
let tls_cb_alpn = tls_callback.clone();
ssl_acceptor.set_alpn_select_callback(move |ssl_ref, client_protos| {
if let Some(c) = tls_cb_alpn.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
match handler.alpn_select_callback(ssl_ref, client_protos) {
Ok(idx) => return Ok(idx),
Err(AlpnError::NOACK) => {}
Err(e) => return Err(e),
}
}
}
if contains_alpn_protocol(client_protos, AlpnProtocol::H2) {
Ok(AlpnProtocol::H2.as_bytes())
} else {
Ok(AlpnProtocol::H1.as_bytes())
}
});
let tls_cb_cert = tls_callback.clone();
ssl_acceptor.set_select_certificate_callback(move |client_hello| {
if let Some(c) = tls_cb_cert.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
return handler.select_certificate_callback(client_hello);
}
}
Ok(())
});
let tls_cb_verify = tls_callback.clone();
let verify_mode = if let Some(ca_path) = tls_cfg.and_then(|t| t.cacert.as_ref()) {
if !Path::new(ca_path).exists() {
return Err(Box::new(JokowayError::Tls(format!(
"CA certificate file not found: {}",
ca_path
))));
}
if let Err(e) = ssl_acceptor.set_ca_file(ca_path) {
return Err(Box::new(JokowayError::Tls(format!(
"Failed to set CA file for client auth: {}",
e
))));
}
SslVerifyMode::PEER | SslVerifyMode::FAIL_IF_NO_PEER_CERT
} else {
SslVerifyMode::NONE
};
ssl_acceptor.set_verify_callback(verify_mode, move |preverify, x509_ctx| {
if let Some(c) = tls_cb_verify.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
return handler.verify_callback(preverify, x509_ctx);
}
}
preverify
});
let tls_cb_sess_new = tls_callback.clone();
ssl_acceptor.set_new_session_callback(move |ssl, session| {
if let Some(c) = tls_cb_sess_new.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
handler.new_session_callback(ssl, session);
}
}
});
let tls_cb_sess_remove = tls_callback.clone();
ssl_acceptor.set_remove_session_callback(move |ctx, session| {
if let Some(c) = tls_cb_sess_remove.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
handler.remove_session_callback(ctx, session);
}
}
});
unsafe {
let tls_cb_sess_get = tls_callback.clone();
ssl_acceptor.set_get_session_callback(move |ssl, id| {
if let Some(c) = tls_cb_sess_get.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
return handler.get_session_callback(ssl, id);
}
}
Ok(None)
});
}
let tls_cb_psk = tls_callback.clone();
ssl_acceptor.set_psk_server_callback(move |ssl, id, psk| {
if let Some(c) = tls_cb_psk.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
return handler.psk_server_callback(ssl, id, psk);
}
}
Ok(0)
});
let tls_cb_status = tls_callback.clone();
ssl_acceptor
.set_status_callback(move |ssl| {
if let Some(c) = tls_cb_status.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
return handler.status_callback(ssl);
}
}
Ok(true)
})
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send + Sync>)?;
let tls_cb_keylog = tls_callback.clone();
ssl_acceptor.set_keylog_callback(move |ssl, line| {
if let Some(c) = tls_cb_keylog.as_ref() {
let h_arc = c.get_handler();
if let Some(handler) = h_arc.as_ref() {
handler.keylog_callback(ssl, line);
}
}
});
if let Some(ciphers) = tls_cfg.and_then(|t| t.cipher_suites.as_ref()) {
let ciphers_str = ciphers.join(":");
if let Err(e) = ssl_acceptor.set_cipher_list(&ciphers_str) {
log::error!("Failed to set cipher suites: {}", e);
return Err(Box::new(JokowayError::Tls(format!(
"Failed to set cipher suites: {}",
e
))));
}
}
let tls_settings = TlsSettings::from(ssl_acceptor);
let mut builder = ProxyServiceBuilder::new(&server.configuration, proxy.clone())
.name("Jokoway HTTPS Proxy Service");
if let Some(opts) = &config.http_server_options {
let mut server_options = pingora::apps::HttpServerOptions::default();
server_options.keepalive_request_limit = opts.keepalive_request_limit;
server_options.h2c = opts.h2c;
server_options.allow_connect_method_proxying = opts.allow_connect_method_proxying;
builder = builder.server_options(server_options);
}
let mut https_service = builder.build();
https_service.add_tls_with_settings(
config.https_listen.as_ref().unwrap(),
None,
tls_settings,
);
server.add_service(https_service);
log::info!(
"HTTPS proxy listening on {}",
config.https_listen.as_ref().unwrap()
);
Ok(())
}
}