From d7a2a8454c9578b400bc9b541e3c34cb210abcff Mon Sep 17 00:00:00 2001 From: mib Date: Tue, 4 Jul 2023 16:53:06 +0200 Subject: [PATCH] refactor: replace time with chrono crate --- Cargo.lock | 1 - Cargo.toml | 1 - src/common.rs | 55 +++++++++++++++++++++++++++++++------------------- src/file.rs | 1 - src/issuer.rs | 8 +++++--- src/kube.rs | 26 ++++++++++++------------ src/monitor.rs | 11 +++++----- 7 files changed, 57 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f6e763..0fad9f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -583,7 +583,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "time", "tokio 1.29.1", "trust-dns-resolver", "url 2.4.0", diff --git a/Cargo.toml b/Cargo.toml index 7bf8fcd..7d7ab95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ serde_derive = "1" serde_json = "1" openssl = "0" base64 = "0" -time = "=0.1.45" # same version as acme-lib uses acme-lib = { git = 'https://github.com/DBCDK/acme-lib', branch = 'add-sans-individually' } regex = "1" lazy_static = "1" diff --git a/src/common.rs b/src/common.rs index 0fb5c3c..8806efd 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,7 +1,7 @@ extern crate openssl; extern crate regex; -use self::openssl::asn1::Asn1TimeRef; +use self::openssl::asn1::{Asn1Time, Asn1TimeRef}; use self::openssl::nid::Nid; use self::openssl::x509::{X509NameEntryRef, X509}; @@ -20,6 +20,7 @@ use std::collections::HashSet; use std::convert::TryFrom; use std::fmt::Formatter; use std::path::PathBuf; +use chrono::{TimeZone, Utc}; pub type CertName = String; #[derive(Debug, Clone, Serialize)] @@ -199,12 +200,17 @@ impl std::convert::From for PersistError { } } +pub enum TimeError { + Diff(openssl::error::ErrorStack), + UnixTimestampOutOfBounds, +} + #[derive(Debug, Clone)] pub struct Cert { pub cn: String, pub sans: HashSet, - pub valid_from: time::Tm, // always utc - pub valid_to: time::Tm // always utc + pub valid_from: chrono::DateTime, + pub valid_to: chrono::DateTime, } impl Cert { @@ -256,18 +262,24 @@ impl Cert { } } - // #cryhard rust openssl lib doesn't allow for a plain convertion from ASN-time to time::Tm or any - // other rustlang time type. :( So... We to_string the ASNTime and re-parse it and hope for the best - fn get_timestamp(time_ref: &Asn1TimeRef) -> Result { - const IN_FORMAT: &str = "%b %e %H:%M:%S %Y %Z"; // May 31 15:21:16 2020 GMT - time::strptime(&format!("{}", &time_ref), IN_FORMAT) + fn get_timestamp(time_ref: &Asn1TimeRef) -> Result, TimeError> { + let epoch = Asn1Time::from_unix(0).expect("Failed to create Asn1Time at unix epoch"); + let diff = epoch.diff(&time_ref).map_err(TimeError::Diff)?; + //let diff = time_ref.diff(&epoch).map_err(TimeError::Diff)?; + let unix_ts = diff.days as i64 * (24 * 60 * 60) + diff.secs as i64; + use chrono::offset::LocalResult; + match chrono::Utc.timestamp_opt(unix_ts.into(), 0) { + LocalResult::None => Err(TimeError::UnixTimestampOutOfBounds), + LocalResult::Single(datetime) => Ok(datetime), + LocalResult::Ambiguous(_, _) => unreachable!("timestamp_opt never returns LocalResult::Ambigious"), + } } pub fn state(&self, config: &FaytheConfig, spec: &CertSpec) -> CertState { - let now = time::now_utc(); + let now = Utc::now(); let state = match self.valid_to { to if now > to => CertState::Expired, - to if now + time::Duration::days(config.renewal_threshold as i64) > to => CertState::ExpiresSoon, + to if now + chrono::Duration::days(config.renewal_threshold as i64) > to => CertState::ExpiresSoon, _ if now < self.valid_from => CertState::NotYetValid, to if now >= self.valid_from && now <= to => CertState::Valid, _ => CertState::Unknown, @@ -369,8 +381,8 @@ impl std::convert::From for TouchError { } } -impl std::convert::From for CertState { - fn from(_: time::ParseError) -> Self { +impl std::convert::From for CertState { + fn from(_: TimeError) -> Self { CertState::ParseError } } @@ -385,6 +397,7 @@ pub mod tests { use crate::set; use super::DNSName; use crate::config::{KubeMonitorConfig, FileMonitorConfig, MonitorConfig}; + use chrono::DateTime; const TIME_FORMAT: &str = "%Y-%m-%dT%H:%M:%S%z"; // 2019-10-09T11:50:22+0200 @@ -487,7 +500,7 @@ pub mod tests { Ingress{ name: "test".to_string(), namespace: "test".to_string(), - touched: time::empty_tm(), + touched: DateTime::::MIN_UTC, hosts: [host.to_string()].to_vec(), } } @@ -518,8 +531,8 @@ pub mod tests { Not Before: Dec 1 11:42:07 2020 GMT Not After : Nov 24 11:42:07 2050 GMT */ - assert_eq!(cert.valid_from, time::strptime("2020-12-01T11:42:07+0000", TIME_FORMAT).unwrap()); - assert_eq!(cert.valid_to, time::strptime("2050-11-24T11:42:07+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_from, DateTime::parse_from_str("2020-12-01T11:42:07+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_to, DateTime::parse_from_str("2050-11-24T11:42:07+0000", TIME_FORMAT).unwrap()); let config = create_test_kubernetes_config(false).faythe_config; let spec = create_test_certspec(cn, sans); @@ -540,8 +553,8 @@ pub mod tests { Not Before: Dec 1 11:42:07 2020 GMT Not After : Nov 24 11:42:07 2050 GMT */ - assert_eq!(cert.valid_from, time::strptime("2020-12-01T11:42:07+0000", TIME_FORMAT).unwrap()); - assert_eq!(cert.valid_to, time::strptime("2050-11-24T11:42:07+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_from, DateTime::parse_from_str("2020-12-01T11:42:07+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_to, DateTime::parse_from_str("2050-11-24T11:42:07+0000", TIME_FORMAT).unwrap()); let config = create_test_kubernetes_config(false).faythe_config; let spec = create_test_certspec(cn, sans); @@ -559,8 +572,8 @@ pub mod tests { Not Before: Dec 1 11:42:07 2020 GMT Not After : Nov 24 11:42:07 2050 GMT */ - assert_eq!(cert.valid_from, time::strptime("2020-12-01T11:42:07+0000", TIME_FORMAT).unwrap()); - assert_eq!(cert.valid_to, time::strptime("2050-11-24T11:42:07+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_from, DateTime::parse_from_str("2020-12-01T11:42:07+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_to, DateTime::parse_from_str("2050-11-24T11:42:07+0000", TIME_FORMAT).unwrap()); let cn = "cn.longlived"; let sans = set![cn, "san1.longlived", "san2.shortlived"]; @@ -610,8 +623,8 @@ pub mod tests { Not Before: Dec 1 11:41:19 2020 GMT Not After : Dec 2 11:41:19 2020 GMT */ - assert_eq!(cert.valid_from, time::strptime("2020-12-01T11:41:19+0000", TIME_FORMAT).unwrap()); - assert_eq!(cert.valid_to, time::strptime("2020-12-02T11:41:19+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_from, DateTime::parse_from_str("2020-12-01T11:41:19+0000", TIME_FORMAT).unwrap()); + assert_eq!(cert.valid_to, DateTime::parse_from_str("2020-12-02T11:41:19+0000", TIME_FORMAT).unwrap()); let config = create_test_kubernetes_config(false).faythe_config; let spec = create_test_certspec(cn, sans); diff --git a/src/file.rs b/src/file.rs index 75b46cf..52857ee 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,4 +1,3 @@ -extern crate time; extern crate walkdir; diff --git a/src/issuer.rs b/src/issuer.rs index f502b82..a739cc4 100644 --- a/src/issuer.rs +++ b/src/issuer.rs @@ -25,6 +25,8 @@ use crate::dns::DNSError; use crate::metrics; use crate::metrics::MetricsType; +use chrono::Utc; + pub fn process(faythe_config: FaytheConfig, rx: Receiver) { let mut queue: VecDeque = VecDeque::new(); @@ -82,7 +84,7 @@ fn check_queue(queue: &mut VecDeque) -> Result<(), IssuerError> { IssuerError::DNS(dns::DNSError::WrongAnswer(domain)) => { log::data("Wrong DNS answer", &domain); // Retry for two hours. Propagation on gratisdns is pretty slow. - if time::now_utc() < order.challenge_time + time::Duration::minutes(120) { + if Utc::now() < order.challenge_time + chrono::Duration::minutes(120) { queue.push_back(order); } else { log::data("giving up validating dns challenge for spec", &order.spec); @@ -169,7 +171,7 @@ fn setup_challenge(config: &FaytheConfig, spec: &CertSpec) -> Result, authorizations: Vec>, - challenge_time: time::Tm, + challenge_time: chrono::DateTime, auth_dns_servers: HashSet, val_dns_servers: HashSet, } diff --git a/src/kube.rs b/src/kube.rs index 5587b6d..f51abfa 100644 --- a/src/kube.rs +++ b/src/kube.rs @@ -2,7 +2,6 @@ extern crate serde; extern crate serde_json; extern crate base64; -extern crate time; use serde_json::{Value}; use serde_json::json; @@ -22,12 +21,14 @@ use acme_lib::Certificate; use base64::Engine; +use chrono::Utc; + #[derive(Debug, Clone)] pub struct Ingress { pub name: String, pub namespace: String, pub hosts: Vec, - pub touched: time::Tm, + pub touched: chrono::DateTime, } #[derive(Debug, Clone)] @@ -77,7 +78,7 @@ pub fn get_ingresses(config: &KubeMonitorConfig) -> Result, KubeErr let rules = vec(&i["spec"]["rules"])?; let touched = match &config.touch_annotation { Some(a) => tm(i["metadata"]["annotations"].get(&a)), - None => time::empty_tm() + None => chrono::DateTime::::MIN_UTC, }; ingresses.push(Ingress{ name: sr(&i["metadata"]["name"])?, @@ -135,12 +136,12 @@ fn sr(subject: &Value) -> Result { } } -fn tm(subject: Option<&Value>) -> time::Tm { +fn tm(subject: Option<&Value>) -> chrono::DateTime { let a = subject.and_then(|s| s.as_str()); match a { // assume "now" if there is something non-parsable in there - Some(a) => time::strptime(&a, self::TIME_FORMAT).unwrap_or(time::now_utc()), - _ => time::empty_tm() + Some(a) => chrono::DateTime::parse_from_str(&a, self::TIME_FORMAT).map(std::convert::Into::into).unwrap_or(Utc::now()), + _ => chrono::DateTime::::MIN_UTC, } } @@ -261,14 +262,13 @@ impl CertSpecable for Ingress { fn touch(&self, config: &ConfigContainer) -> Result<(), TouchError> { let monitor_config = config.get_kube_monitor_config()?; match &monitor_config.touch_annotation { - Some(a) => Ok(self.annotate(&a, - &time::strftime(TIME_FORMAT, &time::now_utc())?)?), + Some(a) => Ok(self.annotate(&a, &Utc::now().format(TIME_FORMAT).to_string())?), None => Ok(()) } } fn should_retry(&self, config: &ConfigContainer) -> bool { - time::now_utc() > self.touched + time::Duration::milliseconds(config.faythe_config.issue_grace as i64) + Utc::now() > self.touched + chrono::Duration::milliseconds(config.faythe_config.issue_grace as i64) } } @@ -303,15 +303,15 @@ impl std::convert::From for KubeError { KubeError::Format } } -impl std::convert::From for KubeError { - fn from(err: time::ParseError) -> Self { +impl std::convert::From for KubeError { + fn from(err: chrono::ParseError) -> Self { log::error("Failed to parse timestamp", &err); KubeError::Format } } -impl std::convert::From for TouchError { - fn from(_err: time::ParseError) -> Self { +impl std::convert::From for TouchError { + fn from(_err: chrono::ParseError) -> Self { TouchError::Failed } } diff --git a/src/monitor.rs b/src/monitor.rs index cf5825a..5c75673 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -1,5 +1,3 @@ -extern crate time; - use std::thread; use std::time::Duration; @@ -24,6 +22,8 @@ use std::prelude::v1::Vec; use crate::metrics; use crate::metrics::MetricsType; +use chrono::Utc; + pub fn monitor_k8s(config: ConfigContainer, tx: Sender) { log::info("k8s monitoring-started"); let monitor_config = config.get_kube_monitor_config().unwrap(); @@ -131,7 +131,6 @@ mod tests { use crate::mpsc; use crate::mpsc::{Receiver, Sender}; use std::collections::HashSet; - use std::ops::Add; fn create_channel() -> (Sender, Receiver) { mpsc::channel() @@ -141,7 +140,7 @@ mod tests { [Ingress { name: "test".to_string(), namespace: "test".to_string(), - touched: time::empty_tm(), + touched: chrono::DateTime::::MIN_UTC, hosts: [host.clone()].to_vec(), }] .to_vec() @@ -156,8 +155,8 @@ mod tests { cert: Cert { cn: host.clone(), sans, - valid_from: time::now_utc(), - valid_to: time::now_utc().add(time::Duration::days(valid_days)), + valid_from: Utc::now(), + valid_to: Utc::now() + chrono::Duration::days(valid_days), }, key: vec![], }