Skip to content

Commit c63ffda

Browse files
authored
feat: Add Azure Key Vault Variable Provider (#2472)
* feat: Add Azure Key Vault Variable Provider Signed-off-by: Thorsten Hans <[email protected]> * chore: Applied feedback and refactored Azure Key Vault variable provider impl Signed-off-by: Thorsten Hans <[email protected]> * chore: update cargo locks Signed-off-by: Thorsten Hans <[email protected]> * chore: Remove FromStr and From<String> implementations for AzureAuthorityHost Signed-off-by: Thorsten Hans <[email protected]> --------- Signed-off-by: Thorsten Hans <[email protected]>
1 parent 5eef6b7 commit c63ffda

File tree

6 files changed

+677
-19
lines changed

6 files changed

+677
-19
lines changed

Cargo.lock

Lines changed: 132 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/trigger/src/runtime_config/variables_provider.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
use std::path::PathBuf;
22

33
use serde::Deserialize;
4-
use spin_variables::provider::{env::EnvProvider, vault::VaultProvider};
4+
use spin_variables::provider::{
5+
azure_key_vault::{AzureAuthorityHost, AzureKeyVaultProvider},
6+
env::EnvProvider,
7+
vault::VaultProvider,
8+
};
59

610
use super::RuntimeConfig;
711

@@ -13,6 +17,7 @@ pub type VariablesProvider = Box<dyn spin_expressions::Provider>;
1317
pub enum VariablesProviderOpts {
1418
Env(EnvVariablesProviderOpts),
1519
Vault(VaultVariablesProviderOpts),
20+
AzureKeyVault(AzureKeyVaultVariablesProviderOpts),
1621
}
1722

1823
impl VariablesProviderOpts {
@@ -26,6 +31,7 @@ impl VariablesProviderOpts {
2631
match self {
2732
Self::Env(opts) => opts.build_provider(),
2833
Self::Vault(opts) => opts.build_provider(),
34+
Self::AzureKeyVault(opts) => opts.build_provider(),
2935
}
3036
}
3137
}
@@ -82,3 +88,26 @@ impl VaultVariablesProviderOpts {
8288
))
8389
}
8490
}
91+
92+
#[derive(Debug, Default, Deserialize)]
93+
#[serde(deny_unknown_fields)]
94+
pub struct AzureKeyVaultVariablesProviderOpts {
95+
pub client_id: String,
96+
pub client_secret: String,
97+
pub tenant_id: String,
98+
pub vault_url: String,
99+
#[serde(default)]
100+
pub authority_host: AzureAuthorityHost,
101+
}
102+
103+
impl AzureKeyVaultVariablesProviderOpts {
104+
pub fn build_provider(&self) -> VariablesProvider {
105+
Box::new(AzureKeyVaultProvider::new(
106+
&self.client_id,
107+
&self.client_secret,
108+
&self.tenant_id,
109+
&self.vault_url,
110+
self.authority_host,
111+
))
112+
}
113+
}

crates/variables/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ tokio = { version = "1", features = ["rt-multi-thread"] }
1818
vaultrs = "0.6.2"
1919
serde = "1.0.188"
2020
tracing = { workspace = true }
21+
azure_security_keyvault = "0.20.0"
22+
azure_core = "0.20.0"
23+
azure_identity = "0.20.0"
2124

2225
[dev-dependencies]
2326
toml = "0.5"

crates/variables/src/provider.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pub mod azure_key_vault;
12
pub mod env;
23
pub mod vault;
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::sync::Arc;
2+
3+
use anyhow::{Context, Result};
4+
use async_trait::async_trait;
5+
use azure_core::Url;
6+
use azure_security_keyvault::SecretClient;
7+
use serde::Deserialize;
8+
use spin_expressions::{Key, Provider};
9+
use tracing::{instrument, Level};
10+
11+
#[derive(Debug)]
12+
pub struct AzureKeyVaultProvider {
13+
client_id: String,
14+
client_secret: String,
15+
tenant_id: String,
16+
vault_url: String,
17+
authority_host: AzureAuthorityHost,
18+
}
19+
20+
impl AzureKeyVaultProvider {
21+
pub fn new(
22+
client_id: impl Into<String>,
23+
client_secret: impl Into<String>,
24+
tenant_id: impl Into<String>,
25+
vault_url: impl Into<String>,
26+
authority_host: impl Into<AzureAuthorityHost>,
27+
) -> Self {
28+
Self {
29+
client_id: client_id.into(),
30+
client_secret: client_secret.into(),
31+
tenant_id: tenant_id.into(),
32+
vault_url: vault_url.into(),
33+
authority_host: authority_host.into(),
34+
}
35+
}
36+
}
37+
38+
#[async_trait]
39+
impl Provider for AzureKeyVaultProvider {
40+
#[instrument(name = "spin_variables.get_from_azure_key_vault", skip(self), err(level = Level::INFO), fields(otel.kind = "client"))]
41+
async fn get(&self, key: &Key) -> Result<Option<String>> {
42+
let http_client = azure_core::new_http_client();
43+
let credential = azure_identity::ClientSecretCredential::new(
44+
http_client,
45+
self.authority_host.into(),
46+
self.tenant_id.to_string(),
47+
self.client_id.to_string(),
48+
self.client_secret.to_string(),
49+
);
50+
51+
let secret_client = SecretClient::new(&self.vault_url, Arc::new(credential))?;
52+
let secret = secret_client
53+
.get(key.as_str())
54+
.await
55+
.context("Failed to read variable from Azure Key Vault")?;
56+
Ok(Some(secret.value))
57+
}
58+
}
59+
60+
#[derive(Debug, Copy, Clone, Deserialize)]
61+
pub enum AzureAuthorityHost {
62+
AzurePublicCloud,
63+
AzureChina,
64+
AzureGermany,
65+
AzureGovernment,
66+
}
67+
68+
impl Default for AzureAuthorityHost {
69+
fn default() -> Self {
70+
Self::AzurePublicCloud
71+
}
72+
}
73+
74+
impl From<AzureAuthorityHost> for Url {
75+
fn from(value: AzureAuthorityHost) -> Self {
76+
let url = match value {
77+
AzureAuthorityHost::AzureChina => "https://login.chinacloudapi.cn/",
78+
AzureAuthorityHost::AzureGovernment => "https://login.microsoftonline.us/",
79+
AzureAuthorityHost::AzureGermany => "https://login.microsoftonline.de/",
80+
AzureAuthorityHost::AzurePublicCloud => "https://login.microsoftonline.com/",
81+
};
82+
Url::parse(url).unwrap()
83+
}
84+
}

0 commit comments

Comments
 (0)