@@ -47,6 +47,7 @@ pub struct ProxyConfig {
4747 pub connect_timeout : Duration ,
4848 pub ocsp_max_ttl : Duration ,
4949 pub ocsp_default_ttl : Duration ,
50+ pub ocsp_refresh_before : Duration ,
5051 pub rim_ttl : Duration ,
5152 pub max_cache_entries_per_kind : usize ,
5253}
@@ -60,6 +61,7 @@ impl ProxyConfig {
6061 ( "connect timeout" , self . connect_timeout ) ,
6162 ( "OCSP maximum TTL" , self . ocsp_max_ttl ) ,
6263 ( "OCSP default TTL" , self . ocsp_default_ttl ) ,
64+ ( "OCSP refresh window" , self . ocsp_refresh_before ) ,
6365 ( "RIM TTL" , self . rim_ttl ) ,
6466 ] {
6567 if value. is_zero ( ) {
@@ -205,22 +207,37 @@ impl Proxy {
205207 }
206208 } ;
207209 if let Some ( entry) = self . cache_get ( "ocsp" , & key) . await {
208- return cached_response ( entry, "HIT" ) ;
210+ if !self . ocsp_needs_refresh ( & entry) {
211+ return cached_response ( entry, "HIT" ) ;
212+ }
209213 }
210214
211215 let fill = self . fill_lock ( "ocsp" , & key) ;
212216 let _fill = fill. lock ( ) . await ;
213- if let Some ( entry) = self . cache_get ( "ocsp" , & key) . await {
214- return cached_response ( entry, "HIT" ) ;
217+ let cached = self . cache_get ( "ocsp" , & key) . await ;
218+ if let Some ( entry) = & cached {
219+ if !self . ocsp_needs_refresh ( entry) {
220+ return cached_response ( entry. clone ( ) , "HIT" ) ;
221+ }
215222 }
216223
217224 let upstream = match self . fetch_ocsp ( body) . await {
218225 Ok ( response) => response,
219226 Err ( error) => {
220227 warn ! ( %error, "OCSP upstream request failed" ) ;
228+ if let Some ( entry) = cached {
229+ return cached_response ( entry, "HIT" ) ;
230+ }
221231 return text_response ( StatusCode :: BAD_GATEWAY , "OCSP upstream unavailable\n " ) ;
222232 }
223233 } ;
234+ if upstream. status != StatusCode :: OK {
235+ if let Some ( entry) = & cached {
236+ warn ! ( status = %upstream. status, "OCSP refresh returned an error; using valid cached response" ) ;
237+ return cached_response ( entry. clone ( ) , "HIT" ) ;
238+ }
239+ }
240+ let cache_state = if cached. is_some ( ) { "REFRESH" } else { "MISS" } ;
224241 if upstream. status == StatusCode :: OK {
225242 let now = now_epoch ( ) ;
226243 match ocsp:: response_cache_expiry (
@@ -250,7 +267,7 @@ impl Proxy {
250267 }
251268 }
252269 }
253- upstream_response ( upstream, "MISS" )
270+ upstream_response ( upstream, cache_state )
254271 }
255272
256273 async fn handle_rim ( & self , rim_id : & str ) -> Response < Full < Bytes > > {
@@ -345,6 +362,11 @@ impl Proxy {
345362 }
346363 }
347364
365+ fn ocsp_needs_refresh ( & self , entry : & CacheEntry ) -> bool {
366+ entry. expires_at . saturating_sub ( now_epoch ( ) )
367+ <= self . config . ocsp_refresh_before . as_secs ( ) as i64
368+ }
369+
348370 fn fill_lock ( & self , namespace : & str , key : & str ) -> Arc < Mutex < ( ) > > {
349371 self . fills
350372 . entry ( format ! ( "{namespace}:{key}" ) )
@@ -498,6 +520,10 @@ mod tests {
498520 }
499521
500522 fn ocsp_response ( now : i64 ) -> Bytes {
523+ ocsp_response_with_ttl ( now, 3600 )
524+ }
525+
526+ fn ocsp_response_with_ttl ( now : i64 , ttl : i64 ) -> Bytes {
501527 let format = |timestamp| {
502528 Utc . timestamp_opt ( timestamp, 0 )
503529 . unwrap ( )
@@ -509,7 +535,7 @@ mod tests {
509535 cert_id,
510536 der ( 0x80 , & [ ] ) ,
511537 der ( 0x18 , format ( now - 60 ) . as_bytes ( ) ) ,
512- der ( 0xa0 , & der ( 0x18 , format ( now + 3600 ) . as_bytes ( ) ) ) ,
538+ der ( 0xa0 , & der ( 0x18 , format ( now + ttl ) . as_bytes ( ) ) ) ,
513539 ] ) ;
514540 let response_data = sequence ( & [
515541 der ( 0x82 , & [ 7 ; 20 ] ) ,
@@ -590,6 +616,7 @@ mod tests {
590616 connect_timeout : Duration :: from_secs ( 1 ) ,
591617 ocsp_max_ttl : Duration :: from_secs ( 86_400 ) ,
592618 ocsp_default_ttl : Duration :: from_secs ( 3600 ) ,
619+ ocsp_refresh_before : Duration :: from_secs ( 300 ) ,
593620 rim_ttl : Duration :: from_secs ( 86_400 ) ,
594621 max_cache_entries_per_kind : 10 ,
595622 } ;
@@ -616,6 +643,45 @@ mod tests {
616643 upstream. abort ( ) ;
617644 }
618645
646+ #[ tokio:: test]
647+ async fn ocsp_cache_refreshes_before_expiry ( ) {
648+ let now = now_epoch ( ) ;
649+ let response = ocsp_response_with_ttl ( now, 60 ) ;
650+ let ( upstream_url, requests, upstream) = mock_upstream ( response, OCSP_CONTENT_TYPE ) . await ;
651+ let cache_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
652+ let proxy = Proxy :: new ( ProxyConfig {
653+ listen_addr : "127.0.0.1:0" . parse ( ) . unwrap ( ) ,
654+ cache_dir : cache_dir. path ( ) . to_path_buf ( ) ,
655+ ocsp_url : upstream_url,
656+ rim_url : Url :: parse ( "https://rim.attestation.nvidia.com" ) . unwrap ( ) ,
657+ service_key : None ,
658+ request_timeout : Duration :: from_secs ( 5 ) ,
659+ connect_timeout : Duration :: from_secs ( 1 ) ,
660+ ocsp_max_ttl : Duration :: from_secs ( 86_400 ) ,
661+ ocsp_default_ttl : Duration :: from_secs ( 3600 ) ,
662+ ocsp_refresh_before : Duration :: from_secs ( 300 ) ,
663+ rim_ttl : Duration :: from_secs ( 86_400 ) ,
664+ max_cache_entries_per_kind : 10 ,
665+ } )
666+ . await
667+ . unwrap ( ) ;
668+
669+ let nonce = std:: process:: id ( ) as u8 ;
670+ assert_eq ! (
671+ proxy. handle_ocsp( ocsp_request( nonce) ) . await . headers( ) [ "x-dstack-cache" ] ,
672+ "MISS"
673+ ) ;
674+ assert_eq ! (
675+ proxy
676+ . handle_ocsp( ocsp_request( nonce. wrapping_add( 1 ) ) )
677+ . await
678+ . headers( ) [ "x-dstack-cache" ] ,
679+ "REFRESH"
680+ ) ;
681+ assert_eq ! ( requests. load( Ordering :: SeqCst ) , 2 ) ;
682+ upstream. abort ( ) ;
683+ }
684+
619685 #[ tokio:: test]
620686 async fn rim_cache_uses_versioned_id ( ) {
621687 let response = Bytes :: from_static ( br#"{"id":"NV_GPU_DRIVER_GH100_580.1","rim":"test"}"# ) ;
@@ -632,6 +698,7 @@ mod tests {
632698 connect_timeout : Duration :: from_secs ( 1 ) ,
633699 ocsp_max_ttl : Duration :: from_secs ( 86_400 ) ,
634700 ocsp_default_ttl : Duration :: from_secs ( 3600 ) ,
701+ ocsp_refresh_before : Duration :: from_secs ( 300 ) ,
635702 rim_ttl : Duration :: from_secs ( 86_400 ) ,
636703 max_cache_entries_per_kind : 10 ,
637704 } )
0 commit comments