regent_sdk/secrets/mod.rs
1//! Secret management
2//!
3//! This module provides comprehensive secret management capabilities for Regent SDK.
4//! It supports multiple secret providers including local files, environment variables,
5//! and cloud-based secret managers (AWS, GCP).
6//!
7//! ## Features
8//!
9//! - **Multiple Providers**: Files, environment variables, AWS Secrets Manager, GCP Secret Manager
10//! - **Secret Pool**: Manage multiple providers with a unified interface
11//! - **Typed Secrets**: Retrieve secrets as specific types (string, structs, etc.)
12//! - **Builder Pattern**: Easy configuration of secret provider pools
13//!
14//! ## Quick Start
15//!
16//! ```no_run
17//! use regent_sdk::secrets::{SecretProvider, SecretProvidersPoolBuilder};
18//!
19//! // Create a pool with file-based secrets
20//! let pool = SecretProvidersPoolBuilder::new()
21//! .add_default_provider("files", SecretProvider::files())
22//! .build()
23//! .unwrap();
24//!
25//! // Or use environment variables
26//! let pool = SecretProvidersPool::from("env", SecretProvider::env_var());
27//! ```
28
29pub mod local;
30pub mod remote;
31
32#[cfg(feature = "aws-secretsmanager")]
33use aws_config::SdkConfig as AwsConfig;
34use serde::de::DeserializeOwned;
35use serde::{Deserialize, Serialize};
36use std::collections::HashMap;
37use std::fmt::Debug;
38use std::sync::Arc;
39use tokio::sync::Mutex;
40#[allow(unused)]
41use tracing::{debug, error, info, trace, warn};
42
43use crate::error::RegentError;
44use crate::secrets::local::environment_variables::EnvVarSecretProvider;
45use crate::secrets::local::files::FilesSecretProvider;
46#[cfg(feature = "aws-secretsmanager")]
47use crate::secrets::remote::aws_secrets_manager::AwsSecretsManagerProvider;
48#[cfg(feature = "gcp-secretmanager")]
49use crate::secrets::remote::gcp_secret_manager::GcpSecretProvider;
50
51/// A cache for storing resolved secrets to ensure idempotency.
52///
53/// This cache stores secrets that have been retrieved from providers, ensuring
54/// that the same secret value is used throughout a compliance operation,
55/// even if the underlying secret is rotated during the operation.
56#[derive(Debug, Clone)]
57#[allow(dead_code)]
58pub struct SecretCache {
59 /// Map from secret reference to cached secret value
60 cache: Arc<Mutex<HashMap<String, String>>>,
61}
62
63impl SecretCache {
64 /// Create a new, empty secret cache.
65 pub fn new() -> Self {
66 Self {
67 cache: Arc::new(Mutex::new(HashMap::new())),
68 }
69 }
70
71 /// Insert a secret into the cache.
72 ///
73 /// # Arguments
74 /// * `secret_ref` - The secret reference string (including provider if specified)
75 /// * `value` - The resolved secret value
76 pub async fn insert(&self, secret_ref: String, value: String) {
77 let mut cache = self.cache.lock().await;
78 cache.insert(secret_ref, value);
79 }
80
81 /// Get a secret from the cache.
82 ///
83 /// # Arguments
84 /// * `secret_ref` - The secret reference string (including provider if specified)
85 ///
86 /// # Returns
87 /// * `Some(String)` if the secret is in the cache
88 /// * `None` if the secret is not cached
89 pub async fn get(&self, secret_ref: &str) -> Option<String> {
90 let cache = self.cache.lock().await;
91 cache.get(secret_ref).cloned()
92 }
93
94 /// Check if a secret is in the cache.
95 ///
96 /// # Arguments
97 /// * `secret_ref` - The secret reference string (including provider if specified)
98 ///
99 /// # Returns
100 /// * `true` if the secret is cached
101 /// * `false` otherwise
102 pub async fn contains(&self, secret_ref: &str) -> bool {
103 let cache = self.cache.lock().await;
104 cache.contains_key(secret_ref)
105 }
106}
107
108/// Enum representing different types of secret providers.
109///
110/// Each variant wraps a specific secret provider implementation.
111///
112/// # Variants
113///
114/// - `Files`: Secret provider that reads from files
115/// - `EnvironmentVariable`: Secret provider that reads from environment variables
116/// - `AwsSecretsManager`: AWS Secrets Manager provider (requires `aws-secretsmanager` feature)
117/// - `GcpSecretManager`: Google Cloud Secret Manager provider (requires `gcp-secretmanager` feature)
118///
119/// # Example
120///
121/// ```no_run
122/// use regent_sdk::secrets::SecretProvider;
123///
124/// // Create a file-based provider
125/// let provider = SecretProvider::files();
126///
127/// // Create an environment variable provider
128/// let provider = SecretProvider::env_var();
129///
130/// // Create an AWS provider (requires feature flag)
131/// // let provider = SecretProvider::aws_secretsmanager(aws_config);
132/// ```
133#[derive(Clone)]
134pub enum SecretProvider {
135 /// Secret provider that retrieves secrets from files on disk.
136 Files(FilesSecretProvider),
137 /// Secret provider that retrieves secrets from environment variables.
138 EnvironmentVariable(EnvVarSecretProvider),
139 /// AWS Secrets Manager provider.
140 ///
141 /// Requires the `aws-secretsmanager` feature to be enabled.
142 #[cfg(feature = "aws-secretsmanager")]
143 AwsSecretsManager(AwsSecretsManagerProvider),
144 /// Google Cloud Secret Manager provider.
145 ///
146 /// Requires the `gcp-secretmanager` feature to be enabled.
147 #[cfg(feature = "gcp-secretmanager")]
148 GcpSecretManager(GcpSecretProvider),
149 // DelineaSecretServer,
150 // HashicorpVault,
151}
152
153impl SecretProvider {
154 /// Create a file-based secret provider.
155 ///
156 /// Secrets are retrieved from files on the filesystem.
157 /// The secret reference should be the file path.
158 ///
159 /// # Example
160 ///
161 /// ```no_run
162 /// use regent_sdk::secrets::SecretProvider;
163 ///
164 /// let provider = SecretProvider::files();
165 /// // Secret reference: "/path/to/secret/file"
166 /// ```
167 pub fn files() -> Self {
168 Self::Files(FilesSecretProvider::new())
169 }
170
171 /// Create an environment variable-based secret provider.
172 ///
173 /// Secrets are retrieved from environment variables.
174 /// The secret reference should be the environment variable name.
175 ///
176 /// # Example
177 ///
178 /// ```no_run
179 /// use regent_sdk::secrets::SecretProvider;
180 ///
181 /// let provider = SecretProvider::env_var();
182 /// // Secret reference: "MY_ENV_VAR"
183 /// ```
184 pub fn env_var() -> Self {
185 Self::EnvironmentVariable(EnvVarSecretProvider::new())
186 }
187
188 /// Create an AWS Secrets Manager provider.
189 ///
190 /// Requires the `aws-secretsmanager` feature to be enabled.
191 ///
192 /// # Arguments
193 ///
194 /// * `aws_config` - AWS SDK configuration
195 ///
196 /// # Example
197 ///
198 /// ```no_run
199 /// use regent_sdk::secrets::SecretProvider;
200 /// use aws_config::SdkConfig;
201 ///
202 /// let aws_config = SdkConfig::default();
203 /// let provider = SecretProvider::aws_secretsmanager(aws_config);
204 /// ```
205 #[cfg(feature = "aws-secretsmanager")]
206 pub fn aws_secretsmanager(aws_config: AwsConfig) -> Self {
207 Self::AwsSecretsManager(AwsSecretsManagerProvider::from(aws_config))
208 }
209
210 /// Create a Google Cloud Secret Manager provider.
211 ///
212 /// Requires the `gcp-secretmanager` feature to be enabled.
213 ///
214 /// # Returns
215 ///
216 /// A new GCP Secret Manager provider or an error if initialization fails.
217 ///
218 /// # Example
219 ///
220 /// ```no_run
221 /// use regent_sdk::secrets::SecretProvider;
222 ///
223 /// let provider = SecretProvider::gcp_secretmanager().await.unwrap();
224 /// ```
225 #[cfg(feature = "gcp-secretmanager")]
226 pub async fn gcp_secretmanager() -> Result<Self, RegentError> {
227 match GcpSecretProvider::new().await {
228 Ok(gcp_secret_provider) => Ok(Self::GcpSecretManager(gcp_secret_provider)),
229 Err(details) => Err(RegentError::SecretsIssue(format!(
230 "Failed to create a GCP SecretManager client : {}",
231 details
232 ))),
233 }
234 }
235
236 pub async fn get_secret_typed<T: DeserializeOwned>(
237 &self,
238 secret_reference: &str,
239 ) -> Result<Secret<T>, RegentError> {
240 match self {
241 SecretProvider::Files(secret_provider) => {
242 secret_provider.get_secret_typed(secret_reference).await
243 }
244 SecretProvider::EnvironmentVariable(secret_provider) => {
245 secret_provider.get_secret_typed(secret_reference).await
246 }
247 #[cfg(feature = "aws-secretsmanager")]
248 SecretProvider::AwsSecretsManager(secret_provider) => {
249 secret_provider.get_secret_typed(secret_reference).await
250 }
251 #[cfg(feature = "gcp-secretmanager")]
252 SecretProvider::GcpSecretManager(secret_provider) => {
253 secret_provider.get_secret_typed(secret_reference).await
254 } // SecretProvider::DelineaSecretServer => {}
255 // SecretProvider::HashicorpVault => {}
256 }
257 }
258
259 pub async fn get_secret_raw(
260 &self,
261 secret_reference: &str,
262 ) -> Result<Secret<String>, RegentError> {
263 match self {
264 SecretProvider::Files(secret_provider) => {
265 secret_provider.get_secret_raw(secret_reference).await
266 }
267 SecretProvider::EnvironmentVariable(secret_provider) => {
268 secret_provider.get_secret_raw(secret_reference).await
269 }
270 #[cfg(feature = "aws-secretsmanager")]
271 SecretProvider::AwsSecretsManager(secret_provider) => {
272 secret_provider.get_secret_raw(secret_reference).await
273 }
274 #[cfg(feature = "gcp-secretmanager")]
275 SecretProvider::GcpSecretManager(secret_provider) => {
276 secret_provider.get_secret_raw(secret_reference).await
277 } // SecretProvider::DelineaSecretServer => {}
278 // SecretProvider::HashicorpVault => {}
279 }
280 }
281}
282
283/// Trait for synchronous secret providers.
284///
285/// Implement this trait for secret providers that can operate synchronously.
286///
287/// # Requirements
288///
289/// Implementers must provide methods for connecting to the secret backend
290/// and retrieving secrets in both typed and raw string formats.
291pub trait SecretProvidingSolution {
292 /// Connect to the secret provider backend.
293 ///
294 /// # Returns
295 ///
296 /// `Ok(())` if connection was successful, or a [`RegentError`] if it failed.
297 async fn connect(&mut self) -> Result<(), RegentError>;
298
299 /// Retrieve a secret as a specific type.
300 ///
301 /// # Type Parameters
302 ///
303 /// * `T` - The type to deserialize the secret into (must implement `DeserializeOwned`)
304 ///
305 /// # Arguments
306 ///
307 /// * `secret_reference` - The reference/identifier for the secret
308 ///
309 /// # Returns
310 ///
311 /// The secret wrapped in a [`Secret`] container, or a [`RegentError`] if retrieval failed.
312 async fn get_secret_typed<T: DeserializeOwned>(
313 &self,
314 secret_reference: &str,
315 ) -> Result<Secret<T>, RegentError>;
316
317 /// Retrieve a secret as a raw string.
318 ///
319 /// # Arguments
320 ///
321 /// * `secret_reference` - The reference/identifier for the secret
322 ///
323 /// # Returns
324 ///
325 /// The secret as a string wrapped in a [`Secret`] container, or a [`RegentError`] if retrieval failed.
326 async fn get_secret_raw(&self, secret_reference: &str) -> Result<Secret<String>, RegentError>;
327}
328
329/// Trait for asynchronous secret providers.
330///
331/// Similar to [`SecretProvidingSolution`] but for providers that require async operations.
332///
333/// # Requirements
334///
335/// Implementers must provide methods for connecting to the secret backend
336/// and retrieving secrets in both typed and raw string formats.
337pub trait AsyncSecretProvidingSolution {
338 /// Connect to the secret provider backend.
339 ///
340 /// # Returns
341 ///
342 /// `Ok(())` if connection was successful, or a [`RegentError`] if it failed.
343 async fn connect(&mut self) -> Result<(), RegentError>;
344
345 /// Retrieve a secret as a specific type.
346 ///
347 /// # Type Parameters
348 ///
349 /// * `T` - The type to deserialize the secret into (must implement `DeserializeOwned`)
350 ///
351 /// # Arguments
352 ///
353 /// * `secret_reference` - The reference/identifier for the secret
354 ///
355 /// # Returns
356 ///
357 /// The secret wrapped in a [`Secret`] container, or a [`RegentError`] if retrieval failed.
358 async fn get_secret_typed<T: DeserializeOwned>(
359 &self,
360 secret_reference: &str,
361 ) -> Result<Secret<T>, RegentError>;
362
363 /// Retrieve a secret as a raw string.
364 ///
365 /// # Arguments
366 ///
367 /// * `secret_reference` - The reference/identifier for the secret
368 ///
369 /// # Returns
370 ///
371 /// The secret as a string wrapped in a [`Secret`] container, or a [`RegentError`] if retrieval failed.
372 async fn get_secret_raw(&self, secret_reference: &str) -> Result<Secret<String>, RegentError>;
373}
374
375/// A wrapper type that holds secret content and prevents accidental leaking.
376///
377/// This type wraps secret values and implements a custom `Debug` formatter that
378/// redacts the actual secret content, preventing accidental exposure in logs.
379///
380/// # Type Parameters
381///
382/// * `T` - The type of the secret value
383///
384/// # Example
385///
386/// ```no_run
387/// use regent_sdk::secrets::Secret;
388///
389/// let password = Secret::from("db_password", "super_secret_123".to_string());
390///
391/// // The secret content is redacted in Debug output
392/// println!("{:?}", password); // Prints: Secret { sec_ref: "db_password", inner: <redacted> }
393///
394/// // Access the inner value when needed
395/// let actual_password = password.inner();
396/// ```
397// Wrapper type which holds secrets content and helps to avoid leaking secrets (usual or debug logging in general...)
398#[derive(Clone, PartialEq, Serialize, Deserialize)]
399pub struct Secret<T> {
400 /// Reference identifier for this secret (used for auditing and debugging).
401 sec_ref: String,
402 /// The actual secret value.
403 inner: T,
404}
405
406impl<T> Debug for Secret<T>
407where
408 T: Debug,
409{
410 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411 f.debug_struct("Secret")
412 .field("sec_ref", &self.sec_ref)
413 .field("inner", &format_args!("<redacted>"))
414 .finish()
415 }
416}
417
418impl<T> Secret<T> {
419 /// Create a new secret wrapper.
420 ///
421 /// # Arguments
422 ///
423 /// * `sec_ref` - A reference identifier for this secret (used for auditing)
424 /// * `inner` - The actual secret value
425 ///
426 /// # Example
427 ///
428 /// ```no_run
429 /// use regent_sdk::secrets::Secret;
430 ///
431 /// let secret = Secret::from("api_key", "sk-1234567890");
432 /// ```
433 pub fn from(sec_ref: &str, inner: T) -> Self {
434 Self {
435 sec_ref: sec_ref.to_string(),
436 inner,
437 }
438 }
439
440 /// Consume the secret wrapper and return the inner value.
441 ///
442 /// **Warning**: This exposes the secret value. Use with caution.
443 ///
444 /// # Returns
445 ///
446 /// The inner secret value.
447 ///
448 /// # Example
449 ///
450 /// ```no_run
451 /// use regent_sdk::secrets::Secret;
452 ///
453 /// let secret = Secret::from("password", "my_password");
454 /// let password: String = secret.inner();
455 /// ```
456 pub fn inner(self) -> T {
457 self.inner
458 }
459}
460
461/// Reference to a secret in a secret provider.
462///
463/// This struct is used to identify secrets without containing the actual secret value.
464/// It consists of a secret reference string and an optional provider name.
465///
466/// # Example
467///
468/// ```no_run
469/// use regent_sdk::secrets::SecretReference;
470///
471/// // Reference to a secret in the default provider
472/// let ref1 = SecretReference::from("my_secret", None);
473///
474/// // Reference to a secret in a specific provider
475/// let ref2 = SecretReference::from("my_secret", Some("aws".to_string()));
476/// ```
477#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
478#[serde(rename_all = "PascalCase")]
479#[serde(deny_unknown_fields)]
480pub struct SecretReference {
481 /// The reference/identifier for the secret (e.g., file path, env var name, secret name).
482 sec_ref: String,
483 /// Optional name of the secret provider to use.
484 /// If `None`, the default provider will be used.
485 provider: Option<String>,
486}
487
488impl SecretReference {
489 /// Create a new secret reference.
490 ///
491 /// # Arguments
492 ///
493 /// * `sec_ref` - The reference/identifier for the secret
494 /// * `provider` - Optional name of the secret provider
495 ///
496 /// # Example
497 ///
498 /// ```no_run
499 /// use regent_sdk::secrets::SecretReference;
500 ///
501 /// // Use default provider
502 /// let ref1 = SecretReference::from("password", None);
503 ///
504 /// // Use specific provider
505 /// let ref2 = SecretReference::from("api_key", Some("aws".to_string()));
506 /// ```
507 pub fn from(sec_ref: &str, provider: Option<String>) -> Self {
508 Self {
509 sec_ref: sec_ref.to_string(),
510 provider,
511 }
512 }
513
514 /// Get the secret reference string.
515 ///
516 /// # Returns
517 ///
518 /// A reference to the secret reference string.
519 pub fn sec_ref(&self) -> &str {
520 &self.sec_ref
521 }
522
523 /// Get the optional provider name.
524 ///
525 /// # Returns
526 ///
527 /// A reference to the optional provider name.
528 pub fn provider(&self) -> &Option<String> {
529 &self.provider
530 }
531}
532
533/// Builder for creating [`SecretProvidersPool`] instances.
534///
535/// This builder allows you to configure multiple secret providers and set a default.
536/// Use the builder pattern to add providers and then call [`SecretProvidersPoolBuilder::build()`] to create the pool.
537///
538/// # Example
539///
540/// ```no_run
541/// use regent_sdk::secrets::{SecretProvider, SecretProvidersPoolBuilder};
542///
543/// let pool = SecretProvidersPoolBuilder::new()
544/// .add_provider("files", SecretProvider::files())
545/// .add_default_provider("env", SecretProvider::env_var())
546/// .build()
547/// .unwrap();
548/// ```
549pub struct SecretProvidersPoolBuilder {
550 providers: HashMap<String, SecretProvider>,
551 default_provider: Option<String>,
552}
553
554impl SecretProvidersPoolBuilder {
555 /// Create a new, empty secret providers pool builder.
556 ///
557 /// # Example
558 ///
559 /// ```no_run
560 /// use regent_sdk::secrets::SecretProvidersPoolBuilder;
561 ///
562 /// let builder = SecretProvidersPoolBuilder::new();
563 /// ```
564 pub fn new() -> Self {
565 Self {
566 providers: HashMap::new(),
567 default_provider: None,
568 }
569 }
570
571 /// Add a secret provider to the pool.
572 ///
573 /// # Arguments
574 ///
575 /// * `name` - Unique identifier for this provider
576 /// * `provider` - The secret provider instance
577 ///
578 /// # Returns
579 ///
580 /// The builder, for method chaining.
581 ///
582 /// # Example
583 ///
584 /// ```no_run
585 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPoolBuilder};
586 ///
587 /// let builder = SecretProvidersPoolBuilder::new()
588 /// .add_provider("files", SecretProvider::files());
589 /// ```
590 pub fn add_provider(mut self, name: &str, provider: SecretProvider) -> Self {
591 if let Some(_old_secret_provider) = self.providers.insert(name.to_string(), provider) {
592 warn!(
593 "You just overrided a secret provider in the pool, also identified by the name {}",
594 name
595 );
596 }
597 self
598 }
599
600 /// Add a secret provider and set it as the default.
601 ///
602 /// This is a convenience method that combines `add_provider` and `set_default`.
603 ///
604 /// # Arguments
605 ///
606 /// * `name` - Unique identifier for this provider
607 /// * `provider` - The secret provider instance
608 ///
609 /// # Returns
610 ///
611 /// The builder, for method chaining.
612 ///
613 /// # Example
614 ///
615 /// ```no_run
616 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPoolBuilder};
617 ///
618 /// let builder = SecretProvidersPoolBuilder::new()
619 /// .add_default_provider("files", SecretProvider::files());
620 /// ```
621 pub fn add_default_provider(mut self, name: &str, provider: SecretProvider) -> Self {
622 if let Some(_old_secret_provider) = self.providers.insert(name.to_string(), provider) {
623 warn!(
624 "You just overrided a secret provider in the pool, also identified by the name {}",
625 name
626 );
627 }
628 self.default_provider = Some(name.to_string());
629 self
630 }
631
632 /// Set the default provider by name.
633 ///
634 /// The provider must have been previously added with `add_provider`.
635 ///
636 /// # Arguments
637 ///
638 /// * `name` - Name of the provider to set as default
639 ///
640 /// # Returns
641 ///
642 /// The builder, for method chaining.
643 ///
644 /// # Example
645 ///
646 /// ```no_run
647 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPoolBuilder};
648 ///
649 /// let builder = SecretProvidersPoolBuilder::new()
650 /// .add_provider("files", SecretProvider::files())
651 /// .add_provider("env", SecretProvider::env_var())
652 /// .set_default("files".to_string());
653 /// ```
654 pub fn set_default(mut self, name: String) -> Self {
655 self.default_provider = Some(name);
656 self
657 }
658
659 /// Build the secret providers pool.
660 ///
661 /// This consumes the builder and returns a new [`SecretProvidersPool`] if validation passes.
662 ///
663 /// # Returns
664 ///
665 /// - `Ok(SecretProvidersPool)` if the pool was successfully built
666 /// - `Err(RegentError)` if validation failed (e.g., no default provider set, or default provider not found)
667 ///
668 /// # Example
669 ///
670 /// ```no_run
671 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPoolBuilder};
672 ///
673 /// let pool = SecretProvidersPoolBuilder::new()
674 /// .add_default_provider("files", SecretProvider::files())
675 /// .build()
676 /// .unwrap();
677 /// ```
678 pub fn build(self) -> Result<SecretProvidersPool, RegentError> {
679 match self.default_provider {
680 Some(default_provider_name) => match self.providers.get(&default_provider_name) {
681 Some(_secrets_provider) => Ok(SecretProvidersPool {
682 providers: self.providers,
683 default_provider: default_provider_name,
684 secret_cache: None,
685 }),
686 None => {
687 error!(
688 "Default secrets provider ({}) is not set",
689 default_provider_name
690 );
691 return Err(RegentError::SecretsIssue(format!(
692 "Default secrets provider ({}) is not set",
693 default_provider_name
694 )));
695 }
696 },
697 None => {
698 error!("No default secrets provider set");
699 return Err(RegentError::SecretsIssue(
700 "No default secrets provider set".to_string(),
701 ));
702 }
703 }
704 }
705}
706
707/// A pool of multiple secret providers with a unified interface.
708///
709/// This struct manages multiple [`SecretProvider`] instances and provides a single
710/// interface for retrieving secrets. When a secret is requested, the pool uses
711/// either the provider specified in the secret reference or the default provider.
712///
713/// # Example
714///
715/// ```no_run
716/// use regent_sdk::secrets::{SecretProvider, SecretProvidersPool, SecretReference};
717///
718/// // Create a pool with a single provider
719/// let pool = SecretProvidersPool::from("files", SecretProvider::files());
720///
721/// // Retrieve a secret
722/// let secret_ref = SecretReference::from("/path/to/secret", None);
723/// let secret = pool.get_secret_raw(&secret_ref).await.unwrap();
724/// ```
725#[derive(Clone)]
726pub struct SecretProvidersPool {
727 /// Map of provider names to provider instances.
728 providers: HashMap<String, SecretProvider>,
729 /// Name of the default provider to use when none is specified.
730 default_provider: String,
731 /// Optional cache for storing resolved secrets to ensure idempotency.
732 /// When `Some`, secrets are cached after first retrieval and subsequent
733 /// requests for the same secret will return the cached value.
734 secret_cache: Option<SecretCache>,
735}
736
737impl SecretProvidersPool {
738 /// Create a new secret providers pool with a single provider.
739 ///
740 /// This is a convenience constructor for pools with only one provider,
741 /// which will automatically be set as the default.
742 ///
743 /// # Arguments
744 ///
745 /// * `name` - Name for the provider
746 /// * `secret_provider` - The secret provider instance
747 ///
748 /// # Returns
749 ///
750 /// A new [`SecretProvidersPool`] with the specified provider as the default.
751 ///
752 /// # Example
753 ///
754 /// ```no_run
755 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPool};
756 ///
757 /// let pool = SecretProvidersPool::from("files", SecretProvider::files());
758 /// ```
759 pub fn from(name: &str, secret_provider: SecretProvider) -> Self {
760 let mut providers: HashMap<String, SecretProvider> = HashMap::new();
761 providers.insert(name.to_string(), secret_provider);
762 Self {
763 providers,
764 default_provider: name.to_string(),
765 secret_cache: None,
766 }
767 }
768
769 /// Enable secret caching for this pool.
770 ///
771 /// When caching is enabled, secrets are stored after first retrieval and
772 /// subsequent requests for the same secret will return the cached value.
773 /// This ensures idempotency by preventing secret rotation from affecting
774 /// compliance operations that are in progress.
775 ///
776 /// # Example
777 ///
778 /// ```no_run
779 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPool};
780 ///
781 /// let mut pool = SecretProvidersPool::from("files", SecretProvider::files());
782 /// pool.enable_caching();
783 /// ```
784 pub fn enable_caching(&mut self) {
785 self.secret_cache = Some(SecretCache::new());
786 }
787
788 /// Disable secret caching for this pool.
789 ///
790 /// # Example
791 ///
792 /// ```no_run
793 /// use regent_sdk::secrets::{SecretProvider, SecretProvidersPool};
794 ///
795 /// let mut pool = SecretProvidersPool::from("files", SecretProvider::files());
796 /// pool.disable_caching();
797 /// ```
798 pub fn disable_caching(&mut self) {
799 self.secret_cache = None;
800 }
801
802 /// Check if caching is enabled for this pool.
803 ///
804 /// # Returns
805 /// * `true` if caching is enabled
806 /// * `false` otherwise
807 pub fn is_caching_enabled(&self) -> bool {
808 self.secret_cache.is_some()
809 }
810
811 /// Create a cache key from a secret reference.
812 ///
813 /// This creates a unique key that combines the provider name and secret reference
814 /// to ensure proper caching across different providers.
815 pub fn create_cache_key(&self, secret_reference: &SecretReference) -> String {
816 let provider = match secret_reference.provider() {
817 Some(user_defined_provider) => user_defined_provider.clone(),
818 None => self.default_provider.clone(),
819 };
820 format!("{}:{}", provider, secret_reference.sec_ref())
821 }
822
823 /// Retrieve a secret as a specific type.
824 ///
825 /// # Type Parameters
826 ///
827 /// * `T` - The type to deserialize the secret into (must implement `DeserializeOwned`)
828 ///
829 /// # Arguments
830 ///
831 /// * `secret_reference` - Reference to the secret to retrieve
832 ///
833 /// # Returns
834 ///
835 /// The secret wrapped in a [`Secret`] container, or a [`RegentError`] if retrieval failed.
836 ///
837 /// # Example
838 ///
839 /// ```no_run
840 /// use regent_sdk::secrets::{SecretProvidersPool, SecretReference};
841 /// use serde::Deserialize;
842 ///
843 /// #[derive(Deserialize)]
844 /// struct ApiCredentials {
845 /// key: String,
846 /// secret: String,
847 /// }
848 ///
849 /// let pool = SecretProvidersPool::from("files", SecretProvider::files());
850 /// let secret_ref = SecretReference::from("api_creds.json", None);
851 /// let creds: Secret<ApiCredentials> = pool.get_secret_typed(&secret_ref).await.unwrap();
852 /// ```
853 pub async fn get_secret_typed<T: DeserializeOwned>(
854 &self,
855 secret_reference: &SecretReference,
856 ) -> Result<Secret<T>, RegentError> {
857 let provider = match secret_reference.provider() {
858 Some(user_defined_provider) => user_defined_provider,
859 None => &self.default_provider,
860 };
861
862 match self.providers.get(provider) {
863 Some(secret_provider) => {
864 secret_provider
865 .get_secret_typed(secret_reference.sec_ref())
866 .await
867 }
868 None => {
869 error!(
870 "Default secrets provider {} not found. Was the SecretProvidersPoolBuilder type used to build this SecretProvidersPool ?",
871 self.default_provider
872 );
873 return Err(RegentError::SecretsIssue(format!(
874 "Default secrets provider {} not found. Was the SecretProvidersPoolBuilder type used to build this SecretProvidersPool ?",
875 self.default_provider
876 )));
877 }
878 }
879 }
880
881 /// Retrieve a secret as a raw string.
882 ///
883 /// # Arguments
884 ///
885 /// * `secret_reference` - Reference to the secret to retrieve
886 ///
887 /// # Returns
888 ///
889 /// The secret as a string wrapped in a [`Secret`] container, or a [`RegentError`] if retrieval failed.
890 ///
891 /// # Example
892 ///
893 /// ```no_run
894 /// use regent_sdk::secrets::{SecretProvidersPool, SecretReference};
895 ///
896 /// let pool = SecretProvidersPool::from("files", SecretProvider::files());
897 /// let secret_ref = SecretReference::from("/path/to/password.txt", None);
898 /// let password = pool.get_secret_raw(&secret_ref).await.unwrap();
899 /// let password_str: String = password.inner();
900 /// ```
901 pub async fn get_secret_raw(
902 &self,
903 secret_reference: &SecretReference,
904 ) -> Result<Secret<String>, RegentError> {
905 let cache_key = self.create_cache_key(secret_reference);
906
907 // Check if the secret is in cache
908 if let Some(cache) = &self.secret_cache {
909 if let Some(cached_value) = cache.get(&cache_key).await {
910 debug!("Secret cache hit for: {}", cache_key);
911 return Ok(Secret::from(secret_reference.sec_ref(), cached_value));
912 }
913 }
914
915 // Secret not in cache, fetch from provider
916 let provider = match secret_reference.provider() {
917 Some(user_defined_provider) => user_defined_provider,
918 None => &self.default_provider,
919 };
920
921 let secret_result = match self.providers.get(provider) {
922 Some(secret_provider) => {
923 secret_provider
924 .get_secret_raw(secret_reference.sec_ref())
925 .await
926 }
927 None => {
928 error!(
929 "Default secrets provider {} not found. Was the SecretProvidersPoolBuilder type used to build this SecretProvidersPool ?",
930 self.default_provider
931 );
932 return Err(RegentError::SecretsIssue(format!(
933 "Default secrets provider {} not found. Was the SecretProvidersPoolBuilder type used to build this SecretProvidersPool ?",
934 self.default_provider
935 )));
936 }
937 };
938
939 // Store in cache if caching is enabled
940 if let Some(cache) = &self.secret_cache {
941 if let Ok(secret) = &secret_result {
942 let secret_value = secret.clone().inner();
943 cache.insert(cache_key.clone(), secret_value).await;
944 debug!("Secret cached for: {}", cache_key);
945 }
946 }
947
948 secret_result
949 }
950}