fix(goose-server): cache TLS cert to disk to avoid slow startup on first launch (#8174)
Signed-off-by: Treebird <treebird@treebird.dev> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
//! the server listener always uses OpenSSL when this feature is active.
|
//! the server listener always uses OpenSSL when this feature is active.
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use goose::config::paths::Paths;
|
||||||
use rcgen::{CertificateParams, DnType, KeyPair, SanType};
|
use rcgen::{CertificateParams, DnType, KeyPair, SanType};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
@@ -105,6 +106,65 @@ pub async fn setup_tls(cert_path: Option<&str>, key_path: Option<&str>) -> Resul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tls_cache_dir() -> std::path::PathBuf {
|
||||||
|
Paths::config_dir().join("tls")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_private_key(path: &std::path::Path, contents: &[u8]) {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
|
||||||
|
let result = std::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.mode(0o600)
|
||||||
|
.open(path);
|
||||||
|
if let Ok(mut file) = result {
|
||||||
|
let _ = file.write_all(contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let _ = std::fs::write(path, contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_cached_tls() -> Option<TlsSetup> {
|
||||||
|
let dir = tls_cache_dir();
|
||||||
|
let cert_pem = std::fs::read(dir.join("server.pem")).ok()?;
|
||||||
|
let key_pem = std::fs::read(dir.join("server.key")).ok()?;
|
||||||
|
|
||||||
|
let der = pem::parse(&cert_pem).ok()?.into_contents();
|
||||||
|
let fingerprint = sha256_fingerprint(&der);
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
let config = axum_server::tls_rustls::RustlsConfig::from_pem(cert_pem, key_pem)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
let config = axum_server::tls_openssl::OpenSSLConfig::from_pem(&cert_pem, &key_pem).ok()?;
|
||||||
|
|
||||||
|
Some(TlsSetup {
|
||||||
|
config,
|
||||||
|
fingerprint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All errors are silently ignored — this is a best-effort optimisation and
|
||||||
|
/// must never prevent the server from starting.
|
||||||
|
fn save_tls_to_cache(cert_pem: &str, key_pem: &str) {
|
||||||
|
let dir = tls_cache_dir();
|
||||||
|
if std::fs::create_dir_all(&dir).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let _ = std::fs::write(dir.join("server.pem"), cert_pem);
|
||||||
|
write_private_key(&dir.join("server.key"), key_pem.as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate a self-signed TLS certificate for localhost (127.0.0.1) and
|
/// Generate a self-signed TLS certificate for localhost (127.0.0.1) and
|
||||||
/// return a [`TlsSetup`] containing the server config and the SHA-256
|
/// return a [`TlsSetup`] containing the server config and the SHA-256
|
||||||
/// fingerprint of the generated certificate (colon-separated hex).
|
/// fingerprint of the generated certificate (colon-separated hex).
|
||||||
@@ -115,6 +175,12 @@ pub async fn self_signed_config() -> Result<TlsSetup> {
|
|||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||||
|
|
||||||
|
// Fast path: reuse a previously cached certificate if one exists.
|
||||||
|
if let Some(cached) = load_cached_tls().await {
|
||||||
|
println!("GOOSED_CERT_FINGERPRINT={}", cached.fingerprint);
|
||||||
|
return Ok(cached);
|
||||||
|
}
|
||||||
|
|
||||||
let (cert, key_pair) = generate_self_signed_cert()?;
|
let (cert, key_pair) = generate_self_signed_cert()?;
|
||||||
|
|
||||||
let fingerprint = sha256_fingerprint(cert.der());
|
let fingerprint = sha256_fingerprint(cert.der());
|
||||||
@@ -123,6 +189,9 @@ pub async fn self_signed_config() -> Result<TlsSetup> {
|
|||||||
let cert_pem = cert.pem();
|
let cert_pem = cert.pem();
|
||||||
let key_pem = key_pair.serialize_pem();
|
let key_pem = key_pair.serialize_pem();
|
||||||
|
|
||||||
|
// Persist for future restarts before moving the strings into the config.
|
||||||
|
save_tls_to_cache(&cert_pem, &key_pem);
|
||||||
|
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
let config = axum_server::tls_rustls::RustlsConfig::from_pem(
|
let config = axum_server::tls_rustls::RustlsConfig::from_pem(
|
||||||
cert_pem.into_bytes(),
|
cert_pem.into_bytes(),
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const findGoosedBinaryPath = (options: FindBinaryOptions = {}): string =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const checkServerStatus = async (client: Client, errorLog: string[]): Promise<boolean> => {
|
export const checkServerStatus = async (client: Client, errorLog: string[]): Promise<boolean> => {
|
||||||
const timeout = 10000;
|
const timeout = 30000;
|
||||||
const interval = 100;
|
const interval = 100;
|
||||||
const maxAttempts = Math.ceil(timeout / interval);
|
const maxAttempts = Math.ceil(timeout / interval);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user