SSL cipher management

This commit is contained in:
Ara Sadoyan
2025-07-29 21:25:27 +02:00
parent b44f7069a0
commit f654312466
2 changed files with 113 additions and 146 deletions

View File

@@ -1,12 +1,12 @@
use dashmap::DashMap;
use log::{error, warn};
use pingora::tls::ssl::{select_next_proto, AlpnError, NameType, SniError, SslAlert, SslContext, SslFiletype, SslMethod, SslRef};
use log::{error, info, warn};
use pingora::tls::ssl::{select_next_proto, AlpnError, NameType, SniError, SslAlert, SslContext, SslFiletype, SslMethod, SslOptions, SslRef, SslVersion};
use pingora_core::listeners::tls::TlsSettings;
use rustls_pemfile::{read_one, Item};
use serde::Deserialize;
use std::collections::HashSet;
use std::fs::File;
use std::io::BufReader;
// use tokio::time::Instant;
use x509_parser::extensions::GeneralName;
use x509_parser::nom::Err as NomErr;
use x509_parser::prelude::*;
@@ -162,7 +162,7 @@ fn load_cert_info(cert_path: &str, key_path: &str, _grade: &str) -> Option<Certi
}
}
if let Ok(ssl_context) = create_ssl_context(cert_path, key_path, _grade) {
if let Ok(ssl_context) = create_ssl_context(cert_path, key_path) {
Some(CertificateInfo {
cert_path: cert_path.to_string(),
key_path: key_path.to_string(),
@@ -176,142 +176,107 @@ fn load_cert_info(cert_path: &str, key_path: &str, _grade: &str) -> Option<Certi
}
}
// fn create_ssl_context(cert_path: &str, key_path: &str) -> Result<SslContext, Box<dyn std::error::Error>> {
// let mut ctx = SslContext::builder(SslMethod::tls())?;
// ctx.set_certificate_chain_file(cert_path)?;
// ctx.set_private_key_file(key_path, SslFiletype::PEM)?;
// ctx.set_alpn_select_callback(prefer_h2);
// let built = ctx.build();
// Ok(built)
// }
struct TlsConfig {
options: pingora::tls::ssl::SslOptions,
ciphers: &'static str,
fn create_ssl_context(cert_path: &str, key_path: &str) -> Result<SslContext, Box<dyn std::error::Error>> {
let mut ctx = SslContext::builder(SslMethod::tls())?;
ctx.set_certificate_chain_file(cert_path)?;
ctx.set_private_key_file(key_path, SslFiletype::PEM)?;
ctx.set_alpn_select_callback(prefer_h2);
let built = ctx.build();
Ok(built)
}
enum TlsGrade {
APlus,
A,
B,
C,
Unsafe,
#[derive(Debug)]
pub struct CipherSuite {
pub ap: &'static str,
pub aa: &'static str,
pub bb: &'static str,
pub cc: &'static str,
pub ff: &'static str,
}
const CIPHERS: CipherSuite = CipherSuite {
ap: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305",
aa: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256",
bb: "ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:AES128-GCM-SHA256",
cc: "AES128-SHA:DES-CBC3-SHA",
ff: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
};
// const CIPHERS: CipherSuite = CipherSuite {
// ap: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256",
// aa: "TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256",
// bb: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256",
// cc: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256",
// ff: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
// };
#[derive(Debug)]
pub enum TlsGrade {
AP,
AA,
BB,
CC,
UN,
}
impl TlsGrade {
fn to_config(&self) -> TlsConfig {
match self {
// A+ (A+ on Qualys SSL Labs)
TlsGrade::APlus => TlsConfig {
options: pingora::tls::ssl::SslOptions::NO_SSL_MASK
| pingora::tls::ssl::SslOptions::NO_TLSV1
| pingora::tls::ssl::SslOptions::NO_TLSV1_1
| pingora::tls::ssl::SslOptions::NO_TLSV1_2,
ciphers: concat!(
// TLS 1.3 ciphers (in order of preference)
"TLS_AES_256_GCM_SHA384:",
"TLS_CHACHA20_POLY1305_SHA256:",
"TLS_AES_128_GCM_SHA256:",
// TLS 1.2 ciphers with PFS and AEAD
"ECDHE-ECDSA-AES256-GCM-SHA384:",
"ECDHE-RSA-AES256-GCM-SHA384:",
"ECDHE-ECDSA-CHACHA20-POLY1305:",
"ECDHE-RSA-CHACHA20-POLY1305:",
"ECDHE-ECDSA-AES128-GCM-SHA256:",
"ECDHE-RSA-AES128-GCM-SHA256"
),
},
// A (A on Qualys SSL Labs)
TlsGrade::A => TlsConfig {
options: pingora::tls::ssl::SslOptions::NO_SSL_MASK | pingora::tls::ssl::SslOptions::NO_TLSV1 | pingora::tls::ssl::SslOptions::NO_TLSV1_1,
ciphers: concat!(
// TLS 1.3 ciphers
"TLS_AES_256_GCM_SHA384:",
"TLS_CHACHA20_POLY1305_SHA256:",
"TLS_AES_128_GCM_SHA256:",
// TLS 1.2 ciphers
"ECDHE-ECDSA-AES256-GCM-SHA384:",
"ECDHE-RSA-AES256-GCM-SHA384:",
"ECDHE-ECDSA-CHACHA20-POLY1305:",
"ECDHE-RSA-CHACHA20-POLY1305:",
"ECDHE-ECDSA-AES128-GCM-SHA256:",
"ECDHE-RSA-AES128-GCM-SHA256:",
"DHE-RSA-AES256-GCM-SHA384:",
"DHE-RSA-AES128-GCM-SHA256"
),
},
// B (B on Qualys SSL Labs)
TlsGrade::B => TlsConfig {
options: pingora::tls::ssl::SslOptions::NO_SSL_MASK | pingora::tls::ssl::SslOptions::NO_TLSV1,
ciphers: concat!(
"ECDHE-ECDSA-AES256-GCM-SHA384:",
"ECDHE-RSA-AES256-GCM-SHA384:",
"ECDHE-ECDSA-AES128-GCM-SHA256:",
"ECDHE-RSA-AES128-GCM-SHA256:",
"DHE-RSA-AES256-GCM-SHA384:",
"DHE-RSA-AES128-GCM-SHA256:",
"ECDHE-ECDSA-AES256-SHA384:",
"ECDHE-RSA-AES256-SHA384:",
"ECDHE-ECDSA-AES128-SHA256:",
"ECDHE-RSA-AES128-SHA256"
),
},
// C (C on Qualys SSL Labs)
TlsGrade::C => TlsConfig {
options: pingora::tls::ssl::SslOptions::NO_SSL_MASK,
ciphers: concat!(
"ECDHE-ECDSA-AES256-GCM-SHA384:",
"ECDHE-RSA-AES256-GCM-SHA384:",
"ECDHE-ECDSA-AES128-GCM-SHA256:",
"ECDHE-RSA-AES128-GCM-SHA256:",
"DHE-RSA-AES256-GCM-SHA384:",
"DHE-RSA-AES128-GCM-SHA256:",
"ECDHE-ECDSA-AES256-SHA384:",
"ECDHE-RSA-AES256-SHA384:",
"ECDHE-ECDSA-AES128-SHA256:",
"ECDHE-RSA-AES128-SHA256:",
"AES256-GCM-SHA384:",
"AES128-GCM-SHA256:",
"AES256-SHA256:",
"AES128-SHA256"
),
},
// Unsafe (F on Qualys SSL Labs)
TlsGrade::Unsafe => TlsConfig {
options: pingora::tls::ssl::SslOptions::empty(),
ciphers: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
},
}
}
fn from_str(s: &str) -> Option<Self> {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_ascii_lowercase().as_str() {
"a+" => Some(TlsGrade::APlus),
"a" => Some(TlsGrade::A),
"b" => Some(TlsGrade::B),
"c" => Some(TlsGrade::C),
"unsafe" => Some(TlsGrade::Unsafe),
"a+" => Some(TlsGrade::AP),
"a" => Some(TlsGrade::AA),
"b" => Some(TlsGrade::BB),
"c" => Some(TlsGrade::CC),
"unsafe" => Some(TlsGrade::UN),
_ => None,
}
}
}
fn create_ssl_context(cert_path: &str, key_path: &str, grade: &str) -> Result<SslContext, Box<dyn std::error::Error>> {
let mut ctx = SslContext::builder(SslMethod::tls())?;
let config = TlsGrade::from_str(grade).map(|g| g.to_config()).unwrap_or_else(|| {
warn!("Invalid TLS grade '{}', defaulting to UNSAFE", grade);
TlsGrade::Unsafe.to_config()
});
ctx.set_options(config.options);
ctx.set_certificate_chain_file(cert_path)?;
ctx.set_private_key_file(key_path, SslFiletype::PEM)?;
ctx.set_cipher_list(config.ciphers)?;
ctx.set_alpn_select_callback(prefer_h2);
Ok(ctx.build())
}
pub fn prefer_h2<'a>(_ssl: &mut SslRef, alpn_in: &'a [u8]) -> Result<&'a [u8], AlpnError> {
match select_next_proto("\x02h2\x08http/1.1".as_bytes(), alpn_in) {
Some(p) => Ok(p),
_ => Err(AlpnError::NOACK),
}
}
pub fn set_tsl_grade(tls_settings: &mut TlsSettings, grade: &str) {
let config_grade = TlsGrade::from_str(grade);
match config_grade {
Some(TlsGrade::AP) => {
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1_2));
// let _ = tls_settings.set_max_proto_version(Some(SslVersion::TLS1_3));
let _ = tls_settings.set_cipher_list(CIPHERS.ap);
let _ = tls_settings.set_ciphersuites(CIPHERS.ap);
info!("TLS grade: {:?}, => AP", tls_settings.options());
}
Some(TlsGrade::AA) => {
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1_1));
let _ = tls_settings.set_cipher_list(CIPHERS.aa);
let _ = tls_settings.set_ciphersuites(CIPHERS.aa);
info!("TLS grade: {:?}, => AA", tls_settings.options());
}
Some(TlsGrade::BB) => {
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1));
let _ = tls_settings.set_cipher_list(CIPHERS.bb);
let _ = tls_settings.set_ciphersuites(CIPHERS.bb);
info!("TLS grade: {:?}, => BB", tls_settings.options());
}
Some(TlsGrade::CC) => {
let _ = tls_settings.set_min_proto_version(Some(SslVersion::SSL3));
let _ = tls_settings.set_cipher_list(CIPHERS.cc);
let _ = tls_settings.set_ciphersuites(CIPHERS.cc);
info!("TLS grade: {:?}, => CC", tls_settings.options());
}
Some(TlsGrade::UN) => {
let _ = tls_settings.set_min_proto_version(Some(SslVersion::SSL3));
let _ = tls_settings.set_cipher_list(CIPHERS.ff);
let _ = tls_settings.set_ciphersuites(CIPHERS.ff);
warn!("TLS grade: {:?}, => UNSAFE", tls_settings.options());
}
None => {
// Defaults to BB
let _ = tls_settings.set_min_proto_version(Some(SslVersion::TLS1));
let _ = tls_settings.set_cipher_list(CIPHERS.bb);
let _ = tls_settings.set_ciphersuites(CIPHERS.bb);
warn!("TLS grade is not detected defaulting top BB");
}
}
}

View File

@@ -46,24 +46,24 @@ pub fn run() {
headers: hh_config,
extraparams: ec_config,
};
// let log_level = cfg.log_level.clone();
// unsafe {
// match log_level.as_str() {
// "info" => env::set_var("RUST_LOG", "info"),
// "error" => env::set_var("RUST_LOG", "error"),
// "warn" => env::set_var("RUST_LOG", "warn"),
// "debug" => env::set_var("RUST_LOG", "debug"),
// "trace" => env::set_var("RUST_LOG", "trace"),
// "off" => env::set_var("RUST_LOG", "off"),
// _ => {
// println!("Error reading log level, defaulting to: INFO");
// env::set_var("RUST_LOG", "info")
// }
// }
// }
// env_logger::builder().init();
/*
let log_level = cfg.log_level.clone();
unsafe {
match log_level.as_str() {
"info" => env::set_var("RUST_LOG", "info"),
"error" => env::set_var("RUST_LOG", "error"),
"warn" => env::set_var("RUST_LOG", "warn"),
"debug" => env::set_var("RUST_LOG", "debug"),
"trace" => env::set_var("RUST_LOG", "trace"),
"off" => env::set_var("RUST_LOG", "off"),
_ => {
println!("Error reading log level, defaulting to: INFO");
env::set_var("RUST_LOG", "info")
}
}
}
env_logger::builder().init();
*/
let grade = cfg.proxy_tls_grade.clone().unwrap_or("b".to_string());
info!("TLS grade set to: {}", grade);
@@ -91,8 +91,10 @@ pub fn run() {
let mut tls_settings =
TlsSettings::intermediate(&certs_for_callback.load().default_cert_path, &certs_for_callback.load().default_key_path).expect("unable to load or parse cert/key");
tls::set_tsl_grade(&mut tls_settings, grade.as_str());
tls_settings.set_servername_callback(move |ssl_ref: &mut SslRef, ssl_alert: &mut SslAlert| certs_for_callback.load().server_name_callback(ssl_ref, ssl_alert));
tls_settings.set_alpn_select_callback(tls::prefer_h2);
proxy.add_tls_with_settings(&bind_address_tls, None, tls_settings);
let certs_for_watcher = certificates.clone();