From d2ae2ca206723530fa3f530d7a32cc028cb3ff33 Mon Sep 17 00:00:00 2001 From: Firstyear Date: Fri, 25 Oct 2024 10:01:30 +1000 Subject: [PATCH] 20241024 1271 cert reload on SIGHUP (#3140) reload certificates and keys on SIGHUP --- examples/server.toml | 4 +- server/core/src/crypto.rs | 212 ++++++++++++++++++++------- server/core/src/https/mod.rs | 277 +++++++++++------------------------ server/core/src/ldaps.rs | 19 ++- server/core/src/lib.rs | 86 +++++++++-- server/daemon/src/main.rs | 5 +- 6 files changed, 339 insertions(+), 264 deletions(-) diff --git a/examples/server.toml b/examples/server.toml index 3ad823384..71f9530c2 100644 --- a/examples/server.toml +++ b/examples/server.toml @@ -44,7 +44,9 @@ db_path = "/var/lib/private/kanidm/kanidm.db" # memory pressure on your system. # db_arc_size = 2048 # -# TLS chain and key in pem format. Both must be present +# TLS chain and key in pem format. Both must be present. +# If the server recieves a SIGHUP, these files will be +# re-read and reloaded if their content is valid. tls_chain = "/var/lib/private/kanidm/chain.pem" tls_key = "/var/lib/private/kanidm/key.pem" # diff --git a/server/core/src/crypto.rs b/server/core/src/crypto.rs index af823113b..d599c7083 100644 --- a/server/core/src/crypto.rs +++ b/server/core/src/crypto.rs @@ -6,7 +6,7 @@ use openssl::error::ErrorStack; use openssl::nid::Nid; use openssl::pkey::{PKeyRef, Private}; use openssl::rsa::Rsa; -use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; +use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslSessionCacheMode, SslVerifyMode}; use openssl::x509::{ extension::{ AuthorityKeyIdentifier, BasicConstraints, ExtendedKeyUsage, KeyUsage, @@ -17,10 +17,10 @@ use openssl::x509::{ use openssl::{asn1, bn, hash, pkey}; use sketching::*; -use crate::config::Configuration; +use crate::config::TlsConfiguration; -use std::fs::File; -use std::io::{Read, Write}; +use std::fs; +use std::io::{ErrorKind, Read, Write}; use std::path::Path; const CA_VALID_DAYS: u32 = 30; @@ -93,55 +93,163 @@ pub fn check_privkey_minimums(privkey: &PKeyRef) -> Result<(), String> /// From the server configuration, generate an OpenSSL acceptor that we can use /// to build our sockets for HTTPS/LDAPS. -pub fn setup_tls(config: &Configuration) -> Result, ()> { - match &config.tls_config { - Some(tls_config) => { - // Signing algorithm minimums are enforced by the SSLAcceptor - it won't start up with a sha1-signed cert. - // https://wiki.mozilla.org/Security/Server_Side_TLS - let mut ssl_builder = - SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).map_err(|openssl_err| { - error!("Failed to start TLS builder"); - error!(?openssl_err); +pub fn setup_tls( + tls_config: &Option, +) -> Result, std::io::Error> { + let Some(tls_param) = tls_config.as_ref() else { + return Ok(None); + }; + + let mut tls_builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?; + + tls_builder + .set_certificate_chain_file(tls_param.chain.clone()) + .map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Failed to create TLS listener: {:?}", err), + ) + })?; + + tls_builder + .set_private_key_file(tls_param.key.clone(), SslFiletype::PEM) + .map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Failed to create TLS listener: {:?}", err), + ) + })?; + + tls_builder.check_private_key().map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Failed to create TLS listener: {:?}", err), + ) + })?; + + // If configured, setup TLS client authentication. + if let Some(client_ca) = tls_param.client_ca.as_ref() { + info!("Loading client certificates from {}", client_ca.display()); + + let verify = SslVerifyMode::PEER; + // In future we may add a "require mTLS option" which would necessitate this. + // verify.insert(SslVerifyMode::FAIL_IF_NO_PEER_CERT); + tls_builder.set_verify(verify); + + // When client certs are available, we disable the TLS session cache. + // This is so that when the smartcard is *removed* on the client, it forces + // the client session to immediately expire. + // + // https://stackoverflow.com/questions/12393711/session-disconnect-the-client-after-smart-card-is-removed + // + // Alternately, on logout we need to trigger https://docs.rs/openssl/latest/openssl/ssl/struct.Ssl.html#method.set_ssl_context + // with https://docs.rs/openssl/latest/openssl/ssl/struct.Ssl.html#method.ssl_context + + // https://docs.rs/openssl/latest/openssl/ssl/struct.SslContextRef.html#method.remove_session + // + // Or we lower session time outs etc. + tls_builder.set_session_cache_mode(SslSessionCacheMode::OFF); + + let read_dir = fs::read_dir(client_ca).map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!( + "Failed to create TLS listener while loading client ca from {}: {:?}", + client_ca.display(), + err + ), + ) + })?; + + for cert_dir_ent in read_dir.filter_map(|item| item.ok()).filter(|item| { + item.file_name() + .to_str() + // Hashed certs end in .0 + // Hashed crls are .r0 + .map(|fname| fname.ends_with(".0")) + .unwrap_or_default() + }) { + let mut cert_pem = String::new(); + fs::File::open(cert_dir_ent.path()) + .and_then(|mut file| file.read_to_string(&mut cert_pem)) + .map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Failed to create TLS listener: {:?}", err), + ) })?; - ssl_builder - .set_certificate_chain_file(&tls_config.chain) - .map_err(|openssl_err| { - error!("Failed to access certificate chain file"); - error!(?openssl_err); - let diag = kanidm_lib_file_permissions::diagnose_path(&tls_config.chain); - info!(%diag); - })?; - - ssl_builder - .set_private_key_file(&tls_config.key, SslFiletype::PEM) - .map_err(|openssl_err| { - error!("Failed to access private key file"); - error!(?openssl_err); - let diag = kanidm_lib_file_permissions::diagnose_path(&tls_config.chain); - info!(%diag); - })?; - - ssl_builder.check_private_key().map_err(|openssl_err| { - error!("Failed to validate private key"); - error!(?openssl_err); + let cert = X509::from_pem(cert_pem.as_bytes()).map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Failed to create TLS listener: {:?}", err), + ) })?; - let acceptor = ssl_builder.build(); - - // let's enforce some TLS minimums! - let privkey = acceptor.context().private_key().ok_or_else(|| { - error!("Failed to access acceptor private key"); + let cert_store = tls_builder.cert_store_mut(); + cert_store.add_cert(cert.clone()).map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!( + "Failed to load cert store while creating TLS listener: {:?}", + err + ), + ) })?; - - check_privkey_minimums(privkey).map_err(|err| { - error!("{}", err); + // This tells the client what CA's they should use. It DOES NOT + // verify them. That's the job of the cert store above! + tls_builder.add_client_ca(&cert).map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Failed to create TLS listener: {:?}", err), + ) })?; - - Ok(Some(acceptor)) } - None => Ok(None), + + // TODO: Build our own CRL map HERE! + + // Allow dumping client cert chains for dev debugging + // In the case this is status=false, should we be dumping these anyway? + if enabled!(tracing::Level::TRACE) { + tls_builder.set_verify_callback(verify, |status, x509store| { + if let Some(current_cert) = x509store.current_cert() { + let cert_text_bytes = current_cert.to_text().unwrap_or_default(); + let cert_text = String::from_utf8_lossy(cert_text_bytes.as_slice()); + tracing::warn!(client_cert = %cert_text); + }; + + if let Some(chain) = x509store.chain() { + for cert in chain.iter() { + let cert_text_bytes = cert.to_text().unwrap_or_default(); + let cert_text = String::from_utf8_lossy(cert_text_bytes.as_slice()); + tracing::warn!(chain_cert = %cert_text); + } + } + + status + }); + } + + // End tls_client setup } + + let tls_acceptor = tls_builder.build(); + + // let's enforce some TLS minimums! + let privkey = tls_acceptor.context().private_key().ok_or_else(|| { + std::io::Error::new( + ErrorKind::Other, + "Failed to access tls_acceptor private key".to_string(), + ) + })?; + + check_privkey_minimums(privkey).map_err(|err| { + std::io::Error::new( + ErrorKind::Other, + format!("Private key minimums were not met: {:?}", err), + ) + })?; + + Ok(Some(tls_acceptor)) } fn get_ec_group() -> Result { @@ -170,13 +278,13 @@ pub(crate) fn write_ca( error!(err = ?e, "Failed to convert cert to PEM"); })?; - File::create(key_path) + fs::File::create(key_path) .and_then(|mut file| file.write_all(&key_pem)) .map_err(|e| { error!(err = ?e, "Failed to create {:?}", key_path); })?; - File::create(cert_path) + fs::File::create(cert_path) .and_then(|mut file| file.write_all(&cert_pem)) .map_err(|e| { error!(err = ?e, "Failed to create {:?}", cert_path); @@ -341,14 +449,14 @@ pub(crate) fn load_ca( let ca_cert_path: &Path = ca_cert_ar.as_ref(); let mut ca_key_pem = vec![]; - File::open(ca_key_path) + fs::File::open(ca_key_path) .and_then(|mut file| file.read_to_end(&mut ca_key_pem)) .map_err(|e| { error!(err = ?e, "Failed to read {:?}", ca_key_path); })?; let mut ca_cert_pem = vec![]; - File::open(ca_cert_path) + fs::File::open(ca_cert_path) .and_then(|mut file| file.read_to_end(&mut ca_cert_pem)) .map_err(|e| { error!(err = ?e, "Failed to read {:?}", ca_cert_path); @@ -413,19 +521,19 @@ pub(crate) fn write_cert( } } - File::create(key_path) + fs::File::create(key_path) .and_then(|mut file| file.write_all(&key_pem)) .map_err(|e| { error!(err = ?e, "Failed to create {:?}", key_path); })?; - File::create(chain_path) + fs::File::create(chain_path) .and_then(|mut file| file.write_all(&chain_pem)) .map_err(|e| { error!(err = ?e, "Failed to create {:?}", chain_path); })?; - File::create(cert_path) + fs::File::create(cert_path) .and_then(|mut file| file.write_all(&cert_pem)) .map_err(|e| { error!(err = ?e, "Failed to create {:?}", cert_path); diff --git a/server/core/src/https/mod.rs b/server/core/src/https/mod.rs index 80d9c366d..05da14693 100644 --- a/server/core/src/https/mod.rs +++ b/server/core/src/https/mod.rs @@ -19,7 +19,7 @@ mod views; use self::extractors::ClientConnInfo; use self::javascript::*; use crate::actors::{QueryServerReadV1, QueryServerWriteV1}; -use crate::config::{Configuration, ServerRole, TlsConfiguration}; +use crate::config::{Configuration, ServerRole}; use crate::CoreAction; use axum::{ @@ -40,8 +40,7 @@ use hyper::body::Incoming; use hyper_util::rt::{TokioExecutor, TokioIo}; use kanidm_proto::{constants::KSESSIONID, internal::COOKIE_AUTH_SESSION_ID}; use kanidmd_lib::{idm::ClientCertInfo, status::StatusActor}; -use openssl::ssl::{Ssl, SslAcceptor, SslFiletype, SslMethod, SslSessionCacheMode, SslVerifyMode}; -use openssl::x509::X509; +use openssl::ssl::{Ssl, SslAcceptor}; use kanidm_lib_crypto::x509_cert::{der::Decode, x509_public_key_s256, Certificate}; @@ -51,6 +50,7 @@ use std::fmt::Write; use tokio::{ net::{TcpListener, TcpStream}, sync::broadcast, + sync::mpsc, task, }; use tokio_openssl::SslStream; @@ -58,8 +58,7 @@ use tower::Service; use tower_http::{services::ServeDir, trace::TraceLayer}; use uuid::Uuid; -use std::fs; -use std::io::{ErrorKind, Read}; +use std::io::ErrorKind; use std::path::PathBuf; use std::pin::Pin; use std::{net::SocketAddr, str::FromStr}; @@ -209,9 +208,12 @@ pub async fn create_https_server( status_ref: &'static StatusActor, qe_w_ref: &'static QueryServerWriteV1, qe_r_ref: &'static QueryServerReadV1, - mut rx: broadcast::Receiver, server_message_tx: broadcast::Sender, + maybe_tls_acceptor: Option, + tls_acceptor_reload_rx: mpsc::Receiver, ) -> Result, ()> { + let rx = server_message_tx.subscribe(); + let js_files = get_js_files(config.role)?; // set up the CSP headers // script-src 'self' @@ -384,204 +386,93 @@ pub async fn create_https_server( info!("Starting the web server..."); - Ok(task::spawn(async move { - tokio::select! { - Ok(action) = rx.recv() => { - match action { - CoreAction::Shutdown => {}, + match maybe_tls_acceptor { + Some(tls_acceptor) => { + let listener = match TcpListener::bind(addr).await { + Ok(l) => l, + Err(err) => { + error!(?err, "Failed to bind tcp listener"); + return Err(()); } - } - res = match config.tls_config { - Some(tls_param) => { - // This isn't optimal, but we can't share this with the - // other path for integration tests because that doesn't - // do tls (yet?) - let listener = match TcpListener::bind(addr).await { - Ok(l) => l, - Err(err) => { - error!(?err, "Failed to bind tcp listener"); - return - } - }; - task::spawn(server_loop(tls_param, listener, app)) - }, - None => { - task::spawn(axum_server::bind(addr).serve(app)) - } - } => { - match res { - Ok(res_inner) => { - match res_inner { - Ok(_) => debug!("Web server exited OK"), - Err(err) => { - error!("Web server exited with {:?}", err); - } - } - - }, - Err(err) => { - error!("Web server exited with {:?}", err); - } - }; - if let Err(err) = server_message_tx.send(CoreAction::Shutdown) { - error!("Web server failed to send shutdown message! {:?}", err) - }; - } - - - }; - - info!("Stopped {}", super::TaskName::HttpsServer); - })) + }; + Ok(task::spawn(server_loop( + tls_acceptor, + listener, + app, + rx, + server_message_tx, + tls_acceptor_reload_rx, + ))) + } + None => Ok(task::spawn(server_loop_plaintext(addr, app, rx))), + } } async fn server_loop( - tls_param: TlsConfiguration, + mut tls_acceptor: SslAcceptor, listener: TcpListener, app: IntoMakeServiceWithConnectInfo, -) -> Result<(), std::io::Error> { - let mut tls_builder = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls())?; - - tls_builder - .set_certificate_chain_file(tls_param.chain.clone()) - .map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("Failed to create TLS listener: {:?}", err), - ) - })?; - - tls_builder - .set_private_key_file(tls_param.key.clone(), SslFiletype::PEM) - .map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("Failed to create TLS listener: {:?}", err), - ) - })?; - - tls_builder.check_private_key().map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("Failed to create TLS listener: {:?}", err), - ) - })?; - - // If configured, setup TLS client authentication. - if let Some(client_ca) = tls_param.client_ca.as_ref() { - info!("Loading client certificates from {}", client_ca.display()); - - let verify = SslVerifyMode::PEER; - // In future we may add a "require mTLS option" which would necessitate this. - // verify.insert(SslVerifyMode::FAIL_IF_NO_PEER_CERT); - tls_builder.set_verify(verify); - - // When client certs are available, we disable the TLS session cache. - // This is so that when the smartcard is *removed* on the client, it forces - // the client session to immediately expire. - // - // https://stackoverflow.com/questions/12393711/session-disconnect-the-client-after-smart-card-is-removed - // - // Alternately, on logout we need to trigger https://docs.rs/openssl/latest/openssl/ssl/struct.Ssl.html#method.set_ssl_context - // with https://docs.rs/openssl/latest/openssl/ssl/struct.Ssl.html#method.ssl_context + - // https://docs.rs/openssl/latest/openssl/ssl/struct.SslContextRef.html#method.remove_session - // - // Or we lower session time outs etc. - tls_builder.set_session_cache_mode(SslSessionCacheMode::OFF); - - let read_dir = fs::read_dir(client_ca).map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!( - "Failed to create TLS listener while loading client ca from {}: {:?}", - client_ca.display(), - err - ), - ) - })?; - - for cert_dir_ent in read_dir.filter_map(|item| item.ok()).filter(|item| { - item.file_name() - .to_str() - // Hashed certs end in .0 - // Hashed crls are .r0 - .map(|fname| fname.ends_with(".0")) - .unwrap_or_default() - }) { - let mut cert_pem = String::new(); - fs::File::open(cert_dir_ent.path()) - .and_then(|mut file| file.read_to_string(&mut cert_pem)) - .map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("Failed to create TLS listener: {:?}", err), - ) - })?; - - let cert = X509::from_pem(cert_pem.as_bytes()).map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("Failed to create TLS listener: {:?}", err), - ) - })?; - - let cert_store = tls_builder.cert_store_mut(); - cert_store.add_cert(cert.clone()).map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!( - "Failed to load cert store while creating TLS listener: {:?}", - err - ), - ) - })?; - // This tells the client what CA's they should use. It DOES NOT - // verify them. That's the job of the cert store above! - tls_builder.add_client_ca(&cert).map_err(|err| { - std::io::Error::new( - ErrorKind::Other, - format!("Failed to create TLS listener: {:?}", err), - ) - })?; - } - - // TODO: Build our own CRL map HERE! - - // Allow dumping client cert chains for dev debugging - // In the case this is status=false, should we be dumping these anyway? - if enabled!(tracing::Level::TRACE) { - tls_builder.set_verify_callback(verify, |status, x509store| { - if let Some(current_cert) = x509store.current_cert() { - let cert_text_bytes = current_cert.to_text().unwrap_or_default(); - let cert_text = String::from_utf8_lossy(cert_text_bytes.as_slice()); - tracing::warn!(client_cert = %cert_text); - }; - - if let Some(chain) = x509store.chain() { - for cert in chain.iter() { - let cert_text_bytes = cert.to_text().unwrap_or_default(); - let cert_text = String::from_utf8_lossy(cert_text_bytes.as_slice()); - tracing::warn!(chain_cert = %cert_text); - } - } - - status - }); - } - - // End tls_client setup - } - - let tls_acceptor = tls_builder.build(); + mut rx: broadcast::Receiver, + server_message_tx: broadcast::Sender, + mut tls_acceptor_reload_rx: mpsc::Receiver, +) { pin_mut!(listener); loop { - if let Ok((stream, addr)) = listener.accept().await { - let tls_acceptor = tls_acceptor.clone(); - let app = app.clone(); - task::spawn(handle_conn(tls_acceptor, stream, app, addr)); + tokio::select! { + Ok(action) = rx.recv() => { + match action { + CoreAction::Shutdown => break, + } + } + accept = listener.accept() => { + match accept { + Ok((stream, addr)) => { + let tls_acceptor = tls_acceptor.clone(); + let app = app.clone(); + task::spawn(handle_conn(tls_acceptor, stream, app, addr)); + } + Err(err) => { + error!("Web server exited with {:?}", err); + if let Err(err) = server_message_tx.send(CoreAction::Shutdown) { + error!("Web server failed to send shutdown message! {:?}", err) + }; + break; + } + } + } + Some(mut new_tls_acceptor) = tls_acceptor_reload_rx.recv() => { + std::mem::swap(&mut tls_acceptor, &mut new_tls_acceptor); + info!("Reloaded http tls acceptor"); + } } } + + info!("Stopped {}", super::TaskName::HttpsServer); +} + +async fn server_loop_plaintext( + addr: SocketAddr, + app: IntoMakeServiceWithConnectInfo, + mut rx: broadcast::Receiver, +) { + let listener = axum_server::bind(addr).serve(app); + + pin_mut!(listener); + + loop { + tokio::select! { + Ok(action) = rx.recv() => { + match action { + CoreAction::Shutdown => + break, + } + } + _ = &mut listener => {} + } + } + + info!("Stopped {}", super::TaskName::HttpsServer); } /// This handles an individual connection. diff --git a/server/core/src/ldaps.rs b/server/core/src/ldaps.rs index c3fe98c57..f8e76678f 100644 --- a/server/core/src/ldaps.rs +++ b/server/core/src/ldaps.rs @@ -16,6 +16,7 @@ use tokio_util::codec::{FramedRead, FramedWrite}; use crate::CoreAction; use tokio::sync::broadcast; +use tokio::sync::mpsc; struct LdapSession { uat: Option, @@ -126,11 +127,12 @@ async fn client_process( } /// TLS LDAP Listener, hands off to [client_process] -async fn tls_acceptor( +async fn ldap_tls_acceptor( listener: TcpListener, - tls_acceptor: SslAcceptor, + mut tls_acceptor: SslAcceptor, qe_r_ref: &'static QueryServerReadV1, mut rx: broadcast::Receiver, + mut tls_acceptor_reload_rx: mpsc::Receiver, ) { loop { tokio::select! { @@ -150,6 +152,10 @@ async fn tls_acceptor( } } } + Some(mut new_tls_acceptor) = tls_acceptor_reload_rx.recv() => { + std::mem::swap(&mut tls_acceptor, &mut new_tls_acceptor); + info!("Reloaded ldap tls acceptor"); + } } } info!("Stopped {}", super::TaskName::LdapActor); @@ -160,6 +166,7 @@ pub(crate) async fn create_ldap_server( opt_ssl_acceptor: Option, qe_r_ref: &'static QueryServerReadV1, rx: broadcast::Receiver, + tls_acceptor_reload_rx: mpsc::Receiver, ) -> Result, ()> { if address.starts_with(":::") { // takes :::xxxx to xxxx @@ -182,7 +189,13 @@ pub(crate) async fn create_ldap_server( Some(ssl_acceptor) => { info!("Starting LDAPS interface ldaps://{} ...", address); - tokio::spawn(tls_acceptor(listener, ssl_acceptor, qe_r_ref, rx)) + tokio::spawn(ldap_tls_acceptor( + listener, + ssl_acceptor, + qe_r_ref, + rx, + tls_acceptor_reload_rx, + )) } None => { error!("The server won't run without TLS!"); diff --git a/server/core/src/lib.rs b/server/core/src/lib.rs index f85436dc4..2c7990cf5 100644 --- a/server/core/src/lib.rs +++ b/server/core/src/lib.rs @@ -52,12 +52,14 @@ use kanidmd_lib::value::CredentialType; use libc::umask; use tokio::sync::broadcast; +use tokio::sync::Notify; use tokio::task; use crate::actors::{QueryServerReadV1, QueryServerWriteV1}; use crate::admin::AdminActor; use crate::config::{Configuration, ServerRole}; use crate::interval::IntervalActor; +use tokio::sync::mpsc; // === internal setup helpers @@ -738,6 +740,7 @@ pub(crate) enum TaskName { IntervalActor, LdapActor, Replication, + TlsAcceptorReload, } impl Display for TaskName { @@ -754,6 +757,7 @@ impl Display for TaskName { TaskName::IntervalActor => "Interval Actor", TaskName::LdapActor => "LDAP Acceptor Actor", TaskName::Replication => "Replication", + TaskName::TlsAcceptorReload => "TlsAcceptor Reload Monitor", } ) } @@ -761,12 +765,17 @@ impl Display for TaskName { pub struct CoreHandle { clean_shutdown: bool, - pub tx: broadcast::Sender, + tx: broadcast::Sender, + tls_acceptor_reload_notify: Arc, /// This stores a name for the handle, and the handle itself so we can tell which failed/succeeded at the end. handles: Vec<(TaskName, task::JoinHandle<()>)>, } impl CoreHandle { + pub fn subscribe(&mut self) -> broadcast::Receiver { + self.tx.subscribe() + } + pub async fn shutdown(&mut self) { if self.tx.send(CoreAction::Shutdown).is_err() { eprintln!("No receivers acked shutdown request. Treating as unclean."); @@ -782,6 +791,10 @@ impl CoreHandle { self.clean_shutdown = true; } + + pub async fn tls_acceptor_reload(&mut self) { + self.tls_acceptor_reload_notify.notify_one() + } } impl Drop for CoreHandle { @@ -826,10 +839,10 @@ pub async fn create_server_core( let status_ref = StatusActor::start(); // Setup TLS (if any) - let _opt_tls_params = match crypto::setup_tls(&config) { - Ok(opt_tls_params) => opt_tls_params, - Err(()) => { - error!("Failed to configure TLS parameters"); + let maybe_tls_acceptor = match crypto::setup_tls(&config.tls_config) { + Ok(tls_acc) => tls_acc, + Err(err) => { + error!(?err, "Failed to configure TLS acceptor"); return Err(()); } }; @@ -1009,6 +1022,54 @@ pub async fn create_server_core( info!("Stopped {}", TaskName::AuditdActor); }); + // Setup a TLS Acceptor Reload trigger. + + let mut broadcast_rx = broadcast_tx.subscribe(); + let tls_acceptor_reload_notify = Arc::new(Notify::new()); + let tls_accepter_reload_task_notify = tls_acceptor_reload_notify.clone(); + let tls_config = config.tls_config.clone(); + + let ldap_configured = config.ldapaddress.is_some(); + let (ldap_tls_acceptor_reload_tx, ldap_tls_acceptor_reload_rx) = mpsc::channel(1); + let (http_tls_acceptor_reload_tx, http_tls_acceptor_reload_rx) = mpsc::channel(1); + + let tls_acceptor_reload_handle = task::spawn(async move { + loop { + tokio::select! { + Ok(action) = broadcast_rx.recv() => { + match action { + CoreAction::Shutdown => break, + } + } + _ = tls_accepter_reload_task_notify.notified() => { + let tls_acceptor = match crypto::setup_tls(&tls_config) { + Ok(Some(tls_acc)) => tls_acc, + Ok(None) => { + warn!("TLS not configured, ignoring reload request."); + continue; + } + Err(err) => { + error!(?err, "Failed to configure and reload TLS acceptor"); + continue; + } + }; + + // We don't log here as the receivers will notify when they have completed + // the reload. + if ldap_configured && + ldap_tls_acceptor_reload_tx.send(tls_acceptor.clone()).await.is_err() { + error!("ldap tls acceptor did not accept the reload, the server may have failed!"); + }; + if http_tls_acceptor_reload_tx.send(tls_acceptor.clone()).await.is_err() { + error!("http tls acceptor did not accept the reload, the server may have failed!"); + break; + }; + } + } + } + info!("Stopped {}", TaskName::TlsAcceptorReload); + }); + // Setup timed events associated to the write thread let interval_handle = IntervalActor::start(server_write_ref, broadcast_tx.subscribe()); // Setup timed events associated to the read thread @@ -1035,13 +1096,8 @@ pub async fn create_server_core( // If we have been requested to init LDAP, configure it now. let maybe_ldap_acceptor_handle = match &config.ldapaddress { Some(la) => { - let opt_ldap_ssl_acceptor = match crypto::setup_tls(&config) { - Ok(t) => t, - Err(()) => { - error!("Failed to configure LDAP TLS parameters"); - return Err(()); - } - }; + let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone(); + if !config_test { // ⚠️ only start the sockets and listeners in non-config-test modes. let h = ldaps::create_ldap_server( @@ -1049,6 +1105,7 @@ pub async fn create_server_core( opt_ldap_ssl_acceptor, server_read_ref, broadcast_tx.subscribe(), + ldap_tls_acceptor_reload_rx, ) .await?; Some(h) @@ -1092,8 +1149,9 @@ pub async fn create_server_core( status_ref, server_write_ref, server_read_ref, - broadcast_tx.subscribe(), broadcast_tx.clone(), + maybe_tls_acceptor, + http_tls_acceptor_reload_rx, ) .await { @@ -1133,6 +1191,7 @@ pub async fn create_server_core( (TaskName::IntervalActor, interval_handle), (TaskName::DelayedActionActor, delayed_handle), (TaskName::AuditdActor, auditd_handle), + (TaskName::TlsAcceptorReload, tls_acceptor_reload_handle), ]; if let Some(backup_handle) = maybe_backup_handle { @@ -1157,6 +1216,7 @@ pub async fn create_server_core( Ok(CoreHandle { clean_shutdown: false, + tls_acceptor_reload_notify, tx: broadcast_tx, handles, }) diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs index 237cd3a23..f3aa51de9 100644 --- a/server/daemon/src/main.rs +++ b/server/daemon/src/main.rs @@ -756,7 +756,7 @@ async fn kanidm_main( loop { #[cfg(target_family = "unix")] { - let mut listener = sctx.tx.subscribe(); + let mut listener = sctx.subscribe(); tokio::select! { Ok(()) = tokio::signal::ctrl_c() => { break @@ -780,7 +780,8 @@ async fn kanidm_main( #[allow(clippy::unwrap_used)] tokio::signal::unix::signal(sigterm).unwrap().recv().await } => { - // Ignore + // Reload TLS certificates + sctx.tls_acceptor_reload().await } Some(()) = async move { let sigterm = tokio::signal::unix::SignalKind::user_defined1();