mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
20241024 1271 cert reload on SIGHUP (#3140)
reload certificates and keys on SIGHUP
This commit is contained in:
parent
4c2eeeb135
commit
d2ae2ca206
|
@ -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"
|
||||
#
|
||||
|
|
|
@ -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<Private>) -> 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<Option<SslAcceptor>, ()> {
|
||||
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<TlsConfiguration>,
|
||||
) -> Result<Option<SslAcceptor>, 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),
|
||||
)
|
||||
})?;
|
||||
|
||||
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);
|
||||
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),
|
||||
)
|
||||
})?;
|
||||
|
||||
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);
|
||||
tls_builder.check_private_key().map_err(|err| {
|
||||
std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Failed to create TLS listener: {:?}", err),
|
||||
)
|
||||
})?;
|
||||
|
||||
ssl_builder.check_private_key().map_err(|openssl_err| {
|
||||
error!("Failed to validate private key");
|
||||
error!(?openssl_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
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
let acceptor = ssl_builder.build();
|
||||
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();
|
||||
|
||||
// let's enforce some TLS minimums!
|
||||
let privkey = acceptor.context().private_key().ok_or_else(|| {
|
||||
error!("Failed to access acceptor private key");
|
||||
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| {
|
||||
error!("{}", err);
|
||||
std::io::Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Private key minimums were not met: {:?}", err),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Some(acceptor))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
Ok(Some(tls_acceptor))
|
||||
}
|
||||
|
||||
fn get_ec_group() -> Result<EcGroup, ErrorStack> {
|
||||
|
@ -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);
|
||||
|
|
|
@ -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<CoreAction>,
|
||||
server_message_tx: broadcast::Sender<CoreAction>,
|
||||
maybe_tls_acceptor: Option<SslAcceptor>,
|
||||
tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
|
||||
) -> Result<task::JoinHandle<()>, ()> {
|
||||
let rx = server_message_tx.subscribe();
|
||||
|
||||
let js_files = get_js_files(config.role)?;
|
||||
// set up the CSP headers
|
||||
// script-src 'self'
|
||||
|
@ -384,205 +386,94 @@ 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 => {},
|
||||
}
|
||||
}
|
||||
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?)
|
||||
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
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
task::spawn(server_loop(tls_param, listener, app))
|
||||
},
|
||||
None => {
|
||||
task::spawn(axum_server::bind(addr).serve(app))
|
||||
Ok(task::spawn(server_loop(
|
||||
tls_acceptor,
|
||||
listener,
|
||||
app,
|
||||
rx,
|
||||
server_message_tx,
|
||||
tls_acceptor_reload_rx,
|
||||
)))
|
||||
}
|
||||
} => {
|
||||
match res {
|
||||
Ok(res_inner) => {
|
||||
match res_inner {
|
||||
Ok(_) => debug!("Web server exited OK"),
|
||||
Err(err) => {
|
||||
error!("Web server exited with {:?}", err);
|
||||
None => Ok(task::spawn(server_loop_plaintext(addr, app, rx))),
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
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);
|
||||
}))
|
||||
}
|
||||
|
||||
async fn server_loop(
|
||||
tls_param: TlsConfiguration,
|
||||
mut tls_acceptor: SslAcceptor,
|
||||
listener: TcpListener,
|
||||
app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>,
|
||||
) -> 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<CoreAction>,
|
||||
server_message_tx: broadcast::Sender<CoreAction>,
|
||||
mut tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
|
||||
) {
|
||||
pin_mut!(listener);
|
||||
|
||||
loop {
|
||||
if let Ok((stream, addr)) = listener.accept().await {
|
||||
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<Router, ClientConnInfo>,
|
||||
mut rx: broadcast::Receiver<CoreAction>,
|
||||
) {
|
||||
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.
|
||||
pub(crate) async fn handle_conn(
|
||||
|
|
|
@ -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<LdapBoundToken>,
|
||||
|
@ -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<CoreAction>,
|
||||
mut tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
|
||||
) {
|
||||
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<SslAcceptor>,
|
||||
qe_r_ref: &'static QueryServerReadV1,
|
||||
rx: broadcast::Receiver<CoreAction>,
|
||||
tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
|
||||
) -> Result<tokio::task::JoinHandle<()>, ()> {
|
||||
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!");
|
||||
|
|
|
@ -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<CoreAction>,
|
||||
tx: broadcast::Sender<CoreAction>,
|
||||
tls_acceptor_reload_notify: Arc<Notify>,
|
||||
/// 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<CoreAction> {
|
||||
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,
|
||||
})
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue