Implement graceful shutdown for listeners, admin socket, eviction task, and ACME
- Replace handle.abort() for HTTPS server tasks with timeout-based join, allowing in-flight requests to drain before forceful shutdown - Add shutdown_rx to start_admin_socket with tokio::select! for clean accept loop exit and Unix socket file cleanup on shutdown - Add shutdown_rx to start_eviction_task with tokio::select! for cancellable eviction loop - Add shutdown channel to spawn_acme_state for cancellable ACME state machine via tokio::select! - Pass Arc<GracefulShutdown> through setup_tls to ACME state machine - Move GracefulShutdown creation before admin socket and TLS setup - Update integration test for new start_eviction_task signature
This commit is contained in:
@@ -8,6 +8,7 @@ use tracing::info;
|
||||
use super::acme::{spawn_acme_state, AcmeTlsConfig};
|
||||
use super::config::crypto_provider;
|
||||
use crate::config::static_config::TlsConfig;
|
||||
use crate::shutdown::GracefulShutdown;
|
||||
|
||||
const ACME_TLS_ALPN_01: &[u8] = b"acme-tls/1";
|
||||
|
||||
@@ -41,7 +42,7 @@ pub enum TlsMode {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn setup_tls(tls_config: &TlsConfig) -> Result<TlsMode> {
|
||||
pub fn setup_tls(tls_config: &TlsConfig, shutdown: Arc<GracefulShutdown>) -> Result<TlsMode> {
|
||||
match tls_config.mode.as_str() {
|
||||
"manual" => {
|
||||
if tls_config.cert_path.is_empty() {
|
||||
@@ -75,7 +76,7 @@ pub fn setup_tls(tls_config: &TlsConfig) -> Result<TlsMode> {
|
||||
|
||||
let default_config = build_acme_server_config(resolver.clone())?;
|
||||
|
||||
spawn_acme_state(state, tls_config.acme_domains.clone());
|
||||
spawn_acme_state(state, tls_config.acme_domains.clone(), shutdown);
|
||||
|
||||
info!(
|
||||
domains = ?tls_config.acme_domains,
|
||||
@@ -136,7 +137,8 @@ mod tests {
|
||||
cert_path: String::new(),
|
||||
key_path: "/some/key.pem".to_string(),
|
||||
};
|
||||
let result = setup_tls(&tls_config);
|
||||
let shutdown = Arc::new(GracefulShutdown::new(30));
|
||||
let result = setup_tls(&tls_config, shutdown);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("cert_path"));
|
||||
@@ -153,7 +155,8 @@ mod tests {
|
||||
cert_path: "/some/cert.pem".to_string(),
|
||||
key_path: String::new(),
|
||||
};
|
||||
let result = setup_tls(&tls_config);
|
||||
let shutdown = Arc::new(GracefulShutdown::new(30));
|
||||
let result = setup_tls(&tls_config, shutdown);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("key_path"));
|
||||
@@ -170,7 +173,8 @@ mod tests {
|
||||
cert_path: String::new(),
|
||||
key_path: String::new(),
|
||||
};
|
||||
let result = setup_tls(&tls_config);
|
||||
let shutdown = Arc::new(GracefulShutdown::new(30));
|
||||
let result = setup_tls(&tls_config, shutdown);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("acme_domains"));
|
||||
@@ -187,7 +191,8 @@ mod tests {
|
||||
cert_path: String::new(),
|
||||
key_path: String::new(),
|
||||
};
|
||||
let result = setup_tls(&tls_config);
|
||||
let shutdown = Arc::new(GracefulShutdown::new(30));
|
||||
let result = setup_tls(&tls_config, shutdown);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("acme_cache_dir"));
|
||||
@@ -204,9 +209,10 @@ mod tests {
|
||||
cert_path: String::new(),
|
||||
key_path: String::new(),
|
||||
};
|
||||
let result = setup_tls(&tls_config);
|
||||
let shutdown = Arc::new(GracefulShutdown::new(30));
|
||||
let result = setup_tls(&tls_config, shutdown);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err().to_string();
|
||||
assert!(err.contains("unknown TLS mode"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
173
src/tls/acme.rs
173
src/tls/acme.rs
@@ -6,6 +6,8 @@ use rustls_acme::caches::DirCache;
|
||||
use rustls_acme::{AcmeConfig, AcmeState, EventError, EventOk, ResolvesServerCertAcme};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::shutdown::GracefulShutdown;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const LETS_ENCRYPT_PRODUCTION_DIRECTORY: &str = "https://acme-v02.api.letsencrypt.org/directory";
|
||||
#[allow(dead_code)]
|
||||
@@ -66,93 +68,106 @@ impl AcmeTlsConfig {
|
||||
pub fn spawn_acme_state(
|
||||
state: AcmeState<std::io::Error, std::io::Error>,
|
||||
domains: Vec<String>,
|
||||
shutdown: Arc<GracefulShutdown>,
|
||||
) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
use futures::StreamExt;
|
||||
let mut state = state;
|
||||
let mut shutdown_rx = shutdown.subscribe();
|
||||
loop {
|
||||
match state.next().await {
|
||||
Some(Ok(event)) => match event {
|
||||
EventOk::DeployedCachedCert => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: deployed cached certificate"
|
||||
);
|
||||
tokio::select! {
|
||||
event = state.next() => {
|
||||
match event {
|
||||
Some(Ok(event)) => match event {
|
||||
EventOk::DeployedCachedCert => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: deployed cached certificate"
|
||||
);
|
||||
}
|
||||
EventOk::DeployedNewCert => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: deployed new certificate"
|
||||
);
|
||||
}
|
||||
EventOk::CertCacheStore => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: certificate stored to cache"
|
||||
);
|
||||
}
|
||||
EventOk::AccountCacheStore => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: account stored to cache"
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(Err(err)) => match &err {
|
||||
EventError::CertCacheLoad(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: certificate cache load failed"
|
||||
);
|
||||
}
|
||||
EventError::AccountCacheLoad(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: account cache load failed"
|
||||
);
|
||||
}
|
||||
EventError::CertCacheStore(e) => {
|
||||
warn!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: certificate cache store failed"
|
||||
);
|
||||
}
|
||||
EventError::AccountCacheStore(e) => {
|
||||
warn!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: account cache store failed"
|
||||
);
|
||||
}
|
||||
EventError::CachedCertParse(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: cached certificate parse failed"
|
||||
);
|
||||
}
|
||||
EventError::Order(e) => {
|
||||
warn!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: certificate order failed, will retry"
|
||||
);
|
||||
}
|
||||
EventError::NewCertParse(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: new certificate parse failed"
|
||||
);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: state machine ended"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EventOk::DeployedNewCert => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: deployed new certificate"
|
||||
);
|
||||
}
|
||||
EventOk::CertCacheStore => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: certificate stored to cache"
|
||||
);
|
||||
}
|
||||
EventOk::AccountCacheStore => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: account stored to cache"
|
||||
);
|
||||
}
|
||||
},
|
||||
Some(Err(err)) => match &err {
|
||||
EventError::CertCacheLoad(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: certificate cache load failed"
|
||||
);
|
||||
}
|
||||
EventError::AccountCacheLoad(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: account cache load failed"
|
||||
);
|
||||
}
|
||||
EventError::CertCacheStore(e) => {
|
||||
warn!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: certificate cache store failed"
|
||||
);
|
||||
}
|
||||
EventError::AccountCacheStore(e) => {
|
||||
warn!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: account cache store failed"
|
||||
);
|
||||
}
|
||||
EventError::CachedCertParse(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: cached certificate parse failed"
|
||||
);
|
||||
}
|
||||
EventError::Order(e) => {
|
||||
warn!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: certificate order failed, will retry"
|
||||
);
|
||||
}
|
||||
EventError::NewCertParse(e) => {
|
||||
error!(
|
||||
domains = ?domains,
|
||||
error = ?e,
|
||||
"ACME: new certificate parse failed"
|
||||
);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
}
|
||||
_ = shutdown_rx.changed() => {
|
||||
info!(
|
||||
domains = ?domains,
|
||||
"ACME: state machine ended"
|
||||
"ACME: state machine shutting down"
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user