@@ -240,28 +240,6 @@ impl From<Encryption> for EncryptionRaw {
240240 }
241241}
242242
243- /// Description of signing keys.
244- ///
245- /// It either holds the key config values directly or references a directory
246- /// where each file contains a key.
247- #[ derive( Debug , Clone ) ]
248- pub enum Keys {
249- Values ( Vec < KeyConfig > ) ,
250- Directory ( Utf8PathBuf ) ,
251- }
252-
253- impl Keys {
254- /// Returns a list of key configs.
255- ///
256- /// If `keys_dir` was given, the keys are read from file.
257- async fn key_configs ( & self ) -> anyhow:: Result < Vec < KeyConfig > > {
258- match self {
259- Keys :: Values ( key_configs) => Ok ( key_configs. clone ( ) ) ,
260- Keys :: Directory ( path) => key_configs_from_path ( path) . await ,
261- }
262- }
263- }
264-
265243/// Reads all keys from the given directory.
266244async fn key_configs_from_path ( path : & Utf8PathBuf ) -> anyhow:: Result < Vec < KeyConfig > > {
267245 let mut result = vec ! [ ] ;
@@ -279,47 +257,6 @@ async fn key_configs_from_path(path: &Utf8PathBuf) -> anyhow::Result<Vec<KeyConf
279257 Ok ( result)
280258}
281259
282- #[ serde_as]
283- #[ derive( JsonSchema , Serialize , Deserialize , Debug , Clone ) ]
284- struct KeysRaw {
285- /// List of private keys to use for signing and encrypting payloads.
286- #[ serde( skip_serializing_if = "Option::is_none" ) ]
287- keys : Option < Vec < KeyConfig > > ,
288-
289- /// Directory of private keys to use for signing and encrypting payloads.
290- #[ schemars( with = "Option<String>" ) ]
291- #[ serde( skip_serializing_if = "Option::is_none" ) ]
292- keys_dir : Option < Utf8PathBuf > ,
293- }
294-
295- impl TryFrom < KeysRaw > for Keys {
296- type Error = anyhow:: Error ;
297-
298- fn try_from ( value : KeysRaw ) -> Result < Keys , Self :: Error > {
299- match ( value. keys , value. keys_dir ) {
300- ( None , None ) => bail ! ( "Missing `keys` or `keys_dir`" ) ,
301- ( None , Some ( path) ) => Ok ( Keys :: Directory ( path) ) ,
302- ( Some ( keys) , None ) => Ok ( Keys :: Values ( keys) ) ,
303- ( Some ( _) , Some ( _) ) => bail ! ( "Cannot specify both `keys` and `keys_dir`" ) ,
304- }
305- }
306- }
307-
308- impl From < Keys > for KeysRaw {
309- fn from ( value : Keys ) -> Self {
310- match value {
311- Keys :: Directory ( path) => KeysRaw {
312- keys_dir : Some ( path) ,
313- keys : None ,
314- } ,
315- Keys :: Values ( keys) => KeysRaw {
316- keys_dir : None ,
317- keys : Some ( keys) ,
318- } ,
319- }
320- }
321- }
322-
323260/// Application secrets
324261#[ serde_as]
325262#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
@@ -330,11 +267,14 @@ pub struct SecretsConfig {
330267 #[ serde( flatten) ]
331268 encryption : Encryption ,
332269
333- /// List of private keys to use for signing and encrypting payloads
334- #[ schemars( with = "KeysRaw" ) ]
335- #[ serde_as( as = "serde_with::TryFromInto<KeysRaw>" ) ]
336- #[ serde( flatten) ]
337- keys : Keys ,
270+ /// List of private keys to use for signing and encrypting payloads.
271+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
272+ keys : Option < Vec < KeyConfig > > ,
273+
274+ /// Directory of private keys to use for signing and encrypting payloads.
275+ #[ schemars( with = "Option<String>" ) ]
276+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
277+ keys_dir : Option < Utf8PathBuf > ,
338278}
339279
340280impl SecretsConfig {
@@ -345,7 +285,7 @@ impl SecretsConfig {
345285 /// Returns an error when a key could not be imported
346286 #[ tracing:: instrument( name = "secrets.load" , skip_all) ]
347287 pub async fn key_store ( & self ) -> anyhow:: Result < Keystore > {
348- let key_configs = self . keys . key_configs ( ) . await ?;
288+ let key_configs = self . key_configs ( ) . await ?;
349289 let web_keys = try_join_all ( key_configs. iter ( ) . map ( KeyConfig :: json_web_key) ) . await ?;
350290
351291 Ok ( Keystore :: new ( JsonWebKeySet :: new ( web_keys) ) )
@@ -380,6 +320,21 @@ impl SecretsConfig {
380320 }
381321 }
382322 }
323+
324+ /// Returns a combined list of key configs given inline and from files.
325+ ///
326+ /// If `keys_dir` was given, the keys are read from file.
327+ async fn key_configs ( & self ) -> anyhow:: Result < Vec < KeyConfig > > {
328+ let mut key_configs = match & self . keys_dir {
329+ Some ( keys_dir) => key_configs_from_path ( keys_dir) . await ?,
330+ None => vec ! [ ] ,
331+ } ;
332+
333+ let inline_key_configs = self . keys . as_deref ( ) . unwrap_or_default ( ) ;
334+ key_configs. extend ( inline_key_configs. iter ( ) . cloned ( ) ) ;
335+
336+ Ok ( key_configs)
337+ }
383338}
384339
385340impl ConfigurationSection for SecretsConfig {
@@ -461,7 +416,8 @@ impl SecretsConfig {
461416
462417 Ok ( Self {
463418 encryption : Encryption :: Value ( Standard . sample ( & mut rng) ) ,
464- keys : Keys :: Values ( vec ! [ rsa_key, ec_p256_key, ec_p384_key, ec_k256_key] ) ,
419+ keys : Some ( vec ! [ rsa_key, ec_p256_key, ec_p384_key, ec_k256_key] ) ,
420+ keys_dir : None ,
465421 } )
466422 }
467423
@@ -502,7 +458,8 @@ impl SecretsConfig {
502458
503459 Self {
504460 encryption : Encryption :: Value ( [ 0xEA ; 32 ] ) ,
505- keys : Keys :: Values ( vec ! [ rsa_key, ecdsa_key] ) ,
461+ keys : Some ( vec ! [ rsa_key, ecdsa_key] ) ,
462+ keys_dir : None ,
506463 }
507464 }
508465}
@@ -619,7 +576,7 @@ mod tests {
619576 ]
620577 ) ;
621578
622- let mut key_config = config. keys . key_configs ( ) . await . unwrap ( ) ;
579+ let mut key_config = config. key_configs ( ) . await . unwrap ( ) ;
623580 key_config. sort_by_key ( |a| {
624581 if let Key :: File ( p) = & a. key {
625582 Some ( p. clone ( ) )
@@ -696,4 +653,57 @@ mod tests {
696653 . await
697654 . unwrap ( ) ;
698655 }
656+
657+ #[ tokio:: test]
658+ async fn load_config_mixed_key_sources ( ) {
659+ task:: spawn_blocking ( || {
660+ Jail :: expect_with ( |jail| {
661+ jail. create_file (
662+ "config.yaml" ,
663+ indoc:: indoc! { r"
664+ secrets:
665+ encryption_file: encryption
666+ keys_dir: keys
667+ keys:
668+ - kid: lekid0
669+ key: |
670+ -----BEGIN EC PRIVATE KEY-----
671+ MHcCAQEEIOtZfDuXZr/NC0V3sisR4Chf7RZg6a2dpZesoXMlsPeRoAoGCCqGSM49
672+ AwEHoUQDQgAECfpqx64lrR85MOhdMxNmIgmz8IfmM5VY9ICX9aoaArnD9FjgkBIl
673+ fGmQWxxXDSWH6SQln9tROVZaduenJqDtDw==
674+ -----END EC PRIVATE KEY-----
675+ " } ,
676+ ) ?;
677+ jail. create_dir ( "keys" ) ?;
678+ jail. create_file (
679+ "keys/key_from_file" ,
680+ indoc:: indoc! { r"
681+ -----BEGIN EC PRIVATE KEY-----
682+ MHcCAQEEIKlZz/GnH0idVH1PnAF4HQNwRafgBaE2tmyN1wjfdOQqoAoGCCqGSM49
683+ AwEHoUQDQgAEHrgPeG+Mt8eahih1h4qaPjhl7jT25cdzBkg3dbVks6gBR2Rx4ug9
684+ h27LAir5RqxByHvua2XsP46rSTChof78uw==
685+ -----END EC PRIVATE KEY-----
686+ " } ,
687+ ) ?;
688+
689+ let config = Figment :: new ( )
690+ . merge ( Yaml :: file ( "config.yaml" ) )
691+ . extract_inner :: < SecretsConfig > ( "secrets" ) ?;
692+
693+ Handle :: current ( ) . block_on ( async move {
694+ let key_config = config. key_configs ( ) . await . unwrap ( ) ;
695+ let key_store = config. key_store ( ) . await . unwrap ( ) ;
696+
697+ assert ! ( key_config[ 0 ] . kid. is_none( ) ) ;
698+ assert ! ( matches!( & key_config[ 0 ] . key, Key :: File ( p) if p == "keys/key_from_file" ) ) ;
699+ assert ! ( key_store. iter( ) . any( |k| k. kid( ) == Some ( "ONUCn80fsiISFWKrVMEiirNVr-QEvi7uQI0QH9q9q4o" ) ) ) ;
700+ assert ! ( key_store. iter( ) . any( |k| k. kid( ) == Some ( "lekid0" ) ) ) ;
701+ } ) ;
702+
703+ Ok ( ( ) )
704+ } ) ;
705+ } )
706+ . await
707+ . unwrap ( ) ;
708+ }
699709}
0 commit comments