Skip to content

Commit c8cbb73

Browse files
committed
Add secrets.encryption_file config option
Signed-off-by: Kai A. Hiller <[email protected]>
1 parent d8fa8f7 commit c8cbb73

File tree

5 files changed

+101
-29
lines changed

5 files changed

+101
-29
lines changed

crates/cli/src/commands/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl Options {
123123
SC::Sync { prune, dry_run } => {
124124
let config = SyncConfig::extract(figment)?;
125125
let clock = SystemClock::default();
126-
let encrypter = config.secrets.encrypter();
126+
let encrypter = config.secrets.encrypter().await?;
127127

128128
// Grab a connection to the database
129129
let mut conn = database_connection_from_config(&config.database).await?;

crates/cli/src/commands/server.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl Options {
9494
.context("could not run database migrations")?;
9595
}
9696

97-
let encrypter = config.secrets.encrypter();
97+
let encrypter = config.secrets.encrypter().await?;
9898

9999
if self.no_sync {
100100
info!("Skipping configuration sync");
@@ -124,8 +124,10 @@ impl Options {
124124
.await
125125
.context("could not import keys from config")?;
126126

127-
let cookie_manager =
128-
CookieManager::derive_from(config.http.public_base.clone(), &config.secrets.encryption);
127+
let cookie_manager = CookieManager::derive_from(
128+
config.http.public_base.clone(),
129+
&config.secrets.encryption().await?,
130+
);
129131

130132
// Load and compile the WASM policies (and fallback to the default embedded one)
131133
info!("Loading and compiling the policy module");

crates/cli/src/commands/syn2mas.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl Options {
133133
// in the MAS database
134134
let config = SyncConfig::extract(figment)?;
135135
let clock = SystemClock::default();
136-
let encrypter = config.secrets.encrypter();
136+
let encrypter = config.secrets.encrypter().await?;
137137

138138
crate::sync::config_sync(
139139
config.upstream_oauth2,

crates/config/src/sections/secrets.rs

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use std::borrow::Cow;
88

9-
use anyhow::{Context, bail};
9+
use anyhow::{Context, anyhow, bail};
1010
use camino::Utf8PathBuf;
1111
use mas_jose::jwk::{JsonWebKey, JsonWebKeySet};
1212
use mas_keystore::{Encrypter, Keystore, PrivateKey};
@@ -46,18 +46,68 @@ pub struct KeyConfig {
4646
key_file: Option<Utf8PathBuf>,
4747
}
4848

49-
/// Application secrets
49+
/// Encryption config option.
50+
#[derive(Debug, Clone)]
51+
pub enum Encryption {
52+
File(Utf8PathBuf),
53+
Value([u8; 32]),
54+
}
55+
56+
/// Encryption fields as serialized in JSON.
5057
#[serde_as]
51-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
52-
pub struct SecretsConfig {
53-
/// Encryption key for secure cookies
58+
#[derive(JsonSchema, Serialize, Deserialize, Debug, Clone)]
59+
struct EncryptionRaw {
60+
/// File containing the encryption key for secure cookies.
61+
#[schemars(with = "Option<String>")]
62+
encryption_file: Option<Utf8PathBuf>,
63+
64+
/// Encryption key for secure cookies.
5465
#[schemars(
55-
with = "String",
66+
with = "Option<String>",
5667
regex(pattern = r"[0-9a-fA-F]{64}"),
5768
example = "example_secret"
5869
)]
59-
#[serde_as(as = "serde_with::hex::Hex")]
60-
pub encryption: [u8; 32],
70+
#[serde_as(as = "Option<serde_with::hex::Hex>")]
71+
encryption: Option<[u8; 32]>,
72+
}
73+
74+
impl TryFrom<EncryptionRaw> for Encryption {
75+
type Error = anyhow::Error;
76+
77+
fn try_from(value: EncryptionRaw) -> Result<Encryption, Self::Error> {
78+
match (value.encryption, value.encryption_file) {
79+
(None, None) => bail!("Missing `encryption` or `encryption_file`"),
80+
(None, Some(path)) => Ok(Encryption::File(path)),
81+
(Some(encryption), None) => Ok(Encryption::Value(encryption)),
82+
(Some(_), Some(_)) => bail!("Cannot specify both `encryption` and `encryption_file`"),
83+
}
84+
}
85+
}
86+
87+
impl From<Encryption> for EncryptionRaw {
88+
fn from(value: Encryption) -> Self {
89+
match value {
90+
Encryption::File(path) => EncryptionRaw {
91+
encryption_file: Some(path),
92+
encryption: None,
93+
},
94+
Encryption::Value(encryption) => EncryptionRaw {
95+
encryption_file: None,
96+
encryption: Some(encryption),
97+
},
98+
}
99+
}
100+
}
101+
102+
/// Application secrets
103+
#[serde_as]
104+
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
105+
pub struct SecretsConfig {
106+
/// Encryption key for secure cookies
107+
#[schemars(with = "EncryptionRaw")]
108+
#[serde_as(as = "serde_with::TryFromInto<EncryptionRaw>")]
109+
#[serde(flatten)]
110+
encryption: Encryption,
61111

62112
/// List of private keys to use for signing and encrypting payloads
63113
#[serde(default)]
@@ -118,9 +168,27 @@ impl SecretsConfig {
118168
}
119169

120170
/// Derive an [`Encrypter`] out of the config
121-
#[must_use]
122-
pub fn encrypter(&self) -> Encrypter {
123-
Encrypter::new(&self.encryption)
171+
///
172+
/// # Errors
173+
///
174+
/// Returns an error when the Encryptor can not be created.
175+
pub async fn encrypter(&self) -> anyhow::Result<Encrypter> {
176+
Ok(Encrypter::new(&self.encryption().await?))
177+
}
178+
179+
/// Returns the encryption secret.
180+
///
181+
/// # Errors
182+
///
183+
/// Returns an error when the encryption secret could not be read from file.
184+
pub async fn encryption(&self) -> anyhow::Result<[u8; 32]> {
185+
// Read the encryption secret either embedded in the config file or on disk
186+
match self.encryption {
187+
Encryption::Value(encryption) => Ok(encryption),
188+
Encryption::File(ref path) => tokio::fs::read(path).await?.try_into().map_err(|_| {
189+
anyhow!("Content of `encryption_file` must be exactly 32 bytes long.")
190+
}),
191+
}
124192
}
125193
}
126194

@@ -246,7 +314,7 @@ impl SecretsConfig {
246314
};
247315

248316
Ok(Self {
249-
encryption: Standard.sample(&mut rng),
317+
encryption: Encryption::Value(Standard.sample(&mut rng)),
250318
keys: vec![rsa_key, ec_p256_key, ec_p384_key, ec_k256_key],
251319
})
252320
}
@@ -291,7 +359,7 @@ impl SecretsConfig {
291359
};
292360

293361
Self {
294-
encryption: [0xEA; 32],
362+
encryption: Encryption::Value([0xEA; 32]),
295363
keys: vec![rsa_key, ecdsa_key],
296364
}
297365
}

docs/config.schema.json

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,25 +1515,27 @@
15151515
"SecretsConfig": {
15161516
"description": "Application secrets",
15171517
"type": "object",
1518-
"required": [
1519-
"encryption"
1520-
],
15211518
"properties": {
1522-
"encryption": {
1523-
"description": "Encryption key for secure cookies",
1524-
"examples": [
1525-
"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
1526-
],
1527-
"type": "string",
1528-
"pattern": "[0-9a-fA-F]{64}"
1529-
},
15301519
"keys": {
15311520
"description": "List of private keys to use for signing and encrypting payloads",
15321521
"default": [],
15331522
"type": "array",
15341523
"items": {
15351524
"$ref": "#/definitions/KeyConfig"
15361525
}
1526+
},
1527+
"encryption_file": {
1528+
"description": "File containing the encryption key for secure cookies.",
1529+
"type": "string"
1530+
},
1531+
"encryption": {
1532+
"description": "Encryption key for secure cookies.",
1533+
"default": null,
1534+
"examples": [
1535+
"0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"
1536+
],
1537+
"type": "string",
1538+
"pattern": "[0-9a-fA-F]{64}"
15371539
}
15381540
}
15391541
},

0 commit comments

Comments
 (0)