when the HTTPS server fails, handle that gracefully (#2546)

This commit is contained in:
James Hodgkinson 2024-02-16 18:30:43 +10:00 committed by GitHub
parent 816fde766f
commit 48f33fb8c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 123 additions and 53 deletions

View file

@ -9,6 +9,7 @@ set -e
WAIT_TIMER=5
echo "Building release binaries..."
cargo build --release --bin kanidm --bin kanidmd
@ -18,6 +19,7 @@ if [ -d '.git' ]; then
cd server/daemon/ || exit 1
fi
if [ ! -f "run_insecure_dev_server.sh" ]; then
echo "I'm not sure where you are, please run this from the root of the repository or the server/daemon directory"
exit 1
@ -29,12 +31,13 @@ echo "Generating certificates..."
cargo run --bin kanidmd --release cert-generate --config ../../examples/insecure_server.toml
echo "Making sure it runs with the DB..."
cargo run --bin kanidmd --release recover-account idm_admin -o json
cargo run --bin kanidmd --release recover-account idm_admin -o json --config ../../examples/insecure_server.toml
echo "Running the server..."
cargo run --bin kanidmd --release server --config ../../examples/insecure_server.toml &
KANIDMD_PID=$!
echo "${KANIDMD_PID}"
echo "Kanidm PID: ${KANIDMD_PID}"
if [ "$(jobs -p | wc -l)" -eq 0 ]; then
echo "Kanidmd failed to start!"
@ -48,8 +51,8 @@ KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
while true; do
echo "Waiting the server to start... testing ${KANIDM_URL}"
curl --cacert "${KANIDM_CA_PATH}" -fs "${KANIDM_URL}" >/dev/null && break
echo "Waiting for the server to start... testing ${KANIDM_URL}"
curl --cacert "${KANIDM_CA_PATH}" -fs "${KANIDM_URL}/status" >/dev/null && break
sleep 2
ATTEMPT="$((ATTEMPT + 1))"
if [ "${ATTEMPT}" -gt 3 ]; then

View file

@ -181,6 +181,7 @@ pub async fn create_https_server(
qe_w_ref: &'static QueryServerWriteV1,
qe_r_ref: &'static QueryServerReadV1,
mut rx: broadcast::Receiver<CoreAction>,
server_message_tx: broadcast::Sender<CoreAction>,
) -> Result<tokio::task::JoinHandle<()>, ()> {
let js_files = get_js_files(config.role)?;
// set up the CSP headers
@ -354,10 +355,26 @@ pub async fn create_https_server(
tokio::spawn(axum_server::bind(addr).serve(app))
}
} => {
if let Err(err) = res {
error!("Web server exited with {:?}", err);
}
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)
};
}
};
#[cfg(feature = "otel")]
opentelemetry::global::shutdown_tracer_provider();
@ -399,10 +416,7 @@ async fn server_loop(
// If configured, setup TLS client authentication.
if let Some(client_ca) = tls_param.client_ca.as_ref() {
debug!(
"Configuring client certificates from {}",
client_ca.display()
);
info!("Loading client certificates from {}", client_ca.display());
let verify = SslVerifyMode::PEER;
// In future we may add a "require mTLS option" which would necesitate this.
@ -425,7 +439,11 @@ async fn server_loop(
let read_dir = fs::read_dir(client_ca).map_err(|err| {
std::io::Error::new(
ErrorKind::Other,
format!("Failed to create TLS listener: {:?}", err),
format!(
"Failed to create TLS listener while loading client ca from {}: {:?}",
client_ca.display(),
err
),
)
})?;
@ -458,7 +476,10 @@ async fn server_loop(
cert_store.add_cert(cert.clone()).map_err(|err| {
std::io::Error::new(
ErrorKind::Other,
format!("Failed to create TLS listener: {:?}", err),
format!(
"Failed to load cert store while creating TLS listener: {:?}",
err
),
)
})?;
// This tells the client what CA's they should use. It DOES NOT

View file

@ -748,7 +748,7 @@ impl Display for TaskName {
pub struct CoreHandle {
clean_shutdown: bool,
tx: broadcast::Sender<CoreAction>,
pub tx: broadcast::Sender<CoreAction>,
/// This stores a name for the handle, and the handle itself so we can tell which failed/succeeded at the end.
handles: Vec<(TaskName, tokio::task::JoinHandle<()>)>,
}
@ -1068,6 +1068,7 @@ pub async fn create_server_core(
server_write_ref,
server_read_ref,
broadcast_tx.subscribe(),
broadcast_tx.clone(),
)
.await
{

View file

@ -530,6 +530,43 @@ async fn kanidm_main() -> ExitCode {
}
}
if let Some(ca_dir) = &(sconfig.tls_client_ca) {
// check that the TLS client CA config option is what we expect
let ca_dir_path = PathBuf::from(&ca_dir);
if !ca_dir_path.exists() {
error!(
"TLS CA folder {} does not exist, server startup will FAIL!",
ca_dir
);
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
info!(%diag);
}
let i_meta = match metadata(&ca_dir_path) {
Ok(m) => m,
Err(e) => {
error!("Unable to read metadata for '{}' - {:?}", ca_dir, e);
let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
info!(%diag);
return ExitCode::FAILURE;
}
};
if !i_meta.is_dir() {
error!(
"ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
ca_dir
);
return ExitCode::FAILURE;
}
if kanidm_lib_file_permissions::readonly(&i_meta) {
warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir);
}
#[cfg(not(target_os = "windows"))]
if i_meta.mode() & 0o007 != 0 {
warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir);
}
}
let sctx = create_server_core(config, config_test).await;
if !config_test {
// On linux, notify systemd.
@ -541,46 +578,54 @@ async fn kanidm_main() -> ExitCode {
loop {
#[cfg(target_family = "unix")]
{
let mut listener = sctx.tx.subscribe();
tokio::select! {
Ok(()) = tokio::signal::ctrl_c() => {
break
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::terminate();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
break
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::alarm();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::hangup();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::user_defined1();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::user_defined2();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
}
Ok(()) = tokio::signal::ctrl_c() => {
break
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::terminate();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
break
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::alarm();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::hangup();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::user_defined1();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
Some(()) = async move {
let sigterm = tokio::signal::unix::SignalKind::user_defined2();
#[allow(clippy::unwrap_used)]
tokio::signal::unix::signal(sigterm).unwrap().recv().await
} => {
// Ignore
}
// we got a message on thr broadcast from somewhere else
Ok(msg) = async move {
listener.recv().await
} => {
debug!("Main loop received message: {:?}", msg);
break
}
}
}
#[cfg(target_family = "windows")]
{