Skip to content
This repository was archived by the owner on Sep 10, 2024. It is now read-only.

Commit 809fe16

Browse files
committed
Flatten the secrets config section
1 parent 156dc08 commit 809fe16

File tree

2 files changed

+91
-61
lines changed

2 files changed

+91
-61
lines changed

crates/config/src/sections/secrets.rs

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
use std::borrow::Cow;
1616

17-
use anyhow::Context;
17+
use anyhow::{bail, Context};
1818
use async_trait::async_trait;
1919
use camino::Utf8PathBuf;
2020
use mas_jose::jwk::{JsonWebKey, JsonWebKeySet};
@@ -35,31 +35,23 @@ fn example_secret() -> &'static str {
3535
"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
3636
}
3737

38-
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
39-
#[serde(rename_all = "snake_case")]
40-
pub enum KeyOrFile {
41-
Key(String),
42-
#[schemars(with = "String")]
43-
KeyFile(Utf8PathBuf),
44-
}
45-
46-
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
47-
#[serde(rename_all = "snake_case")]
48-
pub enum PasswordOrFile {
49-
Password(String),
50-
#[schemars(with = "String")]
51-
PasswordFile(Utf8PathBuf),
52-
}
53-
5438
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
5539
pub struct KeyConfig {
5640
kid: String,
5741

58-
#[serde(flatten)]
59-
password: Option<PasswordOrFile>,
42+
#[serde(skip_serializing_if = "Option::is_none")]
43+
password: Option<String>,
44+
45+
#[serde(skip_serializing_if = "Option::is_none")]
46+
#[schemars(with = "Option<String>")]
47+
password_file: Option<Utf8PathBuf>,
48+
49+
#[serde(skip_serializing_if = "Option::is_none")]
50+
key: Option<String>,
6051

61-
#[serde(flatten)]
62-
key: KeyOrFile,
52+
#[serde(skip_serializing_if = "Option::is_none")]
53+
#[schemars(with = "Option<String>")]
54+
key_file: Option<Utf8PathBuf>,
6355
}
6456

6557
/// Application secrets
@@ -90,25 +82,28 @@ impl SecretsConfig {
9082
pub async fn key_store(&self) -> anyhow::Result<Keystore> {
9183
let mut keys = Vec::with_capacity(self.keys.len());
9284
for item in &self.keys {
93-
let password = match &item.password {
94-
Some(PasswordOrFile::Password(password)) => Some(Cow::Borrowed(password.as_str())),
95-
Some(PasswordOrFile::PasswordFile(path)) => {
96-
Some(Cow::Owned(tokio::fs::read_to_string(path).await?))
85+
let password = match (&item.password, &item.password_file) {
86+
(None, None) => None,
87+
(Some(_), Some(_)) => {
88+
bail!("Cannot specify both `password` and `password_file`")
9789
}
98-
None => None,
90+
(Some(password), None) => Some(Cow::Borrowed(password)),
91+
(None, Some(path)) => Some(Cow::Owned(tokio::fs::read_to_string(path).await?)),
9992
};
10093

10194
// Read the key either embedded in the config file or on disk
102-
let key = match &item.key {
103-
KeyOrFile::Key(key) => {
95+
let key = match (&item.key, &item.key_file) {
96+
(None, None) => bail!("Missing `key` or `key_file`"),
97+
(Some(_), Some(_)) => bail!("Cannot specify both `key` and `key_file`"),
98+
(Some(key), None) => {
10499
// If the key was embedded in the config file, assume it is formatted as PEM
105100
if let Some(password) = password {
106101
PrivateKey::load_encrypted_pem(key, password.as_bytes())?
107102
} else {
108103
PrivateKey::load_pem(key)?
109104
}
110105
}
111-
KeyOrFile::KeyFile(path) => {
106+
(None, Some(path)) => {
112107
// When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
113108
// will try both.
114109
let key = tokio::fs::read(path).await?;
@@ -161,7 +156,9 @@ impl ConfigurationSection for SecretsConfig {
161156
let rsa_key = KeyConfig {
162157
kid: Alphanumeric.sample_string(&mut rng, 10),
163158
password: None,
164-
key: KeyOrFile::Key(rsa_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
159+
password_file: None,
160+
key: Some(rsa_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
161+
key_file: None,
165162
};
166163

167164
let span = tracing::info_span!("ec_p256");
@@ -177,7 +174,9 @@ impl ConfigurationSection for SecretsConfig {
177174
let ec_p256_key = KeyConfig {
178175
kid: Alphanumeric.sample_string(&mut rng, 10),
179176
password: None,
180-
key: KeyOrFile::Key(ec_p256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
177+
password_file: None,
178+
key: Some(ec_p256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
179+
key_file: None,
181180
};
182181

183182
let span = tracing::info_span!("ec_p384");
@@ -193,7 +192,9 @@ impl ConfigurationSection for SecretsConfig {
193192
let ec_p384_key = KeyConfig {
194193
kid: Alphanumeric.sample_string(&mut rng, 10),
195194
password: None,
196-
key: KeyOrFile::Key(ec_p384_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
195+
password_file: None,
196+
key: Some(ec_p384_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
197+
key_file: None,
197198
};
198199

199200
let span = tracing::info_span!("ec_k256");
@@ -209,7 +210,9 @@ impl ConfigurationSection for SecretsConfig {
209210
let ec_k256_key = KeyConfig {
210211
kid: Alphanumeric.sample_string(&mut rng, 10),
211212
password: None,
212-
key: KeyOrFile::Key(ec_k256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
213+
password_file: None,
214+
key: Some(ec_k256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
215+
key_file: None,
213216
};
214217

215218
Ok(Self {
@@ -218,11 +221,49 @@ impl ConfigurationSection for SecretsConfig {
218221
})
219222
}
220223

224+
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
225+
for (index, key) in self.keys.iter().enumerate() {
226+
let annotate = |mut error: figment::Error| {
227+
error.metadata = figment
228+
.find_metadata(&format!("{root}.keys", root = Self::PATH.unwrap()))
229+
.cloned();
230+
error.profile = Some(figment::Profile::Default);
231+
error.path = vec![
232+
Self::PATH.unwrap().to_owned(),
233+
"keys".to_owned(),
234+
index.to_string(),
235+
];
236+
Err(error)
237+
};
238+
239+
if key.key.is_none() && key.key_file.is_none() {
240+
return annotate(figment::Error::from(
241+
"Missing `key` or `key_file`".to_owned(),
242+
));
243+
}
244+
245+
if key.key.is_some() && key.key_file.is_some() {
246+
return annotate(figment::Error::from(
247+
"Cannot specify both `key` and `key_file`".to_owned(),
248+
));
249+
}
250+
251+
if key.password.is_some() && key.password_file.is_some() {
252+
return annotate(figment::Error::from(
253+
"Cannot specify both `password` and `password_file`".to_owned(),
254+
));
255+
}
256+
}
257+
258+
Ok(())
259+
}
260+
221261
fn test() -> Self {
222262
let rsa_key = KeyConfig {
223263
kid: "abcdef".to_owned(),
224264
password: None,
225-
key: KeyOrFile::Key(
265+
password_file: None,
266+
key: Some(
226267
indoc::indoc! {r"
227268
-----BEGIN PRIVATE KEY-----
228269
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN
@@ -237,11 +278,13 @@ impl ConfigurationSection for SecretsConfig {
237278
"}
238279
.to_owned(),
239280
),
281+
key_file: None,
240282
};
241283
let ecdsa_key = KeyConfig {
242284
kid: "ghijkl".to_owned(),
243285
password: None,
244-
key: KeyOrFile::Key(
286+
password_file: None,
287+
key: Some(
245288
indoc::indoc! {r"
246289
-----BEGIN PRIVATE KEY-----
247290
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgqfn5mYO/5Qq/wOOiWgHA
@@ -251,6 +294,7 @@ impl ConfigurationSection for SecretsConfig {
251294
"}
252295
.to_owned(),
253296
),
297+
key_file: None,
254298
};
255299

256300
Self {

docs/config.schema.json

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,38 +1473,24 @@
14731473
},
14741474
"KeyConfig": {
14751475
"type": "object",
1476-
"oneOf": [
1477-
{
1478-
"type": "object",
1479-
"required": [
1480-
"password"
1481-
],
1482-
"properties": {
1483-
"password": {
1484-
"type": "string"
1485-
}
1486-
},
1487-
"additionalProperties": false
1488-
},
1489-
{
1490-
"type": "object",
1491-
"required": [
1492-
"password_file"
1493-
],
1494-
"properties": {
1495-
"password_file": {
1496-
"type": "string"
1497-
}
1498-
},
1499-
"additionalProperties": false
1500-
}
1501-
],
15021476
"required": [
15031477
"kid"
15041478
],
15051479
"properties": {
15061480
"kid": {
15071481
"type": "string"
1482+
},
1483+
"password": {
1484+
"type": "string"
1485+
},
1486+
"password_file": {
1487+
"type": "string"
1488+
},
1489+
"key": {
1490+
"type": "string"
1491+
},
1492+
"key_file": {
1493+
"type": "string"
15081494
}
15091495
}
15101496
},

0 commit comments

Comments
 (0)