use crate::session::SessionBackend;
pub(crate) struct AzureBackend {
pub(crate) vault_url: String,
pub(crate) secret_prefix: String,
}
impl AzureBackend {
fn secret_name(&self, key: &str) -> String {
format!("{}-{}", self.secret_prefix, key)
}
}
fn block_on_isolated<F, T>(fut: F) -> T
where
F: std::future::Future<Output = T> + Send + 'static,
T: Send + 'static,
{
std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to build isolated tokio runtime for Azure SDK call");
rt.block_on(fut)
})
.join()
.expect("isolated runtime thread panicked")
}
impl SessionBackend for AzureBackend {
fn load(&self, key: &str) -> Option<String> {
let vault_url = self.vault_url.clone();
let secret_name = self.secret_name(key);
block_on_isolated(async move {
use azure_identity::DeveloperToolsCredential;
use azure_security_keyvault_secrets::SecretClient;
let credential = match DeveloperToolsCredential::new(None) {
Ok(c) => c,
Err(e) => {
tracing::warn!("Azure credential creation failed: {e}");
return None;
}
};
let client = match SecretClient::new(&vault_url, credential, None) {
Ok(c) => c,
Err(e) => {
tracing::warn!("Azure Key Vault client creation failed: {e}");
return None;
}
};
match client.get_secret(&secret_name, None).await {
Ok(response) => match response.into_model() {
Ok(model) => model.value,
Err(e) => {
tracing::warn!("Azure Key Vault response parsing failed: {e}");
None
}
},
Err(e) => {
tracing::debug!("Azure Key Vault secret '{secret_name}' not found: {e}");
None
}
}
})
}
fn save(&self, key: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> {
let vault_url = self.vault_url.clone();
let secret_name = self.secret_name(key);
let value = value.to_string();
let result: Result<(), Box<dyn std::error::Error + Send + Sync>> =
block_on_isolated(async move {
use azure_identity::DeveloperToolsCredential;
use azure_security_keyvault_secrets::SecretClient;
let credential = DeveloperToolsCredential::new(None)
.map_err(|e| format!("Azure credential error: {e}"))?;
let client = SecretClient::new(&vault_url, credential, None)
.map_err(|e| format!("Azure client error: {e}"))?;
let params = azure_security_keyvault_secrets::models::SetSecretParameters {
value: Some(value),
..Default::default()
};
let body = params
.try_into()
.map_err(|e| format!("Azure request error: {e}"))?;
client
.set_secret(&secret_name, body, None)
.await
.map_err(|e| format!("failed to store session in Azure Key Vault: {e}"))?;
Ok(())
});
result.map_err(|e| -> Box<dyn std::error::Error> { e.to_string().into() })
}
fn clear(&self, key: &str) {
let vault_url = self.vault_url.clone();
let secret_name = self.secret_name(key);
block_on_isolated(async move {
use azure_identity::DeveloperToolsCredential;
use azure_security_keyvault_secrets::SecretClient;
let Ok(credential) = DeveloperToolsCredential::new(None) else {
return;
};
let Ok(client) = SecretClient::new(&vault_url, credential, None) else {
return;
};
let _ = client.delete_secret(&secret_name, None).await;
})
}
}