1
1
//! Dynamically provisions and picks Certificate Authorities.
2
2
3
- use std:: { collections:: BTreeMap , fmt:: Display } ;
3
+ use std:: { collections:: BTreeMap , ffi :: OsStr , fmt:: Display , path :: Path } ;
4
4
5
5
use openssl:: {
6
6
asn1:: { Asn1Integer , Asn1Time } ,
@@ -34,7 +34,7 @@ use tracing::{info, info_span, warn};
34
34
35
35
use crate :: {
36
36
backend:: SecretBackendError ,
37
- crd:: CertificateKeyGeneration ,
37
+ crd:: { AdditionalTrustRoot , CertificateKeyGeneration } ,
38
38
utils:: { asn1time_to_offsetdatetime, Asn1TimeParseError , Unloggable } ,
39
39
} ;
40
40
@@ -55,8 +55,8 @@ pub enum Error {
55
55
#[ snafu( display( "failed to generate certificate key" ) ) ]
56
56
GenerateKey { source : openssl:: error:: ErrorStack } ,
57
57
58
- #[ snafu( display( "failed to load CA {secret}" ) ) ]
59
- FindCa {
58
+ #[ snafu( display( "failed to load {secret}" ) ) ]
59
+ FindSecret {
60
60
source : kube:: Error ,
61
61
secret : ObjectRef < Secret > ,
62
62
} ,
@@ -77,6 +77,12 @@ pub enum Error {
77
77
secret : ObjectRef < Secret > ,
78
78
} ,
79
79
80
+ #[ snafu( display( "unsupported certificate format in key {key:?} of {secret}; supported extensions: .cer, .cert, .crt, .pem" ) ) ]
81
+ UnsupportedCertificateFormat {
82
+ key : String ,
83
+ secret : ObjectRef < Secret > ,
84
+ } ,
85
+
80
86
#[ snafu( display( "failed to parse CA lifetime from key {key:?} of {secret}" ) ) ]
81
87
ParseLifetime {
82
88
source : Asn1TimeParseError ,
@@ -106,9 +112,10 @@ impl SecretBackendError for Error {
106
112
match self {
107
113
Error :: GenerateKey { .. } => tonic:: Code :: Internal ,
108
114
Error :: MissingCertificate { .. } => tonic:: Code :: FailedPrecondition ,
109
- Error :: FindCa { .. } => tonic:: Code :: Unavailable ,
115
+ Error :: FindSecret { .. } => tonic:: Code :: Unavailable ,
110
116
Error :: CaNotFoundAndGenDisabled { .. } => tonic:: Code :: FailedPrecondition ,
111
117
Error :: LoadCertificate { .. } => tonic:: Code :: FailedPrecondition ,
118
+ Error :: UnsupportedCertificateFormat { .. } => tonic:: Code :: InvalidArgument ,
112
119
Error :: ParseLifetime { .. } => tonic:: Code :: FailedPrecondition ,
113
120
Error :: BuildCertificate { .. } => tonic:: Code :: FailedPrecondition ,
114
121
Error :: SerializeCertificate { .. } => tonic:: Code :: FailedPrecondition ,
@@ -293,20 +300,22 @@ impl CertificateAuthority {
293
300
#[ derive( Debug ) ]
294
301
pub struct Manager {
295
302
certificate_authorities : Vec < CertificateAuthority > ,
303
+ additional_trusted_certificates : Vec < X509 > ,
296
304
}
297
305
298
306
impl Manager {
299
307
pub async fn load_or_create (
300
308
client : & stackable_operator:: client:: Client ,
301
309
secret_ref : & SecretReference ,
310
+ additional_trust_roots : & [ AdditionalTrustRoot ] ,
302
311
config : & Config ,
303
312
) -> Result < Self > {
304
313
// Use entry API rather than apply so that we crash and retry on conflicts (to avoid creating spurious certs that we throw away immediately)
305
314
let secrets_api = & client. get_api :: < Secret > ( & secret_ref. namespace ) ;
306
315
let ca_secret = secrets_api
307
316
. entry ( & secret_ref. name )
308
317
. await
309
- . with_context ( |_| FindCaSnafu { secret : secret_ref } ) ?;
318
+ . with_context ( |_| FindSecretSnafu { secret : secret_ref } ) ?;
310
319
let mut update_ca_secret = false ;
311
320
let mut certificate_authorities = match & ca_secret {
312
321
Entry :: Occupied ( ca_secret) => {
@@ -441,11 +450,67 @@ impl Manager {
441
450
return SaveRequestedButForbiddenSnafu . fail ( ) ;
442
451
}
443
452
}
453
+
454
+ let mut additional_trusted_certificates = vec ! [ ] ;
455
+ for AdditionalTrustRoot { secret } in additional_trust_roots {
456
+ additional_trusted_certificates
457
+ . extend ( Self :: read_certificates_from_secret ( client, secret) . await ?) ;
458
+ }
459
+
444
460
Ok ( Self {
445
461
certificate_authorities,
462
+ additional_trusted_certificates,
446
463
} )
447
464
}
448
465
466
+ /// Read certificates from the given Secret
467
+ ///
468
+ /// The keys are assumed to be filenames and their extensions denote the expected format of the
469
+ /// certificate.
470
+ async fn read_certificates_from_secret (
471
+ client : & stackable_operator:: client:: Client ,
472
+ secret_ref : & SecretReference ,
473
+ ) -> Result < Vec < X509 > > {
474
+ let mut certificates = vec ! [ ] ;
475
+
476
+ let secrets_api = & client. get_api :: < Secret > ( & secret_ref. namespace ) ;
477
+ let secret = secrets_api
478
+ . get ( & secret_ref. name )
479
+ . await
480
+ . with_context ( |_| FindSecretSnafu { secret : secret_ref } ) ?;
481
+
482
+ let secret_data = secret. data . unwrap_or_default ( ) ;
483
+ for ( key, ByteString ( value) ) in & secret_data {
484
+ let extension = Path :: new ( key) . extension ( ) . and_then ( OsStr :: to_str) ;
485
+ let certs = match extension {
486
+ Some ( "pem" ) => X509 :: stack_from_pem ( value) ,
487
+ Some ( "cer" ) | Some ( "cert" ) | Some ( "crt" ) => X509 :: from_der ( value)
488
+ . map ( |cert| vec ! [ cert] )
489
+ . or ( X509 :: stack_from_pem ( value) ) ,
490
+ _ => {
491
+ return UnsupportedCertificateFormatSnafu {
492
+ key,
493
+ secret : secret_ref,
494
+ }
495
+ . fail ( ) ;
496
+ }
497
+ }
498
+ . context ( LoadCertificateSnafu {
499
+ key,
500
+ secret : secret_ref,
501
+ } ) ?;
502
+ info ! (
503
+ "Add the certificate(s) {certs:?} from the key [{key}] of [{secret_ref}] to the additional trust roots." ,
504
+ certs = certs,
505
+ secret_ref = secret_ref,
506
+ key = key,
507
+ ) ;
508
+ certificates. extend ( certs) ;
509
+ }
510
+
511
+ Ok ( certificates)
512
+ }
513
+
449
514
/// Get an appropriate [`CertificateAuthority`] for signing a given certificate.
450
515
pub fn find_certificate_authority_for_signing (
451
516
& self ,
@@ -467,5 +532,6 @@ impl Manager {
467
532
self . certificate_authorities
468
533
. iter ( )
469
534
. map ( |ca| & ca. certificate )
535
+ . chain ( & self . additional_trusted_certificates )
470
536
}
471
537
}
0 commit comments