@@ -226,6 +226,52 @@ pub fn bootstrap_archives(
226226 } )
227227}
228228
229+ /// Retain only stable launch inputs from the supervisor-private restart copy.
230+ /// Every container restart establishes a new authenticated listener generation.
231+ pub fn rotate_bootstrap_archives (
232+ sandbox_id : & str ,
233+ container_id : & str ,
234+ previous_channel : & [ u8 ] ,
235+ ) -> Result < BootstrapArchives , ComputeDriverError > {
236+ let mut archive = tar:: Archive :: new ( previous_channel) ;
237+ for entry in archive. entries ( ) . map_err ( invalid) ? {
238+ let entry = entry. map_err ( invalid) ?;
239+ if entry. path ( ) . map_err ( invalid) ?. as_ref ( ) != std:: path:: Path :: new ( "sandbox/bootstrap.json" )
240+ {
241+ continue ;
242+ }
243+ if !entry. header ( ) . entry_type ( ) . is_file ( ) {
244+ return Err ( invalid ( "restart bootstrap must be a regular file" ) ) ;
245+ }
246+ let previous: BoundaryConfig = serde_json:: from_reader ( entry) . map_err ( invalid) ?;
247+ if previous. boundary_id != sandbox_id
248+ || previous
249+ . resource_claims
250+ . get ( "podman.container_id" )
251+ . map ( String :: as_str)
252+ != Some ( container_id)
253+ || !matches ! ( & previous. driver_fence, DriverFenceEvidence :: Podman { container_id: bound, .. } if bound == container_id)
254+ {
255+ return Err ( invalid (
256+ "restart bootstrap does not belong to this Podman workload" ,
257+ ) ) ;
258+ }
259+ previous
260+ . driver_fence
261+ . validate_for_backend ( "podman" )
262+ . map_err ( invalid) ?;
263+ return bootstrap_archives (
264+ sandbox_id,
265+ container_id,
266+ & previous. workload_identity ,
267+ previous. child_env ,
268+ ) ;
269+ }
270+ Err ( invalid (
271+ "supervisor restart archive is missing sandbox/bootstrap.json" ,
272+ ) )
273+ }
274+
229275struct Archive < ' a > {
230276 builder : tar:: Builder < Vec < u8 > > ,
231277 identity : & ' a ResolvedWorkloadIdentity ,
@@ -379,4 +425,69 @@ mod tests {
379425 & archives. channel
380426 ) ;
381427 }
428+
429+ #[ test]
430+ fn restart_rotates_every_authentication_value_while_preserving_launch_inputs ( ) {
431+ let identity = ResolvedWorkloadIdentity :: new (
432+ 1000 ,
433+ 1001 ,
434+ vec ! [ ] ,
435+ "image" . into ( ) ,
436+ "sha256:image" . into ( ) ,
437+ )
438+ . unwrap ( ) ;
439+ let original = bootstrap_archives (
440+ "sandbox" ,
441+ "container" ,
442+ & identity,
443+ HashMap :: from ( [ ( "PATH" . into ( ) , "/usr/bin" . into ( ) ) ] ) ,
444+ )
445+ . unwrap ( ) ;
446+ let renewed = rotate_bootstrap_archives ( "sandbox" , "container" , & original. channel ) . unwrap ( ) ;
447+ let original_files = files ( & original. channel ) ;
448+ let renewed_files = files ( & renewed. channel ) ;
449+ let supervisor_files = files ( & renewed. supervisor ) ;
450+ let old: BoundaryConfig =
451+ serde_json:: from_slice ( & original_files[ & PathBuf :: from ( "sandbox/bootstrap.json" ) ] )
452+ . unwrap ( ) ;
453+ let new: BoundaryConfig =
454+ serde_json:: from_slice ( & renewed_files[ & PathBuf :: from ( "sandbox/bootstrap.json" ) ] )
455+ . unwrap ( ) ;
456+ let supervisor: BoundaryTopology = serde_json:: from_slice (
457+ & supervisor_files[ & PathBuf :: from ( TOPOLOGY_PATH . trim_start_matches ( '/' ) ) ] ,
458+ )
459+ . unwrap ( ) ;
460+ assert_ne ! ( new. generation, old. generation) ;
461+ assert_ne ! ( new. session_epoch, old. session_epoch) ;
462+ assert_ne ! ( new. bootstrap_token, old. bootstrap_token) ;
463+ assert_eq ! ( new. generation, supervisor. generation) ;
464+ assert_eq ! ( new. session_epoch, supervisor. session_epoch) ;
465+ assert_eq ! ( new. bootstrap_token, supervisor. bootstrap_token) ;
466+ assert_eq ! ( new. workload_identity, old. workload_identity) ;
467+ assert_eq ! ( new. resource_claims, old. resource_claims) ;
468+ assert_eq ! ( new. child_env, old. child_env) ;
469+ for path in [
470+ "sandbox/server.crt" ,
471+ "sandbox/server.key" ,
472+ "sandbox/client-ca.crt" ,
473+ ] {
474+ assert_ne ! (
475+ renewed_files[ & PathBuf :: from( path) ] ,
476+ original_files[ & PathBuf :: from( path) ]
477+ ) ;
478+ }
479+ let BoundaryTransport :: Unix { tls, .. } = supervisor. transport else {
480+ panic ! ( "expected Unix channel" )
481+ } ;
482+ assert_eq ! (
483+ tls. ca_certificate_pem. as_bytes( ) ,
484+ & renewed_files[ & PathBuf :: from( "sandbox/client-ca.crt" ) ]
485+ ) ;
486+ assert_eq ! (
487+ supervisor_files[ & PathBuf :: from( RESTART_BUNDLE_PATH . trim_start_matches( '/' ) ) ] ,
488+ renewed. channel
489+ ) ;
490+ assert ! ( rotate_bootstrap_archives( "other" , "container" , & original. channel) . is_err( ) ) ;
491+ assert ! ( rotate_bootstrap_archives( "sandbox" , "other" , & original. channel) . is_err( ) ) ;
492+ }
382493}
0 commit comments