openzeppelin_relayer/config/config_file/signer/
vault_cloud.rs

1//! Configuration for HashiCorp Vault Cloud signer
2//!
3//! This module provides configuration for integrating with HashiCorp Cloud Platform (HCP) Vault,
4//! which is the managed service offering of Vault. The configuration handles the OAuth2 client
5//! credentials flow required for authenticating with HCP.
6//!
7//! The configuration supports:
8//! - Client ID and Secret for OAuth2 authentication
9//! - Organization ID for the HCP account
10//! - Project ID within the organization
11//! - Application name for identification in logs and metrics
12//! - Key name to use for signing operations
13//!
14//! HCP Vault differs from self-hosted Vault by requiring OAuth-based authentication
15//! instead of token or AppRole based authentication methods.
16use crate::{
17    config::ConfigFileError,
18    models::{validate_plain_or_env_value, PlainOrEnvValue},
19};
20use serde::{Deserialize, Serialize};
21use validator::Validate;
22
23use super::{validate_with_validator, SignerConfigValidate};
24
25#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
26#[serde(deny_unknown_fields)]
27pub struct VaultCloudSignerFileConfig {
28    #[validate(length(min = 1, message = "Client ID cannot be empty"))]
29    pub client_id: String,
30    #[validate(custom(function = "validate_plain_or_env_value"))]
31    pub client_secret: PlainOrEnvValue,
32    #[validate(length(min = 1, message = "Organization ID cannot be empty"))]
33    pub org_id: String,
34    #[validate(length(min = 1, message = "Project ID cannot be empty"))]
35    pub project_id: String,
36    #[validate(length(min = 1, message = "Application name cannot be empty"))]
37    pub app_name: String,
38    #[validate(length(min = 1, message = "Key name cannot be empty"))]
39    pub key_name: String,
40}
41
42impl SignerConfigValidate for VaultCloudSignerFileConfig {
43    fn validate(&self) -> Result<(), ConfigFileError> {
44        validate_with_validator(self)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::models::SecretString;
52
53    #[test]
54    fn test_vault_cloud_signer_file_config_valid() {
55        let config = VaultCloudSignerFileConfig {
56            client_id: "client-123".to_string(),
57            client_secret: PlainOrEnvValue::Plain {
58                value: SecretString::new("secret-abc"),
59            },
60            org_id: "org-456".to_string(),
61            project_id: "proj-789".to_string(),
62            app_name: "my-cloud-app".to_string(),
63            key_name: "hcp-key".to_string(),
64        };
65
66        assert!(Validate::validate(&config).is_ok());
67        assert!(SignerConfigValidate::validate(&config).is_ok());
68    }
69
70    #[test]
71    fn test_vault_cloud_signer_file_config_empty_client_id() {
72        let config = VaultCloudSignerFileConfig {
73            client_id: "".to_string(),
74            client_secret: PlainOrEnvValue::Plain {
75                value: SecretString::new("secret-abc"),
76            },
77            org_id: "org-456".to_string(),
78            project_id: "proj-789".to_string(),
79            app_name: "my-cloud-app".to_string(),
80            key_name: "hcp-key".to_string(),
81        };
82
83        let result = SignerConfigValidate::validate(&config);
84        assert!(result.is_err());
85        if let Err(e) = result {
86            let error_message = format!("{:?}", e);
87            assert!(error_message.contains("client_id"));
88            assert!(error_message.contains("cannot be empty"));
89        }
90    }
91
92    #[test]
93    fn test_vault_cloud_signer_file_config_empty_client_secret() {
94        let config = VaultCloudSignerFileConfig {
95            client_id: "client-123".to_string(),
96            client_secret: PlainOrEnvValue::Plain {
97                value: SecretString::new(""),
98            },
99            org_id: "org-456".to_string(),
100            project_id: "proj-789".to_string(),
101            app_name: "my-cloud-app".to_string(),
102            key_name: "hcp-key".to_string(),
103        };
104
105        let result = SignerConfigValidate::validate(&config);
106        assert!(result.is_err());
107        if let Err(e) = result {
108            let error_message = format!("{:?}", e);
109            assert!(error_message.contains("client_secret"));
110        }
111    }
112
113    #[test]
114    fn test_vault_cloud_signer_file_config_empty_org_id() {
115        let config = VaultCloudSignerFileConfig {
116            client_id: "client-123".to_string(),
117            client_secret: PlainOrEnvValue::Plain {
118                value: SecretString::new("secret-abc"),
119            },
120            org_id: "".to_string(),
121            project_id: "proj-789".to_string(),
122            app_name: "my-cloud-app".to_string(),
123            key_name: "hcp-key".to_string(),
124        };
125
126        let result = SignerConfigValidate::validate(&config);
127        assert!(result.is_err());
128        if let Err(e) = result {
129            let error_message = format!("{:?}", e);
130            assert!(error_message.contains("org_id"));
131            assert!(error_message.contains("cannot be empty"));
132        }
133    }
134
135    #[test]
136    fn test_vault_cloud_signer_file_config_empty_project_id() {
137        let config = VaultCloudSignerFileConfig {
138            client_id: "client-123".to_string(),
139            client_secret: PlainOrEnvValue::Plain {
140                value: SecretString::new("secret-abc"),
141            },
142            org_id: "org-456".to_string(),
143            project_id: "".to_string(),
144            app_name: "my-cloud-app".to_string(),
145            key_name: "hcp-key".to_string(),
146        };
147
148        let result = SignerConfigValidate::validate(&config);
149        assert!(result.is_err());
150        if let Err(e) = result {
151            let error_message = format!("{:?}", e);
152            assert!(error_message.contains("project_id"));
153            assert!(error_message.contains("cannot be empty"));
154        }
155    }
156
157    #[test]
158    fn test_vault_cloud_signer_file_config_empty_app_name() {
159        let config = VaultCloudSignerFileConfig {
160            client_id: "client-123".to_string(),
161            client_secret: PlainOrEnvValue::Plain {
162                value: SecretString::new("secret-abc"),
163            },
164            org_id: "org-456".to_string(),
165            project_id: "proj-789".to_string(),
166            app_name: "".to_string(),
167            key_name: "hcp-key".to_string(),
168        };
169
170        let result = SignerConfigValidate::validate(&config);
171        assert!(result.is_err());
172        if let Err(e) = result {
173            let error_message = format!("{:?}", e);
174            assert!(error_message.contains("app_name"));
175            assert!(error_message.contains("cannot be empty"));
176        }
177    }
178
179    #[test]
180    fn test_vault_cloud_signer_file_config_empty_key_name() {
181        let config = VaultCloudSignerFileConfig {
182            client_id: "client-123".to_string(),
183            client_secret: PlainOrEnvValue::Plain {
184                value: SecretString::new("secret-abc"),
185            },
186            org_id: "org-456".to_string(),
187            project_id: "proj-789".to_string(),
188            app_name: "my-cloud-app".to_string(),
189            key_name: "".to_string(),
190        };
191
192        let result = SignerConfigValidate::validate(&config);
193        assert!(result.is_err());
194        if let Err(e) = result {
195            let error_message = format!("{:?}", e);
196            assert!(error_message.contains("key_name"));
197            assert!(error_message.contains("cannot be empty"));
198        }
199    }
200
201    #[test]
202    fn test_vault_cloud_signer_file_config_multiple_errors() {
203        // Config with multiple validation errors
204        let config = VaultCloudSignerFileConfig {
205            client_id: "".to_string(),
206            client_secret: PlainOrEnvValue::Plain {
207                value: SecretString::new(""),
208            },
209            org_id: "".to_string(),
210            project_id: "".to_string(),
211            app_name: "".to_string(),
212            key_name: "".to_string(),
213        };
214
215        let result = validate_with_validator(&config);
216        assert!(result.is_err());
217
218        if let Err(e) = result {
219            if let ConfigFileError::InvalidFormat(msg) = e {
220                assert!(msg.contains("client_id"));
221                assert!(msg.contains("client_secret"));
222                assert!(msg.contains("org_id"));
223                assert!(msg.contains("project_id"));
224                assert!(msg.contains("app_name"));
225                assert!(msg.contains("key_name"));
226            } else {
227                panic!("Expected ConfigFileError::InvalidFormat, got {:?}", e);
228            }
229        }
230    }
231
232    #[test]
233    fn test_serde_deserialize() {
234        let json = r#"
235        {
236            "client_id": "client-123",
237            "client_secret": {
238                "type": "plain",
239                "value":"secret-abc"
240            },
241            "org_id": "org-456",
242            "project_id": "proj-789",
243            "app_name": "my-cloud-app",
244            "key_name": "hcp-key"
245        }
246        "#;
247
248        let config: VaultCloudSignerFileConfig = serde_json::from_str(json).unwrap();
249        assert_eq!(config.client_id, "client-123");
250        assert_eq!(
251            config.client_secret.get_value().unwrap().to_str().as_str(),
252            "secret-abc"
253        );
254        assert_eq!(config.org_id, "org-456");
255        assert_eq!(config.project_id, "proj-789");
256        assert_eq!(config.app_name, "my-cloud-app");
257        assert_eq!(config.key_name, "hcp-key");
258    }
259
260    #[test]
261    fn test_serde_unknown_field() {
262        let json = r#"
263        {
264            "client_id": "client-123",
265            "client_secret": "secret-abc",
266            "org_id": "org-456",
267            "project_id": "proj-789",
268            "app_name": "my-cloud-app",
269            "key_name": "hcp-key",
270            "unknown_field": "should cause error"
271        }
272        "#;
273
274        let result: Result<VaultCloudSignerFileConfig, _> = serde_json::from_str(json);
275        assert!(result.is_err());
276    }
277
278    #[test]
279    fn test_serde_serialize_deserialize() {
280        let config = VaultCloudSignerFileConfig {
281            client_id: "client-123".to_string(),
282            client_secret: PlainOrEnvValue::Plain {
283                value: SecretString::new("secret-abc"),
284            },
285            org_id: "org-456".to_string(),
286            project_id: "proj-789".to_string(),
287            app_name: "my-cloud-app".to_string(),
288            key_name: "hcp-key".to_string(),
289        };
290
291        let serialized = serde_json::to_string(&config).unwrap();
292        let deserialized: VaultCloudSignerFileConfig = serde_json::from_str(&serialized).unwrap();
293
294        assert_eq!(config.app_name, deserialized.app_name);
295        assert_eq!(config.client_id, deserialized.client_id);
296        assert_eq!(config.key_name, deserialized.key_name);
297        assert_eq!(config.org_id, deserialized.org_id);
298        assert_eq!(config.project_id, deserialized.project_id);
299        assert_ne!(config.client_secret, deserialized.client_secret);
300    }
301
302    #[test]
303    fn test_serde_pretty_json() {
304        let json = r#"{
305        "client_id": "client-123",
306        "client_secret": {
307            "type": "plain",
308            "value":"secret-abc"
309        },
310        "org_id": "org-456",
311        "project_id": "proj-789",
312        "app_name": "my-cloud-app",
313        "key_name": "hcp-key"
314        }"#;
315
316        let config: VaultCloudSignerFileConfig = serde_json::from_str(json).unwrap();
317        assert_eq!(config.client_id, "client-123");
318        assert_eq!(
319            config.client_secret.get_value().unwrap().to_str().as_str(),
320            "secret-abc"
321        );
322    }
323
324    #[test]
325    fn test_validate_with_validator() {
326        let valid_config = VaultCloudSignerFileConfig {
327            client_id: "client-123".to_string(),
328            client_secret: PlainOrEnvValue::Plain {
329                value: SecretString::new("secret-abc"),
330            },
331            org_id: "org-456".to_string(),
332            project_id: "proj-789".to_string(),
333            app_name: "my-cloud-app".to_string(),
334            key_name: "hcp-key".to_string(),
335        };
336
337        let invalid_config = VaultCloudSignerFileConfig {
338            client_id: "".to_string(),
339            client_secret: PlainOrEnvValue::Plain {
340                value: SecretString::new("secret-abc"),
341            },
342            org_id: "org-456".to_string(),
343            project_id: "proj-789".to_string(),
344            app_name: "my-cloud-app".to_string(),
345            key_name: "hcp-key".to_string(),
346        };
347
348        assert!(Validate::validate(&valid_config).is_ok());
349        assert!(Validate::validate(&invalid_config).is_err());
350    }
351}