Watch
0
0
Fork
You've already forked hyperhive
0

export bao client-cert not_before/not_after gauges and alert past 80% of lifetime

This commit is contained in:
atlas 2026-10-25 17:41:47 +01:00 • committed by mara
commit 078696aaee
23 changed files with 604 additions and 53 deletions

View file

@ -30,6 +30,8 @@ thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
vaultrs.workspace = true
# `cert::validity` decodes a client certificate's PEM for its validity window.
x509-cert.workspace = true
[lints]
workspace = true

View file

@ -0,0 +1,109 @@
//! A client certificate's validity window, read from the PEM itself.
//!
//! The window is the certificate's own `notBefore`/`notAfter`, the same two
//! timestamps the store checks at login, so a caller that asks "how much of
//! its life is left" and the store never disagree about the answer.
use std::path::Path;
use x509_cert::der::DecodePem as _;
use crate::{Error, client::ENV_CLIENT_CERT};
/// The gauge a service exports for its client certificate's `notBefore`, unix
/// seconds. The alert rule `hh-bao-client-cert-lifetime` selects this name.
pub const NOT_BEFORE_METRIC: &str = "bao.client_cert.not_before";
/// The gauge for the certificate's `notAfter`, unix seconds.
pub const NOT_AFTER_METRIC: &str = "bao.client_cert.not_after";
/// A certificate's validity window, `(notBefore, notAfter)` in unix seconds.
///
/// # Errors
/// [`Error::Certificate`] when `pem` is not one PEM-encoded X.509 certificate.
pub fn validity(pem: &str) -> Result<(i64, i64), Error> {
let cert = x509_cert::Certificate::from_pem(pem.as_bytes())
.map_err(|e| Error::Certificate(e.to_string()))?;
let validity = &cert.tbs_certificate.validity;
let secs = |t: x509_cert::time::Time| {
i64::try_from(t.to_unix_duration().as_secs()).unwrap_or(i64::MAX)
};
Ok((secs(validity.not_before), secs(validity.not_after)))
}
/// [`validity`] of the client certificate file at `path` (the file
/// [`ENV_CLIENT_CERT`] names).
///
/// # Errors
/// [`Error::Identity`] when the file cannot be read, [`Error::Certificate`]
/// when it is not a certificate.
pub fn file_validity(path: &Path) -> Result<(i64, i64), Error> {
let pem = std::fs::read_to_string(path).map_err(|source| Error::Identity {
var: ENV_CLIENT_CERT,
path: path.display().to_string(),
source,
})?;
validity(&pem)
}
#[cfg(test)]
mod tests {
use super::*;
/// Self-signed, CN `hive-agent-atlas`, valid 2026-01-01T00:00:00Z to
/// 2026-04-01T00:00:00Z. Its key was discarded.
pub(crate) const CERT_PEM: &str = "-----BEGIN CERTIFICATE-----
MIIBizCCATGgAwIBAgIUSFpuYmy1UoHvq/MYiyrAudRaWXcwCgYIKoZIzj0EAwIw
GzEZMBcGA1UEAwwQaGl2ZS1hZ2VudC1hdGxhczAeFw0yNjAxMDEwMDAwMDBaFw0y
NjA0MDEwMDAwMDBaMBsxGTAXBgNVBAMMEGhpdmUtYWdlbnQtYXRsYXMwWTATBgcq
hkjOPQIBBggqhkjOPQMBBwNCAAR7p2eZAxjiE1RdsuSHE3CcTjbnd4swzfnmqUPW
9NJN7mvOtqEzxCKfOSFjaIhhyShIQ41/V6vGi80EPDB+uR56o1MwUTAdBgNVHQ4E
FgQUxgZFsN1VY3ODrPS1NUfY/x3EnmswHwYDVR0jBBgwFoAUxgZFsN1VY3ODrPS1
NUfY/x3EnmswDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAnU9e
WOioNTUNK9b6FignejpK5FpyrODUuH/iL4TqCh0CIAz6GzJeLebhRPvYlfVUofdV
hWx3sOwmUgjkRQXoxY+p
-----END CERTIFICATE-----
";
pub(crate) const NOT_BEFORE: i64 = 1_767_225_600;
pub(crate) const NOT_AFTER: i64 = 1_775_001_600;
#[test]
fn the_window_is_read_from_the_certificate() {
assert_eq!(
validity(CERT_PEM).expect("a well-formed certificate"),
(NOT_BEFORE, NOT_AFTER)
);
}
#[test]
fn something_that_is_not_a_certificate_is_an_error() {
assert!(matches!(
validity("not a certificate"),
Err(Error::Certificate(_))
));
assert!(validity("").is_err());
}
#[test]
fn a_file_is_read_through_its_path() {
let dir =
std::env::temp_dir().join(format!("swarm-secret-client-cert-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("scratch dir");
let path = dir.join("cert.pem");
std::fs::write(&path, CERT_PEM).expect("write");
assert_eq!(
file_validity(&path).expect("readable"),
(NOT_BEFORE, NOT_AFTER)
);
std::fs::remove_dir_all(&dir).expect("cleanup");
}
#[test]
fn a_missing_file_names_the_variable_and_path() {
let err = file_validity(Path::new("/nonexistent/cert.pem")).expect_err("no such file");
assert!(
matches!(&err, Error::Identity { var, path, .. }
if *var == ENV_CLIENT_CERT && path == "/nonexistent/cert.pem"),
"{err}"
);
}
}

View file

@ -31,6 +31,7 @@
pub mod acp;
pub mod builder;
pub mod cert;
pub mod ci;
pub mod ci_metrics;
pub mod claude_login;
@ -103,6 +104,10 @@ pub enum Error {
/// identity needs. Names the field, never its value.
#[error("the store issued a certificate whose {0} is empty")]
IncompleteIssue(&'static str),
/// The text was not one PEM-encoded X.509 certificate.
#[error("decoding the certificate: {0}")]
Certificate(String),
}
impl From<vaultrs::error::ClientError> for Error {