This commit is contained in:
Mateusz Gruszczyński committed 2026-11-06 23:16:36 +01:00
1 parent 09414dfb09
commit f4606cce18
36 files changed
+641 -74

No files matched your search

+59
View File
@@ -1,3 +1,41 @@
/// Certificate expiry read from the certificate files, refreshed at most every five minutes.
fn certificate_expiry(
app: &App,
certificates: &[crate::tls::Certificate],
) -> std::collections::HashMap<String, i64> {
type Cached = (std::time::Instant, Vec<(String, String)>, std::collections::HashMap<String, i64>);
static CACHE: std::sync::Mutex<Option<Cached>> = std::sync::Mutex::new(None);
let wanted: Vec<(String, String)> = certificates
.iter()
.map(|cert| (cert.id.clone(), cert.cert_file.clone()))
.collect();
let mut cache = CACHE.lock().unwrap_or_else(|e| e.into_inner());
if let Some((at, files, values)) = cache.as_ref() {
if at.elapsed() < std::time::Duration::from_secs(300) && *files == wanted {
return values.clone();
}
}
let values = read_certificate_expiry(app.policy.cert_dir.as_deref(), &wanted);
*cache = Some((std::time::Instant::now(), wanted, values.clone()));
values
}
fn read_certificate_expiry(
directory: Option<&std::path::Path>,
certificates: &[(String, String)],
) -> std::collections::HashMap<String, i64> {
let Some(directory) = directory else { return Default::default() };
certificates
.iter()
.filter_map(|(id, file)| {
crate::tls::certificate_metadata(directory, file)
.ok()
.and_then(|metadata| metadata.not_after_unix)
.map(|not_after| (id.clone(), not_after))
})
.collect()
}
fn render_core(app: &App, snapshot: &crate::config::Snapshot, out: &mut String, f: &mut Families) {
let config = &snapshot.envelope.config;
let settings = &config.prometheus;
@@ -101,8 +139,29 @@ fn render_core(app: &App, snapshot: &crate::config::Snapshot, out: &mut String,
config.tls.certificates.len(),
);
let renewals = app.acme.renewal_view();
let expiry = certificate_expiry(app, &config.tls.certificates);
for certificate in &config.tls.certificates {
let l = labels(&[("certificate", &certificate.id)]);
if let Some(not_after) = expiry.get(&certificate.id).copied() {
f.add(
"rust_proxy_certificate_not_after_timestamp_seconds",
"gauge",
"Expiry time of the installed certificate (Unix seconds).",
&l,
not_after,
);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|elapsed| elapsed.as_secs() as i64)
.unwrap_or_default();
f.add(
"rust_proxy_certificate_days_remaining",
"gauge",
"Whole days until the installed certificate expires; negative when expired.",
&l,
(not_after - now).div_euclid(86_400),
);
}
let issuer = certificate.issuer.as_ref();
f.add(
"rust_proxy_certificate_auto_renew_enabled",
+14
View File
@@ -218,4 +218,18 @@ mod tests {
assert_eq!(response.status(), StatusCode::OK);
store.close().await;
}
#[test]
fn certificate_expiry_is_read_from_installed_certificate_files() {
let dir = tempfile::tempdir().unwrap();
let generated = rcgen::generate_simple_self_signed(vec!["expiry.localhost".into()]).unwrap();
std::fs::write(dir.path().join("site.pem"), generated.cert.pem()).unwrap();
let values = read_certificate_expiry(
Some(dir.path()),
&[("site".into(), "site.pem".into()), ("missing".into(), "missing.pem".into())],
);
assert!(values.get("site").is_some_and(|not_after| *not_after > 0));
assert!(!values.contains_key("missing"));
assert!(read_certificate_expiry(None, &[("site".into(), "site.pem".into())]).is_empty());
}
}