openzeppelin_relayer/config/config_file/signer/
vault.rs

1//! Configuration for a Vault signer
2//!
3//! This module provides configuration for interacting with HashiCorp Vault as a key
4//! management system for signing operations. It supports both AppRole authentication.
5//!
6//! The configuration supports:
7//! - Vault server address (URL)
8//! - Optional namespace (for Vault Enterprise)
9//! - AppRole authentication (role_id and secret_id)
10//! - Key name to use for signing operations
11//! - Optional mount point override for Transit engine
12use crate::{
13    config::ConfigFileError,
14    models::{validate_plain_or_env_value, PlainOrEnvValue},
15};
16use serde::{Deserialize, Serialize};
17use validator::Validate;
18
19use super::{validate_with_validator, SignerConfigValidate};
20
21#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Validate)]
22#[serde(deny_unknown_fields)]
23pub struct VaultSignerFileConfig {
24    #[validate(url)]
25    pub address: String,
26    pub namespace: Option<String>,
27    #[validate(custom(function = "validate_plain_or_env_value"))]
28    pub role_id: PlainOrEnvValue,
29    #[validate(custom(function = "validate_plain_or_env_value"))]
30    pub secret_id: PlainOrEnvValue,
31    #[validate(length(min = 1, message = "Vault key name cannot be empty"))]
32    pub key_name: String,
33    pub mount_point: Option<String>,
34}
35
36impl SignerConfigValidate for VaultSignerFileConfig {
37    fn validate(&self) -> Result<(), ConfigFileError> {
38        validate_with_validator(self)
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use crate::models::SecretString;
45
46    use super::*;
47    use validator::Validate;
48
49    #[test]
50    fn test_vault_signer_file_config_valid() {
51        let config = VaultSignerFileConfig {
52            address: "https://vault.example.com:8200".to_string(),
53            namespace: Some("namespace1".to_string()),
54            role_id: PlainOrEnvValue::Plain {
55                value: SecretString::new("role-123"),
56            },
57            secret_id: PlainOrEnvValue::Plain {
58                value: SecretString::new("secret-456"),
59            },
60            key_name: "my-key".to_string(),
61            mount_point: Some("transit".to_string()),
62        };
63
64        assert!(Validate::validate(&config).is_ok());
65        assert!(SignerConfigValidate::validate(&config).is_ok());
66    }
67
68    #[test]
69    fn test_vault_signer_file_config_invalid_address() {
70        let config = VaultSignerFileConfig {
71            address: "not-a-url".to_string(),
72            namespace: Some("namespace1".to_string()),
73            role_id: PlainOrEnvValue::Plain {
74                value: SecretString::new("role-123"),
75            },
76            secret_id: PlainOrEnvValue::Plain {
77                value: SecretString::new("secret-456"),
78            },
79            key_name: "my-key".to_string(),
80            mount_point: Some("transit".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("address"));
88        }
89    }
90
91    #[test]
92    fn test_vault_signer_file_config_empty_role_id() {
93        let config = VaultSignerFileConfig {
94            address: "https://vault.example.com:8200".to_string(),
95            namespace: Some("namespace1".to_string()),
96            role_id: PlainOrEnvValue::Plain {
97                value: SecretString::new(""),
98            },
99            secret_id: PlainOrEnvValue::Plain {
100                value: SecretString::new("secret-456"),
101            },
102            key_name: "my-key".to_string(),
103            mount_point: Some("transit".to_string()),
104        };
105
106        let result = SignerConfigValidate::validate(&config);
107        assert!(result.is_err());
108
109        if let Err(e) = result {
110            let error_message = format!("{:?}", e);
111            assert!(error_message.contains("role_id"));
112        }
113    }
114
115    #[test]
116    fn test_vault_signer_file_config_empty_secret_id() {
117        let config = VaultSignerFileConfig {
118            address: "https://vault.example.com:8200".to_string(),
119            namespace: None,
120            role_id: PlainOrEnvValue::Plain {
121                value: SecretString::new("role-123"),
122            },
123            secret_id: PlainOrEnvValue::Plain {
124                value: SecretString::new(""),
125            },
126            key_name: "my-key".to_string(),
127            mount_point: None,
128        };
129
130        let result = SignerConfigValidate::validate(&config);
131        assert!(result.is_err());
132
133        if let Err(e) = result {
134            let error_message = format!("{:?}", e);
135            assert!(error_message.contains("secret_id"));
136        }
137    }
138
139    #[test]
140    fn test_vault_signer_file_config_empty_key_name() {
141        let config = VaultSignerFileConfig {
142            address: "https://vault.example.com:8200".to_string(),
143            namespace: None,
144            role_id: PlainOrEnvValue::Plain {
145                value: SecretString::new("role-123"),
146            },
147            secret_id: PlainOrEnvValue::Plain {
148                value: SecretString::new("secret-456"),
149            },
150            key_name: "".to_string(),
151            mount_point: None,
152        };
153
154        let result = SignerConfigValidate::validate(&config);
155        assert!(result.is_err());
156
157        if let Err(e) = result {
158            let error_message = format!("{:?}", e);
159            assert!(error_message.contains("key_name"));
160            assert!(error_message.contains("cannot be empty"));
161        }
162    }
163
164    #[test]
165    fn test_vault_signer_file_config_optional_fields() {
166        let config = VaultSignerFileConfig {
167            address: "https://vault.example.com:8200".to_string(),
168            namespace: None,
169            role_id: PlainOrEnvValue::Plain {
170                value: SecretString::new("role-123"),
171            },
172            secret_id: PlainOrEnvValue::Plain {
173                value: SecretString::new("secret-456"),
174            },
175            key_name: "my-key".to_string(),
176            mount_point: None,
177        };
178
179        assert!(SignerConfigValidate::validate(&config).is_ok());
180    }
181
182    #[test]
183    fn test_vault_signer_file_config_multiple_errors() {
184        // Create a config with multiple validation errors
185        let config = VaultSignerFileConfig {
186            address: "invalid-url".to_string(),
187            namespace: None,
188            role_id: PlainOrEnvValue::Plain {
189                value: SecretString::new(""),
190            },
191            secret_id: PlainOrEnvValue::Plain {
192                value: SecretString::new(""),
193            },
194            key_name: "".to_string(),
195            mount_point: None,
196        };
197
198        let result = validate_with_validator(&config);
199        assert!(result.is_err());
200
201        if let Err(e) = result {
202            if let ConfigFileError::InvalidFormat(msg) = e {
203                assert!(msg.contains("address"));
204                assert!(msg.contains("role_id"));
205                assert!(msg.contains("secret_id"));
206                assert!(msg.contains("key_name"));
207            } else {
208                panic!("Expected ConfigFileError::InvalidFormat, got {:?}", e);
209            }
210        }
211    }
212
213    #[test]
214    fn test_serde_deserialize() {
215        let json = r#"
216        {
217            "address": "https://vault.example.com:8200",
218            "namespace": "my-namespace",
219            "role_id": {
220                "type": "plain",
221                "value": "role-123"
222            },
223            "secret_id": {
224                "type": "plain",
225                "value": "secret-456"
226            },
227            "key_name": "my-key",
228            "mount_point": "transit"
229        }
230        "#;
231
232        let config: VaultSignerFileConfig = serde_json::from_str(json).unwrap();
233        assert_eq!(config.address, "https://vault.example.com:8200");
234        assert_eq!(config.namespace, Some("my-namespace".to_string()));
235        assert_eq!(
236            config.role_id.get_value().unwrap().to_str().as_str(),
237            "role-123"
238        );
239        assert_eq!(
240            config.secret_id.get_value().unwrap().to_str().as_str(),
241            "secret-456"
242        );
243        assert_eq!(config.key_name, "my-key");
244        assert_eq!(config.mount_point, Some("transit".to_string()));
245    }
246
247    #[test]
248    fn test_serde_unknown_field() {
249        let json = r#"
250        {
251            "address": "https://vault.example.com:8200",
252            "namespace": "my-namespace",
253            "role_id": {
254                "type": "plain",
255                "value": "role-123"
256            },
257            "secret_id": {
258                "type": "plain",
259                "value": "secret-456"
260            },
261            "key_name": "my-key",
262            "mount_point": "transit",
263            "unknown_field": "should cause error"
264        }
265        "#;
266
267        let result: Result<VaultSignerFileConfig, _> = serde_json::from_str(json);
268        assert!(result.is_err());
269    }
270
271    #[test]
272    fn test_serde_serialize_deserialize() {
273        let config = VaultSignerFileConfig {
274            address: "https://vault.example.com:8200".to_string(),
275            namespace: Some("namespace1".to_string()),
276            role_id: PlainOrEnvValue::Plain {
277                value: SecretString::new("role-123"),
278            },
279            secret_id: PlainOrEnvValue::Plain {
280                value: SecretString::new("secret-456"),
281            },
282            key_name: "my-key".to_string(),
283            mount_point: Some("transit".to_string()),
284        };
285
286        let serialized = serde_json::to_string(&config).unwrap();
287        let deserialized: VaultSignerFileConfig = serde_json::from_str(&serialized).unwrap();
288
289        assert_eq!(config.address, deserialized.address);
290        assert_eq!(config.key_name, deserialized.key_name);
291        assert_eq!(config.mount_point, deserialized.mount_point);
292        assert_eq!(config.namespace, deserialized.namespace);
293        assert_ne!(config.role_id, deserialized.role_id);
294        assert_ne!(config.secret_id, deserialized.secret_id);
295    }
296}