@@ -51,12 +51,10 @@ pub enum CaError {
51
51
BadPurpose ,
52
52
#[ error( "path not a directory" ) ]
53
53
BadSpecDirectory ,
54
- #[ error( "failed to generate certificate" ) ]
55
- CertGenFail ,
56
- #[ error( "failed to create self signed cert for key" ) ]
57
- SelfCertGenFail ,
58
54
#[ error( "CA state directory has no key.spec" ) ]
59
55
NoKeySpec ,
56
+ #[ error( "openssl command failed" ) ]
57
+ OpensslCmdFail ,
60
58
}
61
59
62
60
// This is a template for the openssl config file used for all CAs.
@@ -386,20 +384,9 @@ impl Ca {
386
384
. arg ( "-out" )
387
385
. arg ( csr. path ( ) ) ;
388
386
389
- debug ! ( "executing command: \" {:#?}\" " , cmd) ;
390
- let output = match cmd. output ( ) {
391
- Ok ( o) => o,
392
- Err ( e) => {
393
- teardown_warn_only ( connector, pwd) ;
394
- return Err ( e. into ( ) ) ;
395
- }
396
- } ;
397
-
398
- if !output. status . success ( ) {
399
- warn ! ( "command failed with status: {}" , output. status) ;
400
- warn ! ( "stderr: \" {}\" " , String :: from_utf8_lossy( & output. stderr) ) ;
387
+ if let Err ( e) = execute_command ( & mut cmd) {
401
388
teardown_warn_only ( connector, pwd) ;
402
- return Err ( CaError :: SelfCertGenFail . into ( ) ) ;
389
+ return Err ( e ) ;
403
390
}
404
391
405
392
// return the path to the artifact created
@@ -408,9 +395,6 @@ impl Ca {
408
395
// else we'll get back the path to the CSR so that it can be exported
409
396
// and eventually certified by some external process
410
397
let pem = if spec. self_signed {
411
- // sleep to let sessions cycle
412
- thread:: sleep ( Duration :: from_millis ( 1500 ) ) ;
413
-
414
398
info ! ( "Generating self-signed cert for CA root" ) ;
415
399
let mut cmd = Command :: new ( "openssl" ) ;
416
400
cmd. arg ( "ca" )
@@ -432,29 +416,11 @@ impl Ca {
432
416
. arg ( "-in" )
433
417
. arg ( csr. path ( ) )
434
418
. arg ( "-out" )
435
- . arg ( CA_CERT )
436
- . output ( ) ?;
437
-
438
- debug ! ( "executing command: \" {:#?}\" " , cmd) ;
439
- let output = match cmd
440
- . output ( )
441
- . context ( "Failed to self sign cert with `openssl ca`" )
442
- {
443
- Ok ( o) => o,
444
- Err ( e) => {
445
- teardown_warn_only ( connector, pwd) ;
446
- return Err ( e) ;
447
- }
448
- } ;
449
-
450
- if !output. status . success ( ) {
451
- warn ! ( "command failed with status: {}" , output. status) ;
452
- warn ! (
453
- "stderr: \" {}\" " ,
454
- String :: from_utf8_lossy( & output. stderr)
455
- ) ;
419
+ . arg ( CA_CERT ) ;
420
+
421
+ if let Err ( e) = execute_command ( & mut cmd) {
456
422
teardown_warn_only ( connector, pwd) ;
457
- return Err ( CaError :: SelfCertGenFail . into ( ) ) ;
423
+ return Err ( e ) ;
458
424
}
459
425
460
426
let cert_pem =
@@ -498,9 +464,6 @@ impl Ca {
498
464
debug ! ( "writing CSR to: {}" , csr. path( ) . display( ) ) ;
499
465
fs:: write ( & csr, & spec. csr ) ?;
500
466
501
- // sleep to let sessions cycle
502
- thread:: sleep ( Duration :: from_millis ( 2500 ) ) ;
503
-
504
467
info ! (
505
468
"Generating cert from CSR & signing with key: {}" ,
506
469
self . name( )
@@ -532,28 +495,17 @@ impl Ca {
532
495
. arg ( "-out" )
533
496
. arg ( cert. path ( ) ) ;
534
497
535
- debug ! ( "executing command: \" {:#?}\" " , cmd) ;
536
- let output = match cmd. output ( ) {
537
- Ok ( o) => o,
538
- Err ( e) => {
539
- teardown_warn_only ( connector, pwd) ;
540
- return Err ( e. into ( ) ) ;
541
- }
542
- } ;
543
-
544
- if !output. status . success ( ) {
545
- warn ! ( "command failed with status: {}" , output. status) ;
546
- warn ! ( "stderr: \" {}\" " , String :: from_utf8_lossy( & output. stderr) ) ;
498
+ if let Err ( e) = execute_command ( & mut cmd) {
547
499
teardown_warn_only ( connector, pwd) ;
548
- return Err ( CaError :: CertGenFail . into ( ) ) ;
549
- } else {
550
- debug ! (
551
- "Successfully signed CsrSpec \" {}\" producing cert \" {}\" " ,
552
- csr. path( ) . display( ) ,
553
- cert. path( ) . display( )
554
- ) ;
500
+ return Err ( e) ;
555
501
}
556
502
503
+ debug ! (
504
+ "Successfully signed CsrSpec \" {}\" producing cert \" {}\" " ,
505
+ csr. path( ) . display( ) ,
506
+ cert. path( ) . display( )
507
+ ) ;
508
+
557
509
teardown_warn_only ( connector, pwd) ;
558
510
fs:: read ( cert. path ( ) ) . with_context ( || {
559
511
format ! ( "failed to read file {}" , cert. as_ref( ) . display( ) )
@@ -715,3 +667,47 @@ fn teardown_warn_only<P: AsRef<Path>>(mut conn: Child, ret_path: P) {
715
667
) ;
716
668
}
717
669
}
670
+
671
+ const RETRY_MAX : usize = 7 ;
672
+ const RETRY_SLEEP : u64 = 5000 ;
673
+ const ALWAYS_SLEEP : u64 = 2000 ;
674
+
675
+ /// Execute a command with sleep / retry logic specifically tuned for this
676
+ /// use-case. The YubiHSM supports 16 sessions. Unused sessions are reclaimed
677
+ /// after 30 seconds of inactivity. For whatever reason using the YubiHSM as a
678
+ /// backend for openssl through the pkcs#11 provider causes session exhaustion.
679
+ /// Likely someone isn't cleaning up after themselves. To work around this we
680
+ /// do two things:
681
+ /// - Before executing any openssl command we sleep for 2 seconds. This is
682
+ /// intended to keep us from using up all of the available sessions rapidly.
683
+ /// - Implement a retry loop as a last line of defense that will run out the
684
+ /// session clock in its entirety if necessary.
685
+ fn execute_command ( cmd : & mut Command ) -> Result < ( ) > {
686
+ for retry in 0 ..RETRY_MAX {
687
+ debug ! ( "attempt {retry} at executing command: \" {:#?}\" " , cmd) ;
688
+ let output = cmd
689
+ . output ( )
690
+ . with_context ( || format ! ( "failed to execute command: {cmd:?}" ) ) ?;
691
+
692
+ if output. status . success ( ) {
693
+ debug ! ( "command executed successfully" ) ;
694
+ // Sleep unconditionally after success to run down the session
695
+ // reclamation timer on the YubiHSM a bit.
696
+ thread:: sleep ( Duration :: from_millis ( ALWAYS_SLEEP ) ) ;
697
+ break ;
698
+ } else {
699
+ // sleep & retry on any failure
700
+ warn ! ( "command failed with status: {}" , output. status) ;
701
+ warn ! ( "stderr: \" {}\" " , String :: from_utf8_lossy( & output. stderr) ) ;
702
+ if retry == RETRY_MAX - 1 {
703
+ error ! ( "giving up after {RETRY_MAX} attempts" ) ;
704
+ return Err ( CaError :: OpensslCmdFail . into ( ) ) ;
705
+ }
706
+
707
+ info ! ( "attempt {retry} failed, waiting before retrying ..." ) ;
708
+ thread:: sleep ( Duration :: from_millis ( RETRY_SLEEP ) ) ;
709
+ }
710
+ }
711
+
712
+ Ok ( ( ) )
713
+ }
0 commit comments