use crate::secret_client::{RequestMode, SecretClient, SecretError, register_secret_for_redaction};
use secrecy::SecretString;
use std::sync::Arc;
use tokio::task::JoinHandle;
pub type ModuleSecretError = SecretError;
pub async fn get_module_secret_client(
orchestrator_api: &str,
module_id: &str,
) -> Result<Arc<SecretClient>, ModuleSecretError> {
let client = SecretClient::new(orchestrator_api, module_id).await?;
Ok(client)
}
pub async fn get_secret(
client: &Arc<SecretClient>,
key: &str,
) -> Result<SecretString, ModuleSecretError> {
let secret = client.get_secret(key, RequestMode::CacheThenRemote).await?;
register_secret_for_redaction(&secret);
Ok(secret)
}
pub async fn get_secrets(
client: &Arc<SecretClient>,
keys: &[&str],
) -> Result<Vec<SecretString>, ModuleSecretError> {
let mut secrets = Vec::with_capacity(keys.len());
for &key in keys {
let secret = get_secret(client, key).await?;
secrets.push(secret);
}
Ok(secrets)
}
pub fn subscribe_secret_rotations<F>(
client: Arc<SecretClient>,
keys: Vec<String>,
on_rotate: F,
) -> JoinHandle<()>
where
F: Fn(String, SecretString) + Send + 'static,
{
let mut rx = client.subscribe_to_rotations();
tokio::spawn(async move {
while let Ok(rotated_keys) = rx.recv().await {
for key in &rotated_keys {
if keys.contains(key) {
if let Ok(new_secret) = client.get_secret(key, RequestMode::ForceRemote).await {
register_secret_for_redaction(&new_secret);
on_rotate(key.clone(), new_secret.clone());
}
}
}
}
})
}
pub mod typed_secret;
#[cfg(test)]
mod tests {
use super::*;
use crate::secret_client::client::SecretClient;
use secrecy::ExposeSecret;
use std::sync::{Arc, Mutex};
#[tokio::test]
async fn get_secret_registers_for_redaction() {
let client = Arc::new(SecretClient::new_dummy());
client
.insert_test_secret("my_api_key", "secret-value-123")
.await;
let secret = get_secret(&client, "my_api_key").await.unwrap();
assert_eq!(secret.expose_secret(), "secret-value-123");
}
#[tokio::test]
async fn get_secrets_gets_multiple_secrets() {
let client = Arc::new(SecretClient::new_dummy());
client.insert_test_secret("key1", "value1").await;
client.insert_test_secret("key2", "value2").await;
let secrets = get_secrets(&client, &["key1", "key2"]).await.unwrap();
assert_eq!(secrets.len(), 2);
assert_eq!(secrets[0].expose_secret(), "value1");
assert_eq!(secrets[1].expose_secret(), "value2");
}
#[tokio::test]
async fn subscribe_secret_rotations_handles_rotations() {
let client = Arc::new(SecretClient::new_dummy());
client
.insert_test_secret("tracked_key", "initial-value")
.await;
let rotated_value = Arc::new(Mutex::new(String::from("not-rotated")));
let rotated_value_clone = rotated_value.clone();
let _handle = subscribe_secret_rotations(
client.clone(),
vec!["tracked_key".to_string()],
move |key, new_val| {
if key == "tracked_key" {
let mut guard = rotated_value_clone.lock().unwrap();
*guard = new_val.expose_secret().to_string();
}
},
);
client
.send_test_rotation(vec!["tracked_key".to_string()])
.unwrap();
client
.insert_test_secret("tracked_key", "rotated-value")
.await;
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
}