1- use std:: sync:: Arc ;
1+ use std:: { sync:: Arc , time :: SystemTime } ;
22
33use arc_swap:: ArcSwap ;
44use snafu:: { OptionExt , ResultExt , Snafu } ;
@@ -57,6 +57,11 @@ pub struct CertificateResolver {
5757 /// Using a [`ArcSwap`] (over e.g. [`tokio::sync::RwLock`]), so that we can easily
5858 /// (and performant) bridge between async write and sync read.
5959 current_certified_key : ArcSwap < CertifiedKey > ,
60+
61+ /// The wall-clock expiry time (`not_after`) of the current certificate.
62+ /// Used to detect clock drift between monotonic and wall-clock time.
63+ current_not_after : ArcSwap < SystemTime > ,
64+
6065 subject_alterative_dns_names : Arc < Vec < String > > ,
6166
6267 certificate_tx : mpsc:: Sender < Certificate > ,
@@ -68,7 +73,7 @@ impl CertificateResolver {
6873 certificate_tx : mpsc:: Sender < Certificate > ,
6974 ) -> Result < Self > {
7075 let subject_alterative_dns_names = Arc :: new ( subject_alterative_dns_names) ;
71- let certified_key = Self :: generate_new_certificate_inner (
76+ let ( certified_key, not_after ) = Self :: generate_new_certificate_inner (
7277 subject_alterative_dns_names. clone ( ) ,
7378 & certificate_tx,
7479 )
@@ -77,26 +82,51 @@ impl CertificateResolver {
7782 Ok ( Self {
7883 subject_alterative_dns_names,
7984 current_certified_key : ArcSwap :: new ( certified_key) ,
85+ current_not_after : ArcSwap :: new ( Arc :: new ( not_after) ) ,
8086 certificate_tx,
8187 } )
8288 }
8389
8490 pub async fn rotate_certificate ( & self ) -> Result < ( ) > {
85- let certified_key = self . generate_new_certificate ( ) . await ?;
91+ let ( certified_key, not_after ) = self . generate_new_certificate ( ) . await ?;
8692
8793 // TODO: Sign the new cert somehow with the old cert. See https://github.com/stackabletech/decisions/issues/56
8894 self . current_certified_key . store ( certified_key) ;
95+ self . current_not_after . store ( Arc :: new ( not_after) ) ;
8996
9097 Ok ( ( ) )
9198 }
9299
93- async fn generate_new_certificate ( & self ) -> Result < Arc < CertifiedKey > > {
100+ /// Returns `true` if the current certificate is expired or will expire
101+ /// within the given `buffer` duration according to wall-clock time.
102+ ///
103+ /// This catches cases where the monotonic timer (used by `tokio::time`)
104+ /// has drifted from wall-clock time, e.g. due to system hibernation.
105+ pub fn needs_rotation ( & self , buffer : std:: time:: Duration ) -> bool {
106+ let not_after = * * self . current_not_after . load ( ) ;
107+ // If subtraction underflows (buffer > time since epoch), fall back to
108+ // UNIX_EPOCH so that the comparison always triggers rotation.
109+ let deadline = not_after
110+ . checked_sub ( buffer)
111+ . unwrap_or ( SystemTime :: UNIX_EPOCH ) ;
112+
113+ tracing:: debug!(
114+ x509. subject_alterative_names = ?self . subject_alterative_dns_names,
115+ x509. not_after = %humantime:: format_rfc3339( not_after) ,
116+ deadline = %humantime:: format_rfc3339( deadline) ,
117+ "checking if certificate needs rotation"
118+ ) ;
119+
120+ SystemTime :: now ( ) >= deadline
121+ }
122+
123+ async fn generate_new_certificate ( & self ) -> Result < ( Arc < CertifiedKey > , SystemTime ) > {
94124 let subject_alterative_dns_names = self . subject_alterative_dns_names . clone ( ) ;
95125 Self :: generate_new_certificate_inner ( subject_alterative_dns_names, & self . certificate_tx )
96126 . await
97127 }
98128
99- /// Creates a new certificate and returns the certified key.
129+ /// Creates a new certificate and returns the certified key as well as `notAfter` timestamp .
100130 ///
101131 /// The certificate is send to the passed `cert_tx`.
102132 ///
@@ -106,7 +136,7 @@ impl CertificateResolver {
106136 async fn generate_new_certificate_inner (
107137 subject_alterative_dns_names : Arc < Vec < String > > ,
108138 certificate_tx : & mpsc:: Sender < Certificate > ,
109- ) -> Result < Arc < CertifiedKey > > {
139+ ) -> Result < ( Arc < CertifiedKey > , SystemTime ) > {
110140 // The certificate generations can take a while, so we use `spawn_blocking`
111141 let ( cert, certified_key) = tokio:: task:: spawn_blocking ( move || {
112142 let tls_provider =
@@ -144,12 +174,14 @@ impl CertificateResolver {
144174 . await
145175 . context ( TokioSpawnBlockingSnafu ) ??;
146176
177+ let not_after = cert. tbs_certificate . validity . not_after . to_system_time ( ) ;
178+
147179 certificate_tx
148180 . send ( cert)
149181 . await
150182 . map_err ( |_err| CertificateResolverError :: SendCertificateToChannel ) ?;
151183
152- Ok ( certified_key)
184+ Ok ( ( certified_key, not_after ) )
153185 }
154186}
155187
0 commit comments