Skip to content

Commit 2a27a3d

Browse files
CodingAnarchyclaude
andcommitted
feat: Implement Azure Key Vault master key retrieval for KeyManager
- Implemented real Azure Key Vault master key retrieval in load_master_key_from_azure() - Added load_from_azure_key_vault() method with Azure SDK integration - Uses DefaultAzureCredential for secure authentication (service principal, managed identity) - Automatic key size normalization via HMAC-based key derivation for keys shorter than 32 bytes - Graceful fallback to deterministic key generation when Azure Key Vault is unavailable - Support for azure://vault-name/keys/key-name configuration format - Comprehensive error handling with descriptive messages for authentication and network failures - Added comprehensive documentation with Azure Key Vault setup examples - Updated module-level documentation with Azure Key Vault integration examples - Updated CHANGELOG.md to document the new Azure Key Vault master key feature 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent d6effab commit 2a27a3d

File tree

2 files changed

+186
-6
lines changed

2 files changed

+186
-6
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- HMAC-based key derivation for deterministic padding when Azure keys are unavailable
1919
- Comprehensive error handling with descriptive messages for Azure authentication and key retrieval failures
2020

21+
- **🔐 Azure Key Vault Master Key Retrieval**
22+
- Implemented real Azure Key Vault master key retrieval for `KeyManager`
23+
- Master keys can now be loaded directly from Azure Key Vault using `KeySource::External("azure://vault-name/keys/key-name")`
24+
- Automatic key size normalization via HMAC-based key derivation for keys shorter than 32 bytes
25+
- Secure authentication using `DefaultAzureCredential` with support for service principal and managed identity
26+
- Comprehensive error handling with fallback to deterministic key generation when Azure Key Vault is unavailable
27+
- Module-level documentation with complete Azure Key Vault setup examples and credential configuration
28+
2129
### Changed
2230
- **🔐 Encryption Key Rotation Architecture Simplification**
2331
- **BREAKING CHANGE**: Removed `rotate_key_if_needed()` method from `EncryptionEngine`

src/encryption/key_manager.rs

Lines changed: 178 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
//! - Secure key generation and storage
55
//! - Key rotation and lifecycle management
66
//! - Master key encryption (Key Encryption Keys)
7-
//! - External key management service integration
7+
//! - External key management service integration (AWS KMS, Azure Key Vault, GCP KMS, HashiCorp Vault)
8+
//! - Azure Key Vault integration for master key retrieval with automatic credential resolution
89
//! - Audit trails and key usage tracking
910
//!
1011
//! # Security Considerations
@@ -92,6 +93,43 @@
9293
//! # }
9394
//! # }
9495
//! ```
96+
//!
97+
//! ## Azure Key Vault Master Key Integration
98+
//!
99+
//! ```rust,no_run
100+
//! # #[cfg(all(feature = "encryption", feature = "azure-kv"))]
101+
//! # {
102+
//! use hammerwork::encryption::{KeyManager, KeyManagerConfig, KeySource};
103+
//! use sqlx::postgres::PgPool;
104+
//! use std::env;
105+
//!
106+
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
107+
//! # let database_url = "postgres://user:pass@localhost/hammerwork";
108+
//! # let pool = sqlx::PgPool::connect(database_url).await?;
109+
//! // Set up Azure credentials via environment variables
110+
//! env::set_var("AZURE_CLIENT_ID", "your-client-id");
111+
//! env::set_var("AZURE_CLIENT_SECRET", "your-client-secret");
112+
//! env::set_var("AZURE_TENANT_ID", "your-tenant-id");
113+
//!
114+
//! // Configure key manager with Azure Key Vault for master key
115+
//! let config = KeyManagerConfig::new()
116+
//! .with_master_key_source(KeySource::External(
117+
//! "azure://my-vault.vault.azure.net/keys/master-key".to_string()
118+
//! ))
119+
//! .with_auto_rotation_enabled(true);
120+
//!
121+
//! let mut key_manager = KeyManager::new(config, pool).await?;
122+
//!
123+
//! // Master key is automatically loaded from Azure Key Vault
124+
//! // If Azure Key Vault is unavailable, falls back to deterministic generation
125+
//! let key_id = key_manager.generate_key("payment-key",
126+
//! hammerwork::encryption::EncryptionAlgorithm::AES256GCM).await?;
127+
//!
128+
//! println!("Generated key with Azure Key Vault master key: {}", key_id);
129+
//! # Ok(())
130+
//! # }
131+
//! # }
132+
//! ```
95133
96134
use super::{EncryptionAlgorithm, EncryptionError, KeySource};
97135
use chrono::{DateTime, Duration, Utc};
@@ -1756,6 +1794,38 @@ where
17561794
hash[0..32].to_vec()
17571795
}
17581796

1797+
/// Load master key from Azure Key Vault
1798+
///
1799+
/// This method attempts to retrieve the master key from Azure Key Vault using the
1800+
/// configured service URL and key name. If Azure Key Vault is not available or
1801+
/// the `azure-kv` feature is not enabled, it falls back to deterministic key generation.
1802+
///
1803+
/// # Arguments
1804+
///
1805+
/// * `service_config` - Azure Key Vault configuration string in format "azure://vault-name/path/key-name"
1806+
///
1807+
/// # Returns
1808+
///
1809+
/// A 32-byte master key suitable for AES-256 encryption
1810+
///
1811+
/// # Examples
1812+
///
1813+
/// ```rust,no_run
1814+
/// # #[cfg(feature = "encryption")]
1815+
/// # {
1816+
/// use hammerwork::encryption::key_manager::KeyManager;
1817+
///
1818+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1819+
/// // Load master key from Azure Key Vault
1820+
/// let master_key = KeyManager::<sqlx::Postgres>::load_master_key_from_azure(
1821+
/// "azure://my-vault.vault.azure.net/keys/master-key"
1822+
/// ).await;
1823+
///
1824+
/// assert_eq!(master_key.len(), 32); // AES-256 key size
1825+
/// # Ok(())
1826+
/// # }
1827+
/// # }
1828+
/// ```
17591829
#[cfg(feature = "encryption")]
17601830
async fn load_master_key_from_azure(service_config: &str) -> Vec<u8> {
17611831
// Parse Azure Key Vault configuration for master key
@@ -1778,12 +1848,25 @@ where
17781848
vault_url, key_name
17791849
);
17801850

1781-
// In a real implementation, this would:
1782-
// 1. Create Azure Key Vault client with authentication
1783-
// 2. Retrieve the master key from the vault
1784-
// 3. Decrypt and return the master key material
1851+
// Try to load from Azure Key Vault if the feature is enabled
1852+
#[cfg(feature = "azure-kv")]
1853+
{
1854+
use azure_identity::{DefaultAzureCredential, TokenCredentialOptions};
1855+
use azure_security_keyvault::KeyvaultClient;
1856+
1857+
match Self::load_from_azure_key_vault(&vault_url, key_name).await {
1858+
Ok(key_material) => {
1859+
info!("Successfully loaded master key from Azure Key Vault");
1860+
return key_material;
1861+
}
1862+
Err(e) => {
1863+
warn!("Failed to load master key from Azure Key Vault: {}", e);
1864+
info!("Falling back to deterministic key generation");
1865+
}
1866+
}
1867+
}
17851868

1786-
// For now, generate a deterministic key based on the configuration
1869+
// Fallback to deterministic key generation for development/testing
17871870
use sha2::{Digest, Sha256};
17881871
let mut hasher = Sha256::new();
17891872
hasher.update(b"azure-kv-master-key");
@@ -1793,6 +1876,95 @@ where
17931876
hash[0..32].to_vec()
17941877
}
17951878

1879+
/// Load key material directly from Azure Key Vault
1880+
///
1881+
/// This method uses the Azure SDK to authenticate and retrieve key material from
1882+
/// Azure Key Vault. It supports automatic credential resolution through
1883+
/// `DefaultAzureCredential` and handles key size normalization.
1884+
///
1885+
/// # Arguments
1886+
///
1887+
/// * `vault_url` - Full URL to the Azure Key Vault (e.g., "https://my-vault.vault.azure.net")
1888+
/// * `key_name` - Name of the key to retrieve from the vault
1889+
///
1890+
/// # Returns
1891+
///
1892+
/// A 32-byte key material suitable for AES-256 encryption, or an error message
1893+
///
1894+
/// # Security Features
1895+
///
1896+
/// - Uses `DefaultAzureCredential` for secure authentication
1897+
/// - Automatically handles key size normalization via HMAC-based key derivation
1898+
/// - Supports base64-encoded key material from Azure Key Vault
1899+
/// - Includes proper error handling for authentication and network issues
1900+
///
1901+
/// # Examples
1902+
///
1903+
/// ```rust,no_run
1904+
/// # #[cfg(all(feature = "encryption", feature = "azure-kv"))]
1905+
/// # {
1906+
/// use hammerwork::encryption::key_manager::KeyManager;
1907+
///
1908+
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1909+
/// // Load key from Azure Key Vault
1910+
/// let key_material = KeyManager::<sqlx::Postgres>::load_from_azure_key_vault(
1911+
/// "https://my-vault.vault.azure.net",
1912+
/// "master-key"
1913+
/// ).await?;
1914+
///
1915+
/// assert_eq!(key_material.len(), 32); // Always 32 bytes for AES-256
1916+
/// # Ok(())
1917+
/// # }
1918+
/// # }
1919+
/// ```
1920+
#[cfg(all(feature = "encryption", feature = "azure-kv"))]
1921+
async fn load_from_azure_key_vault(vault_url: &str, key_name: &str) -> Result<Vec<u8>, String> {
1922+
use azure_identity::{DefaultAzureCredential, TokenCredentialOptions};
1923+
use azure_security_keyvault::KeyvaultClient;
1924+
1925+
// Create Azure credentials
1926+
let credential = DefaultAzureCredential::create(TokenCredentialOptions::default())
1927+
.map_err(|e| format!("Failed to create Azure credentials: {}", e))?;
1928+
1929+
// Create Azure Key Vault client
1930+
let client = KeyvaultClient::new(vault_url, std::sync::Arc::new(credential))
1931+
.map_err(|e| format!("Failed to create Azure Key Vault client: {}", e))?;
1932+
1933+
// Retrieve the master key from Azure Key Vault
1934+
match client.key_client().get(key_name.to_string()).await {
1935+
Ok(key_response) => {
1936+
if let Some(key_material) = key_response.key.k {
1937+
// Decode the base64-encoded key material
1938+
let decoded_key = base64::engine::general_purpose::URL_SAFE_NO_PAD
1939+
.decode(key_material)
1940+
.map_err(|e| format!("Failed to decode key material: {}", e))?;
1941+
1942+
// Ensure the key is the correct size for AES-256 (32 bytes)
1943+
if decoded_key.len() >= 32 {
1944+
Ok(decoded_key[0..32].to_vec())
1945+
} else {
1946+
// If the key is too short, use it as input for HMAC-based key derivation
1947+
use hmac::{Hmac, Mac};
1948+
use sha2::Sha256;
1949+
1950+
let mut hmac = <Hmac<Sha256> as Mac>::new_from_slice(&decoded_key)
1951+
.map_err(|e| format!("Failed to create HMAC: {}", e))?;
1952+
hmac.update(b"azure-kv-master-key-derivation");
1953+
hmac.update(vault_url.as_bytes());
1954+
hmac.update(key_name.as_bytes());
1955+
let result = hmac.finalize();
1956+
Ok(result.into_bytes()[0..32].to_vec())
1957+
}
1958+
} else {
1959+
Err("Key material not found in Azure Key Vault response".to_string())
1960+
}
1961+
}
1962+
Err(e) => {
1963+
Err(format!("Failed to retrieve key from Azure Key Vault: {}", e))
1964+
}
1965+
}
1966+
}
1967+
17961968
/// Store master key securely in the database
17971969
async fn store_master_key_securely(
17981970
&self,

0 commit comments

Comments
 (0)