Skip to main content

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

  1. Create an .env file:

    keys_config_linux = "/app/config/api_keys.ini"
    keys_config_windows = "C:/config/api_keys.ini"
    LOG_LEVEL = "INFO"
    
  2. 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. The purge= 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 logging
  • get_multiple_secrets(names: List[str]) -> dict[str, str]: Concurrent batch retrieval
  • set_secrets(name, value, *, enabled, tags, content_type, not_before, expires_on, **kwargs) -> bool: Set secret with metadata
  • mask_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 value
  • get_multiple_secrets(keys: List[str]) -> dict[str, str]: Concurrent batch retrieval
  • set_secret(key: str, value: str) -> bool: Set a secret
  • list_secrets() -> List[str]: List all vault secrets
  • delete_secret(key: str, purge: bool = False) -> bool: Delete (and optionally purge) secret
  • purge_secret(key: str) -> bool: Permanently delete a soft-deleted secret
  • mask_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 retrieval
  • get_secrets_batch(secret_names: List[str], max_workers: int = 5) -> Dict[str, str]: Concurrent batch retrieval
  • is_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

  1. Never commit api_keys.ini to version control

    • Add to .gitignore
    • Use Docker volume mounts for production
  2. Use read-only mounts in Docker

    volumes:
      - /secure/api_keys.ini:/app/config/api_keys.ini:ro
    
  3. Separate environments

    [development]
    # Dev credentials
    
    [staging]
    # Staging credentials
    
    [production]
    # Production credentials
    
  4. Use URL masking in logs

    safe_url = Az.mask_sensitive_info(connection_string)
    log.info("Connecting to: %s", safe_url)
    
  5. 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

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. 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

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)

Source distribution for azkees 7.0.2
File Size Uploaded
azkees-7.0.2.tar.gz 22.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for azkees 7.0.2
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

7.0.2 This release

2 release files

7.0.1

2 release files

6.1.2

2 release files

6.1.1

2 release files

6.1.0

2 release files

6.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page