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")
Infisical Secrets Manager
AZKees supports Infisical as an alternative secrets backend, including self-hosted instances.
Installation
pip install azkees[infisical]
Configuration
Add an Infisical section to your api_keys.ini:
[infisical-production]
infisical_url = http://127.0.0.1:8080 # self-hosted, or https://app.infisical.com
infisical_client_id = your-machine-identity-client-id
infisical_client_secret = your-machine-identity-client-secret
infisical_project_id = your-project-id
infisical_environment = prod
infisical_secret_path = /
Create a Machine Identity in your Infisical project (Settings → Machine Identities) and grant it access to your project to obtain the client_id and client_secret.
Usage
from azkees import InfisicalVaultHandler
vault = InfisicalVaultHandler(
section="infisical-production",
keys_config_path="/app/config/api_keys.ini"
)
# Get a secret
api_key = vault.get_secret("MY_API_KEY")
# Upsert a secret (create or update)
vault.set_secret("MY_API_KEY", "new-value")
# Get multiple secrets concurrently
secrets = vault.get_multiple_secrets(["DB_HOST", "DB_PASS", "API_KEY"])
# List all secrets
names = vault.list_secrets()
# Delete a secret (always permanent — Infisical has no soft-delete)
vault.delete_secret("OLD_SECRET")
Note: Unlike Azure Key Vault, Infisical does not support soft-delete or purge.
delete_secret()is always immediate and permanent. Thepurge=parameter is accepted for interface compatibility but is ignored.
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
Release files for azkees 7.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| azkees-7.0.2.tar.gz | 22.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| azkees-7.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 47.0 kB
Release files / azkees-7.0.2.tar.gz
| Download URL | azkees-7.0.2.tar.gz |
|---|---|
| Size | 22.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b8d80d6d525bd189bb52acbc664392092f17b36ad0234eaca008564299ea249
|
|
BLAKE2b-256 checksum How to use checksums |
c95a729ac2ee2f1b2814ad603864b0ab6ecf2afe3bf5487aa6401e33652d9db2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.4.1 CPython/3.12.13 Linux/6.17.0-1022-azure
|
Release files / azkees-7.0.2-py3-none-any.whl
| Download URL | azkees-7.0.2-py3-none-any.whl |
|---|---|
| Size | 25.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
769a596b6416e8b87141ffd76547b57c7465c9b89e411cf3e400e1bbe7cea15e
|
|
BLAKE2b-256 checksum How to use checksums |
c687ccdeb9e931f8f07b4836e718fd92b13091f6fe814edc0ddf4baebd59559b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.4.1 CPython/3.12.13 Linux/6.17.0-1022-azure
|