Skip to content

Commit ec693ed

Browse files
committed
Refactor key options in secret config
Signed-off-by: Kai A. Hiller <[email protected]>
1 parent e92d16c commit ec693ed

File tree

2 files changed

+76
-73
lines changed

2 files changed

+76
-73
lines changed

crates/config/src/sections/secrets.rs

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,52 @@ impl From<Option<Password>> for PasswordRaw {
7777
}
7878
}
7979

80+
/// Key config option.
81+
///
82+
/// It either holds the key value directly or references a file where the key is
83+
/// stored.
84+
#[derive(Clone, Debug)]
85+
pub enum Key {
86+
File(Utf8PathBuf),
87+
Value(String),
88+
}
89+
90+
/// Key fields as serialized in JSON.
91+
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
92+
struct KeyRaw {
93+
#[schemars(with = "Option<String>")]
94+
key_file: Option<Utf8PathBuf>,
95+
key: Option<String>,
96+
}
97+
98+
impl TryFrom<KeyRaw> for Key {
99+
type Error = anyhow::Error;
100+
101+
fn try_from(value: KeyRaw) -> Result<Key, Self::Error> {
102+
match (value.key, value.key_file) {
103+
(None, None) => bail!("Missing `key` or `key_file`"),
104+
(None, Some(path)) => Ok(Key::File(path)),
105+
(Some(key), None) => Ok(Key::Value(key)),
106+
(Some(_), Some(_)) => bail!("Cannot specify both `key` and `key_file`"),
107+
}
108+
}
109+
}
110+
111+
impl From<Key> for KeyRaw {
112+
fn from(value: Key) -> Self {
113+
match value {
114+
Key::File(path) => KeyRaw {
115+
key_file: Some(path),
116+
key: None,
117+
},
118+
Key::Value(key) => KeyRaw {
119+
key_file: None,
120+
key: Some(key),
121+
},
122+
}
123+
}
124+
}
125+
80126
/// A single key with its key ID and optional password.
81127
#[serde_as]
82128
#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
@@ -88,12 +134,10 @@ pub struct KeyConfig {
88134
#[serde(flatten)]
89135
password: Option<Password>,
90136

91-
#[serde(skip_serializing_if = "Option::is_none")]
92-
key: Option<String>,
93-
94-
#[serde(skip_serializing_if = "Option::is_none")]
95-
#[schemars(with = "Option<String>")]
96-
key_file: Option<Utf8PathBuf>,
137+
#[schemars(with = "KeyRaw")]
138+
#[serde_as(as = "serde_with::TryFromInto<KeyRaw>")]
139+
#[serde(flatten)]
140+
key: Key,
97141
}
98142

99143
impl KeyConfig {
@@ -107,6 +151,16 @@ impl KeyConfig {
107151
None => None,
108152
})
109153
}
154+
155+
/// Returns the key.
156+
///
157+
/// If `key_file` was given, the key is read from that file.
158+
async fn key(&self) -> anyhow::Result<Cow<String>> {
159+
Ok(match &self.key {
160+
Key::File(path) => Cow::Owned(tokio::fs::read_to_string(path).await?),
161+
Key::Value(key) => Cow::Borrowed(key),
162+
})
163+
}
110164
}
111165

112166
/// Application secrets
@@ -139,31 +193,13 @@ impl SecretsConfig {
139193
for item in &self.keys {
140194
let password = item.password().await?;
141195

142-
// Read the key either embedded in the config file or on disk
143-
let key = match (&item.key, &item.key_file) {
144-
(None, None) => bail!("Missing `key` or `key_file`"),
145-
(Some(_), Some(_)) => bail!("Cannot specify both `key` and `key_file`"),
146-
(Some(key), None) => {
147-
// If the key was embedded in the config file, assume it is formatted as PEM
148-
if let Some(password) = password {
149-
PrivateKey::load_encrypted_pem(key, password.as_bytes())?
150-
} else {
151-
PrivateKey::load_pem(key)?
152-
}
153-
}
154-
(None, Some(path)) => {
155-
// When reading from disk, it might be either PEM or DER. `PrivateKey::load*`
156-
// will try both.
157-
let key = tokio::fs::read(path).await?;
158-
if let Some(password) = password {
159-
PrivateKey::load_encrypted(&key, password.as_bytes())?
160-
} else {
161-
PrivateKey::load(&key)?
162-
}
163-
}
196+
let key = item.key().await?;
197+
let private_key = match password {
198+
Some(password) => PrivateKey::load_encrypted(key.as_bytes(), password.as_bytes())?,
199+
None => PrivateKey::load(key.as_bytes())?,
164200
};
165201

166-
let key = JsonWebKey::new(key)
202+
let key = JsonWebKey::new(private_key)
167203
.with_kid(item.kid.clone())
168204
.with_use(mas_iana::jose::JsonWebKeyUse::Sig);
169205
keys.push(key);
@@ -183,34 +219,7 @@ impl SecretsConfig {
183219
impl ConfigurationSection for SecretsConfig {
184220
const PATH: Option<&'static str> = Some("secrets");
185221

186-
fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> {
187-
for (index, key) in self.keys.iter().enumerate() {
188-
let annotate = |mut error: figment::Error| {
189-
error.metadata = figment
190-
.find_metadata(&format!("{root}.keys", root = Self::PATH.unwrap()))
191-
.cloned();
192-
error.profile = Some(figment::Profile::Default);
193-
error.path = vec![
194-
Self::PATH.unwrap().to_owned(),
195-
"keys".to_owned(),
196-
index.to_string(),
197-
];
198-
Err(error)
199-
};
200-
201-
if key.key.is_none() && key.key_file.is_none() {
202-
return annotate(figment::Error::from(
203-
"Missing `key` or `key_file`".to_owned(),
204-
));
205-
}
206-
207-
if key.key.is_some() && key.key_file.is_some() {
208-
return annotate(figment::Error::from(
209-
"Cannot specify both `key` and `key_file`".to_owned(),
210-
));
211-
}
212-
}
213-
222+
fn validate(&self, _figment: &figment::Figment) -> Result<(), figment::Error> {
214223
Ok(())
215224
}
216225
}
@@ -236,8 +245,7 @@ impl SecretsConfig {
236245
let rsa_key = KeyConfig {
237246
kid: Alphanumeric.sample_string(&mut rng, 10),
238247
password: None,
239-
key: Some(rsa_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
240-
key_file: None,
248+
key: Key::Value(rsa_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
241249
};
242250

243251
let span = tracing::info_span!("ec_p256");
@@ -253,8 +261,7 @@ impl SecretsConfig {
253261
let ec_p256_key = KeyConfig {
254262
kid: Alphanumeric.sample_string(&mut rng, 10),
255263
password: None,
256-
key: Some(ec_p256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
257-
key_file: None,
264+
key: Key::Value(ec_p256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
258265
};
259266

260267
let span = tracing::info_span!("ec_p384");
@@ -270,8 +277,7 @@ impl SecretsConfig {
270277
let ec_p384_key = KeyConfig {
271278
kid: Alphanumeric.sample_string(&mut rng, 10),
272279
password: None,
273-
key: Some(ec_p384_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
274-
key_file: None,
280+
key: Key::Value(ec_p384_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
275281
};
276282

277283
let span = tracing::info_span!("ec_k256");
@@ -287,8 +293,7 @@ impl SecretsConfig {
287293
let ec_k256_key = KeyConfig {
288294
kid: Alphanumeric.sample_string(&mut rng, 10),
289295
password: None,
290-
key: Some(ec_k256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
291-
key_file: None,
296+
key: Key::Value(ec_k256_key.to_pem(pem_rfc7468::LineEnding::LF)?.to_string()),
292297
};
293298

294299
Ok(Self {
@@ -301,7 +306,7 @@ impl SecretsConfig {
301306
let rsa_key = KeyConfig {
302307
kid: "abcdef".to_owned(),
303308
password: None,
304-
key: Some(
309+
key: Key::Value(
305310
indoc::indoc! {r"
306311
-----BEGIN PRIVATE KEY-----
307312
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAymS2RkeIZo7pUeEN
@@ -316,12 +321,11 @@ impl SecretsConfig {
316321
"}
317322
.to_owned(),
318323
),
319-
key_file: None,
320324
};
321325
let ecdsa_key = KeyConfig {
322326
kid: "ghijkl".to_owned(),
323327
password: None,
324-
key: Some(
328+
key: Key::Value(
325329
indoc::indoc! {r"
326330
-----BEGIN PRIVATE KEY-----
327331
MIGEAgEAMBAGByqGSM49AgEGBSuBBAAKBG0wawIBAQQgqfn5mYO/5Qq/wOOiWgHA
@@ -331,7 +335,6 @@ impl SecretsConfig {
331335
"}
332336
.to_owned(),
333337
),
334-
key_file: None,
335338
};
336339

337340
Self {

docs/config.schema.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,16 +1547,16 @@
15471547
"kid": {
15481548
"type": "string"
15491549
},
1550-
"key": {
1550+
"password_file": {
15511551
"type": "string"
15521552
},
1553-
"key_file": {
1553+
"password": {
15541554
"type": "string"
15551555
},
1556-
"password_file": {
1556+
"key_file": {
15571557
"type": "string"
15581558
},
1559-
"password": {
1559+
"key": {
15601560
"type": "string"
15611561
}
15621562
}

0 commit comments

Comments
 (0)