@@ -80,19 +80,40 @@ impl CaBundle {
8080/// Returns `Err` only on invalid PEM data in a file that was otherwise
8181/// readable. Missing files / missing env vars are not errors.
8282pub fn load_ca_bundle ( extra_paths : & [ PathBuf ] ) -> crate :: Result < CaBundle > {
83+ let env = EnvSources {
84+ crab_bundle : std:: env:: var ( CRAB_CA_BUNDLE_ENV ) . ok ( ) ,
85+ ssl_cert_file : std:: env:: var ( SSL_CERT_FILE_ENV ) . ok ( ) ,
86+ ssl_cert_dir : std:: env:: var ( SSL_CERT_DIR_ENV ) . ok ( ) ,
87+ } ;
88+ load_ca_bundle_with_env ( & env, extra_paths)
89+ }
90+
91+ /// Env-var inputs to the CA loader.
92+ ///
93+ /// Split out from [`load_ca_bundle`] so tests can inject a hermetic
94+ /// environment instead of reading the process's real env (which on CI
95+ /// runners includes system-wide cert bundles that pollute assertions).
96+ #[ derive( Debug , Default , Clone ) ]
97+ struct EnvSources {
98+ crab_bundle : Option < String > ,
99+ ssl_cert_file : Option < String > ,
100+ ssl_cert_dir : Option < String > ,
101+ }
102+
103+ fn load_ca_bundle_with_env ( env : & EnvSources , extra_paths : & [ PathBuf ] ) -> crate :: Result < CaBundle > {
83104 let mut bundle = CaBundle :: default ( ) ;
84105
85106 // 1. CRAB_CA_BUNDLE (single file, takes priority)
86- if let Ok ( path) = std :: env:: var ( CRAB_CA_BUNDLE_ENV ) {
87- try_load_file ( & mut bundle, Path :: new ( & path) ) ;
107+ if let Some ( path) = env. crab_bundle . as_deref ( ) {
108+ try_load_file ( & mut bundle, Path :: new ( path) ) ;
88109 }
89110 // 2. SSL_CERT_FILE
90- if let Ok ( path) = std :: env:: var ( SSL_CERT_FILE_ENV ) {
91- try_load_file ( & mut bundle, Path :: new ( & path) ) ;
111+ if let Some ( path) = env. ssl_cert_file . as_deref ( ) {
112+ try_load_file ( & mut bundle, Path :: new ( path) ) ;
92113 }
93114 // 3. SSL_CERT_DIR
94- if let Ok ( dir) = std :: env:: var ( SSL_CERT_DIR_ENV ) {
95- try_load_dir ( & mut bundle, Path :: new ( & dir) ) ;
115+ if let Some ( dir) = env. ssl_cert_dir . as_deref ( ) {
116+ try_load_dir ( & mut bundle, Path :: new ( dir) ) ;
96117 }
97118 // 4. Caller extras
98119 for path in extra_paths {
@@ -256,13 +277,19 @@ mod tests {
256277 ) ;
257278 }
258279
280+ /// Hermetic load: no env vars, only explicit paths. Used by tests to
281+ /// avoid picking up the CI runner's system cert bundle (e.g. Ubuntu
282+ /// sets `SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt`, which
283+ /// would add hundreds of certs and break `assert_eq!(b.len(), N)`).
284+ fn load_hermetic ( extra_paths : & [ PathBuf ] ) -> CaBundle {
285+ load_ca_bundle_with_env ( & EnvSources :: default ( ) , extra_paths) . unwrap ( )
286+ }
287+
259288 #[ test]
260289 fn load_missing_env_returns_empty_bundle ( ) {
261- // None of the env vars are set in this test; nothing loaded.
262- let b = load_ca_bundle ( & [ ] ) . unwrap ( ) ;
263- // May not be empty if the user's env already has SSL_CERT_FILE set,
264- // so check the weaker property: no-extra-paths load succeeds.
265- let _ = b;
290+ let b = load_hermetic ( & [ ] ) ;
291+ assert ! ( b. is_empty( ) ) ;
292+ assert_eq ! ( b. len( ) , 0 ) ;
266293 }
267294
268295 #[ test]
@@ -272,7 +299,7 @@ mod tests {
272299 let mut f = fs:: File :: create ( & path) . unwrap ( ) ;
273300 f. write_all ( CERT_A . as_bytes ( ) ) . unwrap ( ) ;
274301
275- let b = load_ca_bundle ( std:: slice:: from_ref ( & path) ) . unwrap ( ) ;
302+ let b = load_hermetic ( std:: slice:: from_ref ( & path) ) ;
276303 assert_eq ! ( b. len( ) , 1 ) ;
277304 assert_eq ! ( b. sources, vec![ path] ) ;
278305 }
@@ -284,15 +311,15 @@ mod tests {
284311 fs:: write ( tmp. path ( ) . join ( "b.crt" ) , CERT_B ) . unwrap ( ) ;
285312 fs:: write ( tmp. path ( ) . join ( "readme.txt" ) , "ignored" ) . unwrap ( ) ;
286313
287- let b = load_ca_bundle ( std:: slice:: from_ref ( & tmp. path ( ) . to_path_buf ( ) ) ) . unwrap ( ) ;
314+ let b = load_hermetic ( std:: slice:: from_ref ( & tmp. path ( ) . to_path_buf ( ) ) ) ;
288315 assert_eq ! ( b. len( ) , 2 ) ;
289316 assert_eq ! ( b. sources. len( ) , 2 ) ;
290317 }
291318
292319 #[ test]
293320 fn load_extra_path_nonexistent_is_silent ( ) {
294- let b = load_ca_bundle ( & [ PathBuf :: from ( "/definitely/does/not/exist/bundle.pem" ) ] ) . unwrap ( ) ;
295- let _ = b ;
321+ let b = load_hermetic ( & [ PathBuf :: from ( "/definitely/does/not/exist/bundle.pem" ) ] ) ;
322+ assert ! ( b . is_empty ( ) ) ;
296323 }
297324
298325 #[ test]
@@ -301,7 +328,7 @@ mod tests {
301328 let path = tmp. path ( ) . join ( "empty.pem" ) ;
302329 fs:: write ( & path, "just some text, no BEGIN CERTIFICATE" ) . unwrap ( ) ;
303330
304- let b = load_ca_bundle ( std:: slice:: from_ref ( & path) ) . unwrap ( ) ;
331+ let b = load_hermetic ( std:: slice:: from_ref ( & path) ) ;
305332 // The file was readable but had no valid blocks; bundle stays empty.
306333 assert_eq ! ( b. len( ) , 0 ) ;
307334 }
0 commit comments