1use crate::{
15 config::ConfigFileError,
16 models::{validate_plain_or_env_value, PlainOrEnvValue},
17};
18use serde::{Deserialize, Serialize};
19use validator::Validate;
20
21use super::{validate_with_validator, SignerConfigValidate};
22
23pub fn default_auth_uri() -> String {
24 "https://accounts.google.com/o/oauth2/auth".to_string()
25}
26pub fn default_token_uri() -> String {
27 "https://oauth2.googleapis.com/token".to_string()
28}
29fn default_auth_provider_x509_cert_url() -> String {
30 "https://www.googleapis.com/oauth2/v1/certs".to_string()
31}
32fn default_client_x509_cert_url() -> String {
33 "https://www.googleapis.com/robot/v1/metadata/x509/solana-signer%40forward-emitter-459820-r7.iam.gserviceaccount.com".to_string()
34}
35
36fn default_universe_domain() -> String {
37 "googleapis.com".to_string()
38}
39
40fn default_key_version() -> u32 {
41 1
42}
43
44fn default_location() -> String {
45 "global".to_string()
46}
47
48#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
49#[serde(deny_unknown_fields)]
50pub struct ServiceAccountConfig {
51 #[validate(length(min = 1, message = "project_id cannot be empty"))]
52 pub project_id: String,
53 #[validate(custom(function = "validate_plain_or_env_value"))]
54 pub private_key_id: PlainOrEnvValue,
55 #[validate(custom(function = "validate_plain_or_env_value"))]
56 pub private_key: PlainOrEnvValue,
57 #[validate(custom(function = "validate_plain_or_env_value"))]
58 pub client_email: PlainOrEnvValue,
59 #[validate(length(min = 1, message = "client_id cannot be empty"))]
60 pub client_id: String,
61 #[validate(url)]
62 #[serde(default = "default_auth_uri")]
63 pub auth_uri: String,
64 #[validate(url)]
65 #[serde(default = "default_token_uri")]
66 pub token_uri: String,
67 #[validate(url)]
68 #[serde(default = "default_auth_provider_x509_cert_url")]
69 pub auth_provider_x509_cert_url: String,
70 #[validate(url)]
71 #[serde(default = "default_client_x509_cert_url")]
72 pub client_x509_cert_url: String,
73 #[serde(default = "default_universe_domain")]
74 pub universe_domain: String,
75}
76
77#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
78#[serde(deny_unknown_fields)]
79pub struct KmsKeyConfig {
80 #[serde(default = "default_location")]
81 pub location: String,
82 #[validate(length(min = 1, message = "key_ring_id name cannot be empty"))]
83 pub key_ring_id: String,
84 #[validate(length(min = 1, message = "key_id cannot be empty"))]
85 pub key_id: String,
86 #[serde(default = "default_key_version")]
87 pub key_version: u32,
88}
89
90#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
91#[serde(deny_unknown_fields)]
92pub struct GoogleCloudKmsSignerFileConfig {
93 #[validate(nested)]
94 pub service_account: ServiceAccountConfig,
95 #[validate(nested)]
96 pub key: KmsKeyConfig,
97}
98
99impl SignerConfigValidate for GoogleCloudKmsSignerFileConfig {
100 fn validate(&self) -> Result<(), ConfigFileError> {
101 validate_with_validator(self)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::models::SecretString;
109
110 #[test]
111 fn test_google_cloud_kms_signer_file_config_valid() {
112 let config = GoogleCloudKmsSignerFileConfig {
113 service_account: ServiceAccountConfig {
114 project_id: "project-123".to_string(),
115 private_key_id: PlainOrEnvValue::Plain {
116 value: SecretString::new("private-key-id"),
117 },
118 private_key: PlainOrEnvValue::Plain {
119 value: SecretString::new("private-key"),
120 },
121 client_email: PlainOrEnvValue::Plain {
122 value: SecretString::new("client@email.com"),
123 },
124 client_id: "client-id-123".to_string(),
125 auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
126 token_uri: "https://oauth2.googleapis.com/token".to_string(),
127 auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
128 client_x509_cert_url: "https://www.googleapis.com/robot/v1/metadata/x509/solana-signer%40forward-emitter-459820-r7.iam.gserviceaccount.com".to_string(),
129 universe_domain: "googleapis.com".to_string(),
130 },
131 key: KmsKeyConfig {
132 location: default_location(),
133 key_ring_id: "ring-1".to_string(),
134 key_id: "key-1".to_string(),
135 key_version: 1,
136 },
137 };
138
139 assert!(Validate::validate(&config).is_ok());
140 assert!(SignerConfigValidate::validate(&config).is_ok());
141 }
142
143 #[test]
144 fn test_google_cloud_kms_signer_file_config_empty_project_id() {
145 let config = GoogleCloudKmsSignerFileConfig {
146 service_account: ServiceAccountConfig {
147 project_id: "".to_string(),
148 private_key_id: PlainOrEnvValue::Plain {
149 value: SecretString::new("private-key-id"),
150 },
151 private_key: PlainOrEnvValue::Plain {
152 value: SecretString::new("private-key"),
153 },
154 client_email: PlainOrEnvValue::Plain {
155 value: SecretString::new("client@email.com"),
156 },
157 client_id: "client-id-123".to_string(),
158 auth_uri: default_auth_uri(),
159 token_uri: default_token_uri(),
160 auth_provider_x509_cert_url: default_auth_provider_x509_cert_url(),
161 client_x509_cert_url: default_client_x509_cert_url(),
162 universe_domain: default_universe_domain(),
163 },
164 key: KmsKeyConfig {
165 location: default_location(),
166 key_ring_id: "ring-1".to_string(),
167 key_id: "key-1".to_string(),
168 key_version: 1,
169 },
170 };
171
172 let result = SignerConfigValidate::validate(&config);
173
174 assert!(result.is_err());
175 if let Err(e) = result {
176 let error_message = format!("{:?}", e);
177 assert!(error_message.contains("project_id"));
178 assert!(error_message.contains("cannot be empty"));
179 }
180 }
181
182 #[test]
183 fn test_google_cloud_kms_signer_file_config_empty_key_ring_id() {
184 let config = GoogleCloudKmsSignerFileConfig {
185 service_account: ServiceAccountConfig {
186 project_id: "project-123".to_string(),
187 private_key_id: PlainOrEnvValue::Plain {
188 value: SecretString::new("private-key-id"),
189 },
190 private_key: PlainOrEnvValue::Plain {
191 value: SecretString::new("private-key"),
192 },
193 client_email: PlainOrEnvValue::Plain {
194 value: SecretString::new("client@email.com"),
195 },
196 client_id: "client-id-123".to_string(),
197 auth_uri: default_auth_uri(),
198 token_uri: default_token_uri(),
199 auth_provider_x509_cert_url: default_auth_provider_x509_cert_url(),
200 client_x509_cert_url: default_client_x509_cert_url(),
201 universe_domain: default_universe_domain(),
202 },
203 key: KmsKeyConfig {
204 location: default_location(),
205 key_ring_id: "".to_string(),
206 key_id: "key-1".to_string(),
207 key_version: 1,
208 },
209 };
210
211 let result = SignerConfigValidate::validate(&config);
212 assert!(result.is_err());
213 if let Err(e) = result {
214 let error_message = format!("{:?}", e);
215 assert!(error_message.contains("key_ring_id"));
216 assert!(error_message.contains("cannot be empty"));
217 }
218 }
219
220 #[test]
221 fn test_google_cloud_kms_signer_file_config_empty_key_id() {
222 let config = GoogleCloudKmsSignerFileConfig {
223 service_account: ServiceAccountConfig {
224 project_id: "project-123".to_string(),
225 private_key_id: PlainOrEnvValue::Plain {
226 value: SecretString::new("private-key-id"),
227 },
228 private_key: PlainOrEnvValue::Plain {
229 value: SecretString::new("private-key"),
230 },
231 client_email: PlainOrEnvValue::Plain {
232 value: SecretString::new("client@email.com"),
233 },
234 client_id: "client-id-123".to_string(),
235 auth_uri: default_auth_uri(),
236 token_uri: default_token_uri(),
237 auth_provider_x509_cert_url: default_auth_provider_x509_cert_url(),
238 client_x509_cert_url: default_client_x509_cert_url(),
239 universe_domain: default_universe_domain(),
240 },
241 key: KmsKeyConfig {
242 location: default_location(),
243 key_ring_id: "ring-1".to_string(),
244 key_id: "".to_string(),
245 key_version: 1,
246 },
247 };
248
249 let result = SignerConfigValidate::validate(&config);
250
251 assert!(result.is_err());
252 if let Err(e) = result {
253 let error_message = format!("{:?}", e);
254 assert!(error_message.contains("key_id"));
255 assert!(error_message.contains("cannot be empty"));
256 }
257 }
258
259 #[test]
260 fn test_serde_deserialize() {
261 let json = r#"
262 {
263 "service_account": {
264 "project_id": "project-123",
265 "private_key_id": {
266 "type": "plain",
267 "value": "private-key-id"
268 },
269 "private_key": {
270 "type": "plain",
271 "value": "private-key"
272 },
273 "client_email": {
274 "type": "plain",
275 "value": "client@email.com"
276 },
277 "client_id": "client-id-123"
278 },
279 "key": {
280 "key_ring_id": "ring-1",
281 "key_id": "key-1",
282 "key_version": 1
283 }
284 }
285 "#;
286
287 let config: GoogleCloudKmsSignerFileConfig = serde_json::from_str(json).unwrap();
288 assert_eq!(config.service_account.project_id, "project-123");
289 assert_eq!(
290 config
291 .service_account
292 .private_key_id
293 .get_value()
294 .unwrap()
295 .to_str()
296 .as_str(),
297 "private-key-id"
298 );
299 assert_eq!(
300 config
301 .service_account
302 .private_key
303 .get_value()
304 .unwrap()
305 .to_str()
306 .as_str(),
307 "private-key"
308 );
309 assert_eq!(
310 config
311 .service_account
312 .client_email
313 .get_value()
314 .unwrap()
315 .to_str()
316 .as_str(),
317 "client@email.com"
318 );
319 assert_eq!(config.service_account.client_id, "client-id-123");
320 assert_eq!(config.key.key_ring_id, "ring-1");
321 assert_eq!(config.key.key_id, "key-1");
322 assert_eq!(config.key.key_version, 1);
323 }
324
325 #[test]
326 fn test_serde_unknown_field() {
327 let json = r#"
328 {
329 "service_account": {
330 "project_id": "project-123",
331 "private_key_id": {
332 "type": "plain",
333 "value": "private-key-id"
334 },
335 "private_key": {
336 "type": "plain",
337 "value": "private-key"
338 },
339 "client_email": {
340 "type": "plain",
341 "value": "client@email.com"
342 },
343 "client_id": "client-id-123"
344 },
345 "key": {
346 "key_ring_id": "ring-1",
347 "key_id": "key-1",
348 "key_version": 1
349 },
350 "unknown_field": "should cause error"
351 }
352 "#;
353
354 let result: Result<GoogleCloudKmsSignerFileConfig, _> = serde_json::from_str(json);
355 assert!(result.is_err());
356 }
357
358 #[test]
359 fn test_serde_serialize_deserialize() {
360 let config = GoogleCloudKmsSignerFileConfig {
361 service_account: ServiceAccountConfig {
362 project_id: "project-123".to_string(),
363 private_key_id: PlainOrEnvValue::Plain {
364 value: SecretString::new("private-key-id"),
365 },
366 private_key: PlainOrEnvValue::Plain {
367 value: SecretString::new("private-key"),
368 },
369 client_email: PlainOrEnvValue::Plain {
370 value: SecretString::new("client@email.com"),
371 },
372 client_id: "client-id-123".to_string(),
373 auth_uri: default_auth_uri(),
374 token_uri: default_token_uri(),
375 auth_provider_x509_cert_url: default_auth_provider_x509_cert_url(),
376 client_x509_cert_url: default_client_x509_cert_url(),
377 universe_domain: default_universe_domain(),
378 },
379 key: KmsKeyConfig {
380 location: default_location(),
381 key_ring_id: "ring-1".to_string(),
382 key_id: "key-1".to_string(),
383 key_version: 1,
384 },
385 };
386
387 let serialized = serde_json::to_string(&config).unwrap();
388 let deserialized: GoogleCloudKmsSignerFileConfig =
389 serde_json::from_str(&serialized).unwrap();
390
391 assert_eq!(
392 config.service_account.project_id,
393 deserialized.service_account.project_id
394 );
395 assert_eq!(config.key.key_id, deserialized.key.key_id);
396 assert_eq!(config.key.key_ring_id, deserialized.key.key_ring_id);
397 assert_eq!(config.key.key_version, deserialized.key.key_version);
398 }
399
400 #[test]
401 fn test_defaults_applied() {
402 let json = r#"
403 {
404 "service_account": {
405 "project_id": "project-123",
406 "private_key_id": { "type": "plain", "value": "private-key-id" },
407 "private_key": { "type": "plain", "value": "private-key" },
408 "client_email": { "type": "plain", "value": "client@email.com" },
409 "client_id": "client-id-123"
410 },
411 "key": {
412 "key_ring_id": "ring-1",
413 "key_id": "key-1"
414 }
415 }
416 "#;
417 let config: GoogleCloudKmsSignerFileConfig = serde_json::from_str(json).unwrap();
418 assert_eq!(config.service_account.auth_uri, default_auth_uri());
419 assert_eq!(config.service_account.token_uri, default_token_uri());
420 assert_eq!(config.key.key_version, 1);
421 }
422}