Skip to content

Commit c9a8123

Browse files
committed
Allow keys and keys_dir simultaneously
1 parent a93fa72 commit c9a8123

File tree

1 file changed

+82
-72
lines changed

1 file changed

+82
-72
lines changed

crates/config/src/sections/secrets.rs

Lines changed: 82 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
266244
async 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

340280
impl 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

385340
impl 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

Comments
 (0)