Skip to main content

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}