openzeppelin_relayer/config/config_file/signer/
google_cloud_kms.rs

1//! Configuration for Google Cloud KMS signer
2//!
3//! This module provides configuration for using Google Cloud KMS as a signing mechanism.
4//! Google Cloud KMS allows you to manage cryptographic keys and perform signing operations
5//! without exposing private keys directly to your application.
6//!
7//! The configuration supports:
8//! - Service account credentials (project_id, private_key_id, private_key, client_email, etc.)
9//! - KMS key identification (key_ring_id, key_id, key_version)
10//! - Optional universe domain and other GCP-specific fields
11//!
12//! This configuration is used to securely interact with Google Cloud KMS for operations
13//! such as public key retrieval and message signing.
14use 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}