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,9 @@ 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+ /// The wall-clock expiry time (`not_after`) of the current certificate.
61+ /// Used to detect clock drift between monotonic and wall-clock time.
62+ current_not_after : ArcSwap < SystemTime > ,
6063 subject_alterative_dns_names : Arc < Vec < String > > ,
6164
6265 certificate_tx : mpsc:: Sender < Certificate > ,
@@ -68,7 +71,7 @@ impl CertificateResolver {
6871 certificate_tx : mpsc:: Sender < Certificate > ,
6972 ) -> Result < Self > {
7073 let subject_alterative_dns_names = Arc :: new ( subject_alterative_dns_names) ;
71- let certified_key = Self :: generate_new_certificate_inner (
74+ let ( certified_key, not_after ) = Self :: generate_new_certificate_inner (
7275 subject_alterative_dns_names. clone ( ) ,
7376 & certificate_tx,
7477 )
@@ -77,20 +80,37 @@ impl CertificateResolver {
7780 Ok ( Self {
7881 subject_alterative_dns_names,
7982 current_certified_key : ArcSwap :: new ( certified_key) ,
83+ current_not_after : ArcSwap :: new ( Arc :: new ( not_after) ) ,
8084 certificate_tx,
8185 } )
8286 }
8387
8488 pub async fn rotate_certificate ( & self ) -> Result < ( ) > {
85- let certified_key = self . generate_new_certificate ( ) . await ?;
89+ let ( certified_key, not_after ) = self . generate_new_certificate ( ) . await ?;
8690
8791 // TODO: Sign the new cert somehow with the old cert. See https://github.com/stackabletech/decisions/issues/56
8892 self . current_certified_key . store ( certified_key) ;
93+ self . current_not_after . store ( Arc :: new ( not_after) ) ;
8994
9095 Ok ( ( ) )
9196 }
9297
93- async fn generate_new_certificate ( & self ) -> Result < Arc < CertifiedKey > > {
98+ /// Returns `true` if the current certificate is expired or will expire
99+ /// within the given `buffer` duration according to wall-clock time.
100+ ///
101+ /// This catches cases where the monotonic timer (used by `tokio::time`)
102+ /// has drifted from wall-clock time, e.g. due to system hibernation.
103+ pub fn needs_rotation ( & self , buffer : std:: time:: Duration ) -> bool {
104+ let not_after = * * self . current_not_after . load ( ) ;
105+ // If subtraction underflows (buffer > time since epoch), fall back to
106+ // UNIX_EPOCH so that the comparison always triggers rotation.
107+ let deadline = not_after
108+ . checked_sub ( buffer)
109+ . unwrap_or ( SystemTime :: UNIX_EPOCH ) ;
110+ SystemTime :: now ( ) >= deadline
111+ }
112+
113+ async fn generate_new_certificate ( & self ) -> Result < ( Arc < CertifiedKey > , SystemTime ) > {
94114 let subject_alterative_dns_names = self . subject_alterative_dns_names . clone ( ) ;
95115 Self :: generate_new_certificate_inner ( subject_alterative_dns_names, & self . certificate_tx )
96116 . await
@@ -106,7 +126,7 @@ impl CertificateResolver {
106126 async fn generate_new_certificate_inner (
107127 subject_alterative_dns_names : Arc < Vec < String > > ,
108128 certificate_tx : & mpsc:: Sender < Certificate > ,
109- ) -> Result < Arc < CertifiedKey > > {
129+ ) -> Result < ( Arc < CertifiedKey > , SystemTime ) > {
110130 // The certificate generations can take a while, so we use `spawn_blocking`
111131 let ( cert, certified_key) = tokio:: task:: spawn_blocking ( move || {
112132 let tls_provider =
@@ -144,12 +164,14 @@ impl CertificateResolver {
144164 . await
145165 . context ( TokioSpawnBlockingSnafu ) ??;
146166
167+ let not_after = cert. tbs_certificate . validity . not_after . to_system_time ( ) ;
168+
147169 certificate_tx
148170 . send ( cert)
149171 . await
150172 . map_err ( |_err| CertificateResolverError :: SendCertificateToChannel ) ?;
151173
152- Ok ( certified_key)
174+ Ok ( ( certified_key, not_after ) )
153175 }
154176}
155177
0 commit comments