#[cfg(any(
feature = "aws-secrets",
feature = "gcp-secrets",
feature = "azure-secrets"
))]
const MAX_SECRETS: usize = 10_000;
#[cfg(feature = "aws-secrets")]
pub async fn list_aws_secrets(
region: Option<&str>,
) -> Result<Vec<String>, vti_common::error::AppError> {
use vti_common::error::AppError;
let mut config_loader = aws_config::from_env();
if let Some(region) = region {
config_loader = config_loader.region(aws_config::Region::new(region.to_owned()));
}
let sdk_config = config_loader.load().await;
let client = aws_sdk_secretsmanager::Client::new(&sdk_config);
let mut names: Vec<String> = Vec::new();
let mut next_token: Option<String> = None;
loop {
let mut req = client.list_secrets();
if let Some(token) = next_token.as_ref() {
req = req.next_token(token.clone());
}
let output = req
.send()
.await
.map_err(|e| AppError::SecretStore(format!("AWS list_secrets failed: {e}")))?;
names.extend(
output
.secret_list()
.iter()
.filter_map(|entry| entry.name().map(String::from)),
);
if names.len() >= MAX_SECRETS {
names.truncate(MAX_SECRETS);
break;
}
match output.next_token() {
Some(t) if !t.is_empty() => next_token = Some(t.to_string()),
_ => break,
}
}
Ok(names)
}
#[cfg(feature = "gcp-secrets")]
pub async fn list_gcp_secrets(project: &str) -> Result<Vec<String>, vti_common::error::AppError> {
use vti_common::error::AppError;
let client = google_cloud_secretmanager_v1::client::SecretManagerService::builder()
.build()
.await
.map_err(|e| AppError::SecretStore(format!("GCP Secret Manager client error: {e}")))?;
let prefix = format!("projects/{project}/secrets/");
let mut names: Vec<String> = Vec::new();
let mut page_token: Option<String> = None;
loop {
let mut req = client
.list_secrets()
.set_parent(format!("projects/{project}"));
if let Some(token) = page_token.as_ref() {
req = req.set_page_token(token.clone());
}
let response = req
.send()
.await
.map_err(|e| AppError::SecretStore(format!("GCP list_secrets failed: {e}")))?;
names.extend(
response
.secrets
.iter()
.map(|s| s.name.strip_prefix(&prefix).unwrap_or(&s.name).to_owned()),
);
if names.len() >= MAX_SECRETS {
names.truncate(MAX_SECRETS);
break;
}
if response.next_page_token.is_empty() {
break;
}
page_token = Some(response.next_page_token);
}
Ok(names)
}
#[cfg(feature = "azure-secrets")]
pub async fn list_azure_secrets(
vault_url: &str,
) -> Result<Vec<String>, vti_common::error::AppError> {
use azure_security_keyvault_secrets::{ResourceExt, SecretClient};
use futures_util::TryStreamExt;
use vti_common::error::AppError;
let credential = azure_identity::DeveloperToolsCredential::new(None)
.map_err(|e| AppError::SecretStore(format!("Azure credential error: {e}")))?;
let client = SecretClient::new(vault_url, credential, None)
.map_err(|e| AppError::SecretStore(format!("Azure Key Vault client error: {e}")))?;
let mut names: Vec<String> = Vec::new();
let mut pager = client
.list_secret_properties(None)
.map_err(|e| AppError::SecretStore(format!("Azure list_secret_properties failed: {e}")))?;
while let Some(secret) = pager
.try_next()
.await
.map_err(|e| AppError::SecretStore(format!("Azure list page error: {e}")))?
{
let id = secret
.resource_id()
.map_err(|e| AppError::SecretStore(format!("Azure resource_id parse error: {e}")))?;
names.push(id.name);
if names.len() >= MAX_SECRETS {
break;
}
}
Ok(names)
}