Skip to content

Commit a02bb7a

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 a02bb7a

File tree

3 files changed

+36
-29
lines changed

3 files changed

+36
-29
lines changed

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::Args;
99
use cipherstash_client::config::vars::{
1010
CS_CLIENT_ACCESS_KEY, CS_CLIENT_ID, CS_CLIENT_KEY, CS_DEFAULT_KEYSET_ID, CS_WORKSPACE_CRN,
1111
};
12+
use cipherstash_client::zerokms::ClientKey;
1213
use config::{Config, Environment};
1314
use cts_common::Crn;
1415
use regex::Regex;
@@ -42,7 +43,7 @@ pub struct AuthConfig {
4243

4344
#[derive(Debug, Deserialize, Clone, PartialEq)]
4445
pub struct EncryptConfig {
45-
pub client_id: String,
46+
pub client_id: Uuid,
4647
pub client_key: String,
4748
pub default_keyset_id: Option<Uuid>,
4849
}
@@ -68,7 +69,7 @@ pub struct DevelopmentConfig {
6869
pub enable_mapping_errors: bool,
6970

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

7374
#[serde(default)]
7475
pub cts_host: Option<String>,
@@ -113,6 +114,9 @@ impl TandemConfig {
113114
config.log.format = args.log_format;
114115
}
115116

117+
ClientKey::from_hex_v1(config.encrypt.client_id, &config.encrypt.client_key)
118+
.map_err(|e| ConfigError::InvalidClientKey(e.into()))?;
119+
116120
Ok(config)
117121
}
118122

@@ -191,7 +195,7 @@ impl TandemConfig {
191195
}
192196

193197
// Source order is important!
194-
let config = Config::builder()
198+
let config: TandemConfig = Config::builder()
195199
.add_source(config::File::with_name(&args.config_file_path).required(false))
196200
.add_source(cs_env_source)
197201
.add_source(stash_setup_source)
@@ -246,7 +250,7 @@ impl TandemConfig {
246250
}
247251
}
248252

249-
pub fn zerokms_host(&self) -> Option<String> {
253+
pub fn zerokms_host(&self) -> Option<url::Url> {
250254
self.development
251255
.as_ref()
252256
.and_then(|dev| dev.zerokms_host.clone())
@@ -326,7 +330,7 @@ impl TandemConfig {
326330
client_access_key: "test".to_string(),
327331
},
328332
encrypt: EncryptConfig {
329-
client_id: "test".to_string(),
333+
client_id: Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
330334
client_key: "test".to_string(),
331335
default_keyset_id: Some(
332336
Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(),
@@ -426,9 +430,12 @@ mod tests {
426430
temp_env::with_vars(
427431
[
428432
// 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")),
433+
(
434+
"CS_ENCRYPT__CLIENT_ID",
435+
Some("11111111-1111-1111-1111-111111111111"),
436+
),
437+
(CS_CLIENT_ID, Some("22222222-2222-2222-2222-222222222222")),
438+
(CS_CLIENT_KEY, Some("test_key")),
432439
(
433440
CS_DEFAULT_KEYSET_ID,
434441
Some("dd0a239f-02e2-4c8e-ba20-d9f0f85af9ac"),
@@ -440,7 +447,10 @@ mod tests {
440447
TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml")
441448
.unwrap();
442449

443-
assert_eq!(config.encrypt.client_id, "CS_CLIENT_ID".to_string());
450+
assert_eq!(
451+
config.encrypt.client_id,
452+
Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap()
453+
);
444454

445455
assert_eq!(
446456
config.auth.client_access_key,
@@ -474,8 +484,8 @@ mod tests {
474484
.unwrap();
475485

476486
assert_eq!(
477-
&config.encrypt.client_id,
478-
"dd0a239f-02e2-4c8e-ba20-d9f0f85af9ac"
487+
config.encrypt.client_id,
488+
Uuid::parse_str("dd0a239f-02e2-4c8e-ba20-d9f0f85af9ac").unwrap()
479489
);
480490
},
481491
);

packages/cipherstash-proxy/src/error.rs

Lines changed: 12 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
}
@@ -128,6 +128,9 @@ pub enum ConfigError {
128128
#[error(transparent)]
129129
FileOrEnvironment(#[from] config::ConfigError),
130130

131+
#[error("Invalid client_key: {0}")]
132+
InvalidClientKey(Box<dyn std::error::Error + Send + Sync>),
133+
131134
#[error("Dataset id is not a valid UUID.")]
132135
InvalidDatasetId,
133136

@@ -439,6 +442,12 @@ impl From<config::ConfigError> for Error {
439442
}
440443
}
441444

445+
impl From<cipherstash_client::zerokms::ZeroKMSBuilderError> for Error {
446+
fn from(e: cipherstash_client::zerokms::ZeroKMSBuilderError) -> Self {
447+
Error::ZeroKMS(e.into())
448+
}
449+
}
450+
442451
impl From<cipherstash_client::encryption::TypeParseError> for Error {
443452
fn from(e: cipherstash_client::encryption::TypeParseError) -> Self {
444453
Error::Encrypt(e.into())

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

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,17 @@ 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
2019
let strategy = AutoStrategy::builder()
2120
.with_access_key(&config.auth.client_access_key)
2221
.with_workspace_crn(config.auth.workspace_crn.clone())
2322
.detect()?;
2423

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");
24+
let client_key = ClientKey::from_hex_v1(config.encrypt.client_id, &config.encrypt.client_key)
25+
.expect("validated during config loading");
3326

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

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");
29+
if let Some(url) = config.zerokms_host() {
4230
builder = builder.with_base_url(url);
4331
}
4432

0 commit comments

Comments
 (0)