1use serde::{Deserialize, Serialize};
7use std::path::PathBuf;
8use std::time::Duration;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct SecretsConfig {
13 #[serde(flatten)]
15 pub backend: SecretsBackend,
16 #[serde(default)]
18 pub common: CommonSecretsConfig,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(tag = "type", rename_all = "lowercase")]
24pub enum SecretsBackend {
25 Vault(VaultConfig),
27 File(FileConfig),
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct CommonSecretsConfig {
34 #[serde(default = "default_timeout")]
36 pub timeout_seconds: u64,
37 #[serde(default = "default_max_retries")]
39 pub max_retries: u32,
40 #[serde(default = "default_enable_cache")]
42 pub enable_cache: bool,
43 #[serde(default = "default_cache_ttl")]
45 pub cache_ttl_seconds: u64,
46 pub audit: Option<super::auditing::AuditConfig>,
48}
49
50impl Default for CommonSecretsConfig {
51 fn default() -> Self {
52 Self {
53 timeout_seconds: default_timeout(),
54 max_retries: default_max_retries(),
55 enable_cache: default_enable_cache(),
56 cache_ttl_seconds: default_cache_ttl(),
57 audit: None,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct VaultConfig {
65 pub url: String,
67 pub auth: VaultAuthConfig,
69 pub namespace: Option<String>,
71 #[serde(default = "default_vault_mount")]
73 pub mount_path: String,
74 #[serde(default = "default_vault_api_version")]
76 pub api_version: String,
77 #[serde(default)]
79 pub tls: VaultTlsConfig,
80 #[serde(default)]
82 pub connection: VaultConnectionConfig,
83}
84
85#[derive(Clone, Serialize, Deserialize)]
87#[serde(tag = "method", rename_all = "lowercase")]
88pub enum VaultAuthConfig {
89 Token {
91 token: String,
93 },
94 AppRole {
96 role_id: String,
98 secret_id: String,
100 #[serde(default = "default_approle_mount")]
102 mount_path: String,
103 },
104 Kubernetes {
106 #[serde(default = "default_k8s_token_path")]
108 token_path: String,
109 role: String,
111 #[serde(default = "default_k8s_mount")]
113 mount_path: String,
114 },
115 Aws {
117 region: String,
119 role: String,
121 #[serde(default = "default_aws_mount")]
123 mount_path: String,
124 },
125}
126
127impl std::fmt::Debug for VaultAuthConfig {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 match self {
130 VaultAuthConfig::Token { .. } => f
131 .debug_struct("VaultAuthConfig::Token")
132 .field("token", &"[REDACTED]")
133 .finish(),
134 VaultAuthConfig::AppRole {
135 role_id,
136 mount_path,
137 ..
138 } => f
139 .debug_struct("VaultAuthConfig::AppRole")
140 .field("role_id", role_id)
141 .field("secret_id", &"[REDACTED]")
142 .field("mount_path", mount_path)
143 .finish(),
144 VaultAuthConfig::Kubernetes {
145 token_path,
146 role,
147 mount_path,
148 } => f
149 .debug_struct("VaultAuthConfig::Kubernetes")
150 .field("token_path", token_path)
151 .field("role", role)
152 .field("mount_path", mount_path)
153 .finish(),
154 VaultAuthConfig::Aws {
155 region,
156 role,
157 mount_path,
158 } => f
159 .debug_struct("VaultAuthConfig::Aws")
160 .field("region", region)
161 .field("role", role)
162 .field("mount_path", mount_path)
163 .finish(),
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize, Default)]
170pub struct VaultTlsConfig {
171 #[serde(default)]
173 pub skip_verify: bool,
174 pub ca_cert: Option<PathBuf>,
176 pub client_cert: Option<PathBuf>,
178 pub client_key: Option<PathBuf>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct VaultConnectionConfig {
185 #[serde(default = "default_max_connections")]
187 pub max_connections: usize,
188 #[serde(default = "default_connection_timeout")]
190 pub connection_timeout_seconds: u64,
191 #[serde(default = "default_request_timeout")]
193 pub request_timeout_seconds: u64,
194}
195
196impl Default for VaultConnectionConfig {
197 fn default() -> Self {
198 Self {
199 max_connections: default_max_connections(),
200 connection_timeout_seconds: default_connection_timeout(),
201 request_timeout_seconds: default_request_timeout(),
202 }
203 }
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct FileConfig {
209 pub path: PathBuf,
211 #[serde(default = "default_file_format")]
213 pub format: FileFormat,
214 #[serde(default)]
216 pub encryption: FileEncryptionConfig,
217 pub permissions: Option<u32>,
219 #[serde(default)]
221 pub watch_for_changes: bool,
222 #[serde(default)]
224 pub backup: FileBackupConfig,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(rename_all = "lowercase")]
230pub enum FileFormat {
231 Json,
233 Yaml,
235 Toml,
237 Env,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct FileEncryptionConfig {
244 #[serde(default)]
246 pub enabled: bool,
247 #[serde(default = "default_encryption_algorithm")]
249 pub algorithm: String,
250 #[serde(default = "default_kdf")]
252 pub kdf: String,
253 #[serde(default)]
255 pub key: FileKeyConfig,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct FileKeyConfig {
261 #[serde(default = "default_key_provider")]
263 pub provider: String,
264 pub env_var: Option<String>,
266 pub service: Option<String>,
268 pub account: Option<String>,
270 pub file_path: Option<PathBuf>,
272}
273
274impl Default for FileKeyConfig {
275 fn default() -> Self {
276 Self {
277 provider: default_key_provider(),
278 env_var: None,
279 service: None,
280 account: None,
281 file_path: None,
282 }
283 }
284}
285
286impl Default for FileEncryptionConfig {
287 fn default() -> Self {
288 Self {
289 enabled: false,
290 algorithm: default_encryption_algorithm(),
291 kdf: default_kdf(),
292 key: FileKeyConfig::default(),
293 }
294 }
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct FileBackupConfig {
300 #[serde(default)]
302 pub enabled: bool,
303 pub backup_dir: Option<PathBuf>,
305 #[serde(default = "default_max_backups")]
307 pub max_backups: usize,
308 #[serde(default = "default_backup_before_write")]
310 pub backup_before_write: bool,
311}
312
313impl Default for FileBackupConfig {
314 fn default() -> Self {
315 Self {
316 enabled: false,
317 backup_dir: None,
318 max_backups: default_max_backups(),
319 backup_before_write: default_backup_before_write(),
320 }
321 }
322}
323
324fn default_timeout() -> u64 {
326 30
327}
328fn default_max_retries() -> u32 {
329 3
330}
331fn default_enable_cache() -> bool {
332 true
333}
334fn default_cache_ttl() -> u64 {
335 300
336}
337fn default_vault_mount() -> String {
338 "secret".to_string()
339}
340fn default_vault_api_version() -> String {
341 "v2".to_string()
342}
343fn default_approle_mount() -> String {
344 "approle".to_string()
345}
346fn default_k8s_token_path() -> String {
347 "/var/run/secrets/kubernetes.io/serviceaccount/token".to_string()
348}
349fn default_k8s_mount() -> String {
350 "kubernetes".to_string()
351}
352fn default_aws_mount() -> String {
353 "aws".to_string()
354}
355fn default_max_connections() -> usize {
356 10
357}
358fn default_connection_timeout() -> u64 {
359 10
360}
361fn default_request_timeout() -> u64 {
362 30
363}
364fn default_file_format() -> FileFormat {
365 FileFormat::Json
366}
367fn default_encryption_algorithm() -> String {
368 "AES-256-GCM".to_string()
369}
370fn default_kdf() -> String {
371 "PBKDF2".to_string()
372}
373fn default_key_provider() -> String {
374 "env".to_string()
375}
376fn default_max_backups() -> usize {
377 5
378}
379fn default_backup_before_write() -> bool {
380 true
381}
382
383impl SecretsConfig {
384 pub fn vault_with_token(url: String, token: String) -> Self {
386 Self {
387 backend: SecretsBackend::Vault(VaultConfig {
388 url,
389 auth: VaultAuthConfig::Token { token },
390 namespace: None,
391 mount_path: default_vault_mount(),
392 api_version: default_vault_api_version(),
393 tls: VaultTlsConfig::default(),
394 connection: VaultConnectionConfig::default(),
395 }),
396 common: CommonSecretsConfig::default(),
397 }
398 }
399
400 pub fn file_json(path: PathBuf) -> Self {
402 Self {
403 backend: SecretsBackend::File(FileConfig {
404 path,
405 format: FileFormat::Json,
406 encryption: FileEncryptionConfig::default(),
407 permissions: Some(0o600),
408 watch_for_changes: false,
409 backup: FileBackupConfig::default(),
410 }),
411 common: CommonSecretsConfig::default(),
412 }
413 }
414
415 pub fn backend_type(&self) -> &'static str {
417 match &self.backend {
418 SecretsBackend::Vault(_) => "vault",
419 SecretsBackend::File(_) => "file",
420 }
421 }
422
423 pub fn timeout(&self) -> Duration {
425 Duration::from_secs(self.common.timeout_seconds)
426 }
427
428 pub fn cache_ttl(&self) -> Duration {
430 Duration::from_secs(self.common.cache_ttl_seconds)
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_vault_config_creation() {
440 let config = SecretsConfig::vault_with_token(
441 "https://vault.example.com".to_string(),
442 "hvs.token123".to_string(),
443 );
444
445 assert_eq!(config.backend_type(), "vault");
446 if let SecretsBackend::Vault(vault_config) = &config.backend {
447 assert_eq!(vault_config.url, "https://vault.example.com");
448 if let VaultAuthConfig::Token { token } = &vault_config.auth {
449 assert_eq!(token, "hvs.token123");
450 } else {
451 panic!("Expected token auth");
452 }
453 } else {
454 panic!("Expected vault backend");
455 }
456 }
457
458 #[test]
459 fn test_file_config_creation() {
460 let path = PathBuf::from("/etc/secrets/app.json");
461 let config = SecretsConfig::file_json(path.clone());
462
463 assert_eq!(config.backend_type(), "file");
464 if let SecretsBackend::File(file_config) = &config.backend {
465 assert_eq!(file_config.path, path);
466 assert!(matches!(file_config.format, FileFormat::Json));
467 } else {
468 panic!("Expected file backend");
469 }
470 }
471
472 #[test]
473 fn test_common_config_defaults() {
474 let config = CommonSecretsConfig::default();
475 assert_eq!(config.timeout_seconds, 30);
476 assert_eq!(config.max_retries, 3);
477 assert!(config.enable_cache);
478 assert_eq!(config.cache_ttl_seconds, 300);
479 }
480
481 #[test]
482 fn test_timeout_conversion() {
483 let config = SecretsConfig::file_json(PathBuf::from("/test"));
484 assert_eq!(config.timeout(), Duration::from_secs(30));
485 assert_eq!(config.cache_ttl(), Duration::from_secs(300));
486 }
487}