Skip to content

Commit ef63279

Browse files
committed
refactor(config): validate client_id, client_key, and zerokms_host at config load
Push parsing errors into the config boundary instead of panicking at construction time. EncryptConfig.client_id is now Uuid and DevelopmentConfig.zerokms_host is now url::Url, so serde enforces validity at deserialization. client_key hex is validated in load(). Folds Error::ZeroKMSBuilder into ZeroKMSError::Builder to maintain domain-grouped error taxonomy.
1 parent 9619605 commit ef63279

3 files changed

Lines changed: 44 additions & 29 deletions

File tree

packages/cipherstash-proxy/src/config/tandem.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub struct AuthConfig {
4242

4343
#[derive(Debug, Deserialize, Clone, PartialEq)]
4444
pub struct EncryptConfig {
45-
pub client_id: String,
45+
pub client_id: Uuid,
4646
pub client_key: String,
4747
pub default_keyset_id: Option<Uuid>,
4848
}
@@ -68,7 +68,7 @@ pub struct DevelopmentConfig {
6868
pub enable_mapping_errors: bool,
6969

7070
#[serde(default)]
71-
pub zerokms_host: Option<String>,
71+
pub zerokms_host: Option<url::Url>,
7272

7373
#[serde(default)]
7474
pub cts_host: Option<String>,
@@ -113,6 +113,16 @@ impl TandemConfig {
113113
config.log.format = args.log_format;
114114
}
115115

116+
// Validate client_key is valid hex for the given client_id
117+
cipherstash_client::zerokms::ClientKey::from_hex_v1(
118+
config.encrypt.client_id,
119+
&config.encrypt.client_key,
120+
)
121+
.map_err(|e| ConfigError::InvalidParameter {
122+
name: "client_key".to_string(),
123+
value: format!("{e}"),
124+
})?;
125+
116126
Ok(config)
117127
}
118128

@@ -191,7 +201,7 @@ impl TandemConfig {
191201
}
192202

193203
// Source order is important!
194-
let config = Config::builder()
204+
let config: TandemConfig = Config::builder()
195205
.add_source(config::File::with_name(&args.config_file_path).required(false))
196206
.add_source(cs_env_source)
197207
.add_source(stash_setup_source)
@@ -246,7 +256,7 @@ impl TandemConfig {
246256
}
247257
}
248258

249-
pub fn zerokms_host(&self) -> Option<String> {
259+
pub fn zerokms_host(&self) -> Option<url::Url> {
250260
self.development
251261
.as_ref()
252262
.and_then(|dev| dev.zerokms_host.clone())
@@ -326,7 +336,7 @@ impl TandemConfig {
326336
client_access_key: "test".to_string(),
327337
},
328338
encrypt: EncryptConfig {
329-
client_id: "test".to_string(),
339+
client_id: Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
330340
client_key: "test".to_string(),
331341
default_keyset_id: Some(
332342
Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
@@ -426,9 +436,9 @@ mod tests {
426436
temp_env::with_vars(
427437
[
428438
// Orignal recipe ENV var
429-
("CS_ENCRYPT__CLIENT_ID", Some("CS_ENCRYPT__CLIENT_ID")),
430-
(CS_CLIENT_ID, Some("CS_CLIENT_ID")),
431-
(CS_CLIENT_KEY, Some("CS_CLIENT_KEY")),
439+
("CS_ENCRYPT__CLIENT_ID", Some("11111111-1111-1111-1111-111111111111")),
440+
(CS_CLIENT_ID, Some("22222222-2222-2222-2222-222222222222")),
441+
(CS_CLIENT_KEY, Some("test_key")),
432442
(
433443
CS_DEFAULT_KEYSET_ID,
434444
Some("dd0a239f-02e2-4c8e-ba20-d9f0f85af9ac"),
@@ -440,7 +450,10 @@ mod tests {
440450
TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml")
441451
.unwrap();
442452

443-
assert_eq!(config.encrypt.client_id, "CS_CLIENT_ID".to_string());
453+
assert_eq!(
454+
config.encrypt.client_id,
455+
Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap()
456+
);
444457

445458
assert_eq!(
446459
config.auth.client_access_key,
@@ -474,8 +487,8 @@ mod tests {
474487
.unwrap();
475488

476489
assert_eq!(
477-
&config.encrypt.client_id,
478-
"dd0a239f-02e2-4c8e-ba20-d9f0f85af9ac"
490+
config.encrypt.client_id,
491+
Uuid::parse_str("dd0a239f-02e2-4c8e-ba20-d9f0f85af9ac").unwrap()
479492
);
480493
},
481494
);

packages/cipherstash-proxy/src/error.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ pub enum Error {
5454
#[error(transparent)]
5555
ZeroKMS(#[from] ZeroKMSError),
5656

57-
#[error(transparent)]
58-
ZeroKMSBuilder(#[from] cipherstash_client::zerokms::ZeroKMSBuilderError),
59-
6057
#[error("Unknown error")]
6158
Unknown,
6259

@@ -75,6 +72,9 @@ pub enum ZeroKMSError {
7572
#[error("ZeroKMS authentication failed. Check the configured credentials. For help visit {}#zerokms-authentication-failed", ERROR_DOC_BASE_URL)]
7673
AuthenticationFailed,
7774

75+
#[error(transparent)]
76+
Builder(#[from] cipherstash_client::zerokms::ZeroKMSBuilderError),
77+
7878
#[error(transparent)]
7979
System(#[from] cipherstash_client::zerokms::Error),
8080
}
@@ -439,6 +439,12 @@ impl From<config::ConfigError> for Error {
439439
}
440440
}
441441

442+
impl From<cipherstash_client::zerokms::ZeroKMSBuilderError> for Error {
443+
fn from(e: cipherstash_client::zerokms::ZeroKMSBuilderError) -> Self {
444+
Error::ZeroKMS(e.into())
445+
}
446+
}
447+
442448
impl From<cipherstash_client::encryption::TypeParseError> for Error {
443449
fn from(e: cipherstash_client::encryption::TypeParseError) -> Self {
444450
Error::Encrypt(e.into())

packages/cipherstash-proxy/src/proxy/zerokms/mod.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,25 @@ pub type ZerokmsClient = ZeroKMS<AutoStrategy, ClientKey>;
1616
pub(crate) fn init_zerokms_client(
1717
config: &TandemConfig,
1818
) -> Result<ZerokmsClient, ZeroKMSBuilderError> {
19-
// 1. Build auth strategy from proxy config
19+
// Bridge development.cts_host from TOML config to CS_CTS_HOST env var
20+
// so AutoStrategy picks it up. Env var takes precedence if already set.
21+
if let Some(cts_host) = config.cts_host() {
22+
if std::env::var("CS_CTS_HOST").is_err() {
23+
std::env::set_var("CS_CTS_HOST", cts_host);
24+
}
25+
}
26+
2027
let strategy = AutoStrategy::builder()
2128
.with_access_key(&config.auth.client_access_key)
2229
.with_workspace_crn(config.auth.workspace_crn.clone())
2330
.detect()?;
2431

25-
// 2. Parse client key
26-
let client_id: uuid::Uuid = config
27-
.encrypt
28-
.client_id
29-
.parse()
30-
.expect("client_id must be a valid UUID");
31-
let client_key = ClientKey::from_hex_v1(client_id, &config.encrypt.client_key)
32-
.expect("client_key must be valid hex");
32+
let client_key = ClientKey::from_hex_v1(config.encrypt.client_id, &config.encrypt.client_key)
33+
.expect("validated during config loading");
3334

34-
// 3. Build ZeroKMS client (with_base_url must be called before with_client_key)
3535
let mut builder = ZeroKMSBuilder::new(strategy);
3636

37-
// Optional: override ZeroKMS endpoint for development
38-
if let Some(zerokms_host) = config.zerokms_host() {
39-
let url: url::Url = zerokms_host
40-
.parse()
41-
.expect("zerokms_host must be a valid URL");
37+
if let Some(url) = config.zerokms_host() {
4238
builder = builder.with_base_url(url);
4339
}
4440

0 commit comments

Comments
 (0)