A secure and efficient Azure Key Vault client with advanced logging, concurrent operations, and Docker support
Project description
AZKees - Azure Key Vault Easy & Secure
AZKees is a Python package that provides a secure and efficient way to interact with Azure Key Vault secrets, with built-in logging support, concurrent operations, and Docker-friendly configuration.
Features
- 🔐 Secure secret management using Azure Key Vault
- ⚡ Concurrent batch operations for better performance
- 🎯 Singleton pattern with LRU caching for optimal efficiency
- 📝 Comprehensive logging with color-coded output
- 🐳 Docker volume support for secure configuration mounting
- 🔄 Secret lifecycle management (create, update, delete, purge, recover)
- 🛡️ URL masking for sensitive data in logs
- 🌐 Platform-independent configuration (Windows/Linux)
- 📊 Support for secret metadata (tags, expiration, etc.)
Installation
Production Use (Recommended)
pip install azkees
Using Poetry
poetry add azkees
Development (Optional)
For contributing or local development only:
# Clone the repository (requires access)
git clone https://github.com/bek42/azkees.git
cd azkees
# Install in editable mode
pip install -e .
# Or using Poetry
poetry install
Configuration
Option 1: Direct Path (Recommended for Docker)
Pass the configuration path directly when initializing the client:
from azkees import Az
az_client = Az(
config_section="production",
keys_config_path="/app/config/api_keys.ini"
)
Option 2: Environment Variables
-
Create an
.envfile:keys_config_linux = "/app/config/api_keys.ini" keys_config_windows = "C:/config/api_keys.ini" LOG_LEVEL = "INFO"
-
Use without explicit path:
from azkees import Az az_client = Az(config_section="production")
API Keys Configuration File
Create api_keys.ini with your Azure credentials:
[production]
azure_tenant_id = your-tenant-id
azure_client_id = your-client-id
azure_client_secret = your-client-secret
azure_vault_url = https://your-vault.vault.azure.net/
[development]
azure_tenant_id = dev-tenant-id
azure_client_id = dev-client-id
azure_client_secret = dev-client-secret
azure_vault_url = https://dev-vault.vault.azure.net/
Usage
Basic Usage
from azkees import Az
# Initialize with explicit config path (recommended for Docker)
az_client = Az(
config_section="production",
keys_config_path="/app/config/api_keys.ini"
)
# Retrieve a single secret
secret_dict = az_client.get_secrets(name="my-secret")
print(f"Secret value: {secret_dict['value']}")
# Retrieve multiple secrets concurrently (fast!)
secrets = az_client.get_multiple_secrets(["secret1", "secret2", "secret3"])
for name, value in secrets.items():
print(f"{name}: {value}")
# Set a secret with metadata
az_client.set_secrets(
name="my-secret",
value="my-secret-value",
tags={"environment": "production", "owner": "team-a"},
content_type="password"
)
# Mask sensitive URLs in logs
safe_url = Az.mask_sensitive_info("postgresql://user:password@host:5432/db?token=abc123")
print(safe_url) # postgresql://user:@host:5432/db?token=****
Advanced Usage with VaultHandler
from azkees import VaultHandler
# Initialize vault handler
vault = VaultHandler(
section="production",
keys_config_path="/app/config/api_keys.ini"
)
# Get a single secret
api_key = vault.get_secret("api-key")
# Get multiple secrets concurrently
db_secrets = vault.get_multiple_secrets([
"db-host",
"db-password",
"db-username"
])
# List all secrets in vault
all_secrets = vault.list_secrets()
print(f"Found {len(all_secrets)} secrets")
# Set a new secret
vault.set_secret("new-secret", "secret-value")
# Delete a secret (soft delete)
vault.delete_secret("old-secret")
# Delete and permanently purge
vault.delete_secret("temp-secret", purge=True)
# Permanently purge a deleted secret
vault.purge_secret("deleted-secret")
Singleton Pattern with Caching
from azkees import AzureVaultClient
# First initialization
client1 = AzureVaultClient(
config_section="production",
keys_config_path="/app/config/api_keys.ini"
)
# Second call returns the same instance (singleton)
client2 = AzureVaultClient("production", "/app/config/api_keys.ini")
assert client1 is client2 # True
# Get secret with LRU caching (subsequent calls use cache)
secret = client1.get_secret("cached-secret") # Fetches from Azure
secret = client1.get_secret("cached-secret") # Returns from cache
# Batch operation with concurrent execution
secrets = client1.get_secrets_batch([
"secret1", "secret2", "secret3", "secret4", "secret5"
])
# Check if vault is available
if client1.is_available:
print("Vault connection is active")
Docker Integration
Docker Volume Mount (Recommended)
This approach keeps sensitive credentials out of your .env file and allows secure configuration mounting:
docker-compose.yml:
services:
app:
image: your-app:latest
container_name: your-app
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- .env # No sensitive data here!
volumes:
# Mount api_keys.ini as read-only volume
- /host/path/to/api_keys.ini:/app/config/api_keys.ini:ro
- ./logs:/app/logs
networks:
- app-net
networks:
app-net:
driver: bridge
In your application:
from azkees import Az
# Use the mounted config file
az_client = Az(
config_section="production",
keys_config_path="/app/config/api_keys.ini"
)
Full Docker Example
Dockerfile:
FROM python:3.12-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Create config directory (will be mounted)
RUN mkdir -p /app/config /app/logs
# Run application
CMD ["python", "main.py"]
docker-compose.yml (Complete Example):
services:
backend:
image: your-registry/your-app:production
container_name: your-app-backend
restart: unless-stopped
ports:
- "8000:8000"
env_file:
- /path/to/backend-prod.env
volumes:
# Data persistence
- /host/data/app:/data
# Secure config mount (read-only)
- /secure/location/api_keys.ini:/app/config/api_keys.ini:ro
# Log directory
- /host/data/app/logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 120s
networks:
- app-net
networks:
app-net:
driver: bridge
Environment Variables for Docker
Your .env file can now be safely committed to version control:
# Application settings
APP_NAME=my-app
ENVIRONMENT=production
LOG_LEVEL=INFO
# No sensitive Azure credentials here!
# They're in the mounted api_keys.ini file
API Reference
Az Class
Main client for Azure Key Vault operations with logging.
Methods
get_secrets(name: str) -> dict: Retrieve a secret with loggingget_multiple_secrets(names: List[str]) -> dict[str, str]: Concurrent batch retrievalset_secrets(name, value, *, enabled, tags, content_type, not_before, expires_on, **kwargs) -> bool: Set secret with metadatamask_sensitive_info(url: str, sensitive_keys: List[str] | None) -> str: Static method to mask URLs
VaultHandler Class
Centralized vault operations manager.
Methods
get_secret(key: str) -> str: Get single secret valueget_multiple_secrets(keys: List[str]) -> dict[str, str]: Concurrent batch retrievalset_secret(key: str, value: str) -> bool: Set a secretlist_secrets() -> List[str]: List all vault secretsdelete_secret(key: str, purge: bool = False) -> bool: Delete (and optionally purge) secretpurge_secret(key: str) -> bool: Permanently delete a soft-deleted secretmask_sensitive_info(url: str, sensitive_keys: Optional[List[str]]) -> str: Static URL masking
AzureVaultClient Class
Singleton client with LRU caching.
Methods
get_secret(secret_name: str, secret_version: Optional[str] = None) -> str: Cached secret retrievalget_secrets_batch(secret_names: List[str], max_workers: int = 5) -> Dict[str, str]: Concurrent batch retrievalis_available(property): Check vault connection status
Performance Features
Concurrent Operations
Batch operations use ThreadPoolExecutor for parallel API calls:
# Instead of 5 sequential calls (~5 seconds)
secrets = {}
for name in ["s1", "s2", "s3", "s4", "s5"]:
secrets[name] = vault.get_secret(name)
# Use concurrent batch (~1 second)
secrets = vault.get_multiple_secrets(["s1", "s2", "s3", "s4", "s5"])
LRU Caching
The AzureVaultClient uses @lru_cache(maxsize=128) for frequently accessed secrets:
client = AzureVaultClient("prod", "/app/config/api_keys.ini")
# First call: fetches from Azure (~200ms)
secret = client.get_secret("api-key")
# Subsequent calls: returns from cache (~0.01ms)
secret = client.get_secret("api-key")
Security Best Practices
-
Never commit
api_keys.inito version control- Add to
.gitignore - Use Docker volume mounts for production
- Add to
-
Use read-only mounts in Docker
volumes: - /secure/api_keys.ini:/app/config/api_keys.ini:ro
-
Separate environments
[development] # Dev credentials [staging] # Staging credentials [production] # Production credentials
-
Use URL masking in logs
safe_url = Az.mask_sensitive_info(connection_string) log.info("Connecting to: %s", safe_url)
-
Implement proper Azure RBAC
- Grant minimum required permissions
- Use managed identities when possible
- Rotate secrets regularly
Troubleshooting
FileNotFoundError: Configuration file not found
# Make sure the path is correct
az_client = Az(
config_section="production",
keys_config_path="/app/config/api_keys.ini" # Check this path
)
ValueError: Configuration section not found
Check your api_keys.ini has the correct section:
[production] # This must match your config_section parameter
azure_tenant_id = ...
Secret not found errors
try:
secret = vault.get_secret("my-secret")
except KeyError as e:
log.error("Secret not found: %s", e)
Docker volume mount issues
# Verify the file exists on host
ls -la /host/path/to/api_keys.ini
# Check file permissions (should be readable)
chmod 644 /host/path/to/api_keys.ini
# Verify mount inside container
docker exec your-container ls -la /app/config/api_keys.ini
Development
Running Tests
# Install dev dependencies
poetry install --with dev
# Run tests
pytest tests/
# Run with coverage
pytest --cov=azkees tests/
Building for PyPI
# Build the package
poetry build
# Publish to PyPI
poetry publish
Changelog
See CHANGELOG.md for version history.
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Author
Bharani Nitturi - bek42
Support
- 📧 Email: bharani.nitturi@gmail.com
- 🐛 Issues: GitHub Issues
- 📚 Documentation: GitHub Wiki
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file azkees-6.0.1.tar.gz.
File metadata
- Download URL: azkees-6.0.1.tar.gz
- Upload date:
- Size: 30.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.12 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e00c2541de220115c38d7f95ce2f0f3de9070a66428d9954f5097d209915b984
|
|
| MD5 |
4c120d4318a654a8d74d745084891445
|
|
| BLAKE2b-256 |
a403e35767d1d41ce8ab24385cb46223b1b8baa91ae96b2175663a09a4496f45
|
File details
Details for the file azkees-6.0.1-py3-none-any.whl.
File metadata
- Download URL: azkees-6.0.1-py3-none-any.whl
- Upload date:
- Size: 31.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.12.12 Linux/6.11.0-1018-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba2838e0a431fb87042cef93016581f87784fc9c9d14543d3718ece4d90bfa4d
|
|
| MD5 |
997b74308e7f48be445cb3df6a15edea
|
|
| BLAKE2b-256 |
1eb87f722a20b5b98a3e28ce2d5b2db5852a0dec8d5bd1e34c43135ffc674907
|