Skip to main content

Python implementation of Vault Agent with client-side caching and automatic authentication

Project description

PyVault Agent

A Python implementation of Vault Agent providing client-side caching and automatic authentication for HashiCorp Vault. PyVault Agent brings the core functionality of HashiCorp's Vault Agent directly into your Python applications as a library, eliminating the need for external processes while providing the same benefits of credential caching and automatic token management.

Why PyVault Agent?

Traditional HashiCorp Vault Agent runs as a separate daemon process that manages authentication and caching. PyVault Agent provides the same functionality as an embedded Python library, offering several advantages:

  • Simplified deployment: No need to manage separate agent processes
  • Direct integration: Native Python API for your applications
  • Reduced complexity: Single process architecture eliminates IPC overhead
  • Fine-grained control: Programmatic access to cache management and configuration
  • Development friendly: Easy to use in development environments and testing

Key Problems Solved

  1. Credential Management: Automatically handles multiple authentication methods (AppRole, UserPass, Kubernetes) and token renewal
  2. Performance Optimization: Caches secrets to reduce Vault API calls and improve response times
  3. Connection Pool Issues: Manages database connection pools with expiring credentials
  4. Token Expiration: Seamlessly re-authenticates when tokens expire
  5. Thread Safety: Safe for use in multi-threaded applications

Features

  • Client-side caching: Reduces API calls to Vault with configurable TTL
  • Automatic re-authentication: Seamlessly handles token expiration
  • Multiple authentication methods: AppRole, UserPass, and Kubernetes service account authentication
  • Flexible auth mount points: Support for custom auth method mount paths
  • KV secrets engine support: Read-only secret access with version support (v1 & v2)
  • Database secrets engine: Manage dynamic database credentials
  • Identity delegation tokens: Cached token generation for workload identity delegation
  • Custom endpoint access: Generic POST method for any Vault endpoint
  • Connection pool management: Automatic credential refresh for database pools
  • Thread-safe caching: Safe for concurrent usage

Installation

pip install pyvault-agent

Quick Start

Authentication Methods

PyVault Agent supports three authentication methods. Choose the one that fits your deployment:

AppRole Authentication

import os
from vault_agent import VaultAgentClient

client = VaultAgentClient.with_approle(
    url=os.getenv("VAULT_ADDR"),
    role_id=os.getenv("VAULT_ROLE_ID"),
    secret_id=os.getenv("VAULT_SECRET_ID"),
    cache_ttl=300,  # Cache for 5 minutes
    max_cache_size=1000
)

UserPass Authentication

client = VaultAgentClient.with_userpass(
    url=os.getenv("VAULT_ADDR"),
    username=os.getenv("VAULT_USERNAME"),
    password=os.getenv("VAULT_PASSWORD"),
    cache_ttl=300,
    max_cache_size=1000
)

Kubernetes Authentication

# Auto-detects JWT from /var/run/secrets/kubernetes.io/serviceaccount/token
client = VaultAgentClient.with_kubernetes(
    url=os.getenv("VAULT_ADDR"),
    role="my-app-role",
    cache_ttl=300,
    max_cache_size=1000
)

# Or provide explicit JWT token
client = VaultAgentClient.with_kubernetes(
    url=os.getenv("VAULT_ADDR"),
    role="my-app-role",
    jwt=os.getenv("VAULT_K8S_JWT"),
    cache_ttl=300,
    max_cache_size=1000
)

Basic Usage

# Once authenticated (using any method above)

# Mount secrets engines dynamically
kv = client.mount(path="secret", type="kv")
database = client.mount(path="database", type="database")

# KV Secrets - Read application configuration
try:
    config = kv.read("myapp/config")
    api_key = config["api_key"]
    db_password = config["db_password"]
    print("Configuration loaded from Vault")
except Exception as e:
    print(f"Failed to load config: {e}")

# Database Credentials - Get dynamic database credentials
try:
    creds = database.read("myapp-db-role")
    print(f"Database user: {creds['username']}")

    # Create connection string
    conn_str = database.get_connection_string(
        role="myapp-db-role",
        template="postgresql://{username}:{password}@{host}:{port}/{database}",
        host="db.example.com",
        port=5432,
        database="myapp"
    )
except Exception as e:
    print(f"Failed to get database credentials: {e}")

# Cache Management - Monitor cache performance
stats = client.get_cache_stats()
print(f"Cache efficiency: {stats['hits']}/{stats['hits'] + stats['misses']} hits")

Advanced Usage

Configuration Options

All authentication methods support the same configuration options:

# AppRole with all options
client = VaultAgentClient.with_approle(
    url="https://vault.example.com",
    role_id="role-id",
    secret_id="secret-id",
    auth_mount_point="approle",  # Auth mount path (default: "approle")
    cache_ttl=300,               # Default cache TTL in seconds
    max_cache_size=1000,         # Maximum number of cached entries
    namespace="team-a",          # Vault namespace (Enterprise)
    verify=True                  # SSL certificate verification
)

# UserPass with custom mount point
client = VaultAgentClient.with_userpass(
    url="https://vault.example.com",
    username="myuser",
    password="mypassword",
    auth_mount_point="userpass-ldap",  # Custom auth mount path
    cache_ttl=300,
    max_cache_size=1000
)

# Kubernetes with custom mount point
client = VaultAgentClient.with_kubernetes(
    url="https://vault.example.com",
    role="my-app-role",
    auth_mount_point="kubernetes-prod",  # Custom auth mount path
    cache_ttl=300,
    max_cache_size=1000
)

Working with Different Secret Engines

# Mount engines with custom paths
kv = client.mount(path="secret", type="kv")
kv_custom = client.mount(path="kv-v2", type="kv")
database = client.mount(path="database", type="database")
db_custom = client.mount(path="db", type="database")

# KV v1 and v2 secrets
config = kv.read("app/config")

# KV v2 secrets with versioning
old_config = kv.read("app/config", version=1)

# Database dynamic credentials
creds = database.read("postgres-readonly")

# Database static credentials
static_creds = database.get_static_credentials("app-service-account")

# Skip cache for fresh credentials
fresh_creds = database.read("postgres-readonly", skip_cache=True)

Custom Vault Endpoints

For endpoints not covered by the built-in secrets engines, use the generic post() method:

# POST to any Vault endpoint
response = client.post("my-custom-engine/data/path", data={"key": "value"})

Identity Delegation Tokens

For workload identity delegation, use get_delegation_token() which provides automatic caching based on the JWT subject and Vault entity:

# Get delegation token with caching
response = client.get_delegation_token(
    role="customer-service",
    subject_token=subject_jwt,  # JWT token of the subject entity
    mount_point="identity-delegation",  # Optional, defaults to "identity-delegation"
)

# Access the delegated token
delegated_token = response["auth"]["client_token"]

# Force fresh token (skip cache)
response = client.get_delegation_token(
    role="customer-service",
    subject_token=subject_jwt,
    skip_cache=True,
)

The cache key is derived from the Vault entity ID and JWT subject claim, with TTL based on the token's lease duration.

Database Connection Pools

One of the key challenges with dynamic database credentials is managing connection pools when credentials expire. PyVault Agent provides a DatabaseConnectionManager that automatically handles credential refresh and pool recreation:

from vault_agent import VaultAgentClient
from vault_agent.database_pool import DatabaseConnectionManager
import psycopg2.pool

# Create client with any auth method
client = VaultAgentClient.with_approle(...)

# Mount database secrets engine
database = client.mount(path="database", type="database")

# Managed connection pool with auto-refresh
with DatabaseConnectionManager(
    database_secrets=database,
    role="postgres-role",
    pool_class=psycopg2.pool.SimpleConnectionPool,
    pool_kwargs={
        "minconn": 1,
        "maxconn": 10,
        "host": "db.example.com",
        "database": "myapp"
    },
    refresh_buffer=0.8,  # Refresh at 80% of credential TTL
    validation_query="SELECT 1",  # Query to validate connections
) as manager:

    # Get connections that are automatically managed
    with manager.get_connection() as conn:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM users")
        results = cursor.fetchall()

Background Refresh

For high-performance applications, use BackgroundRefreshManager to refresh credentials proactively:

from vault_agent.database_pool import BackgroundRefreshManager

# Mount database secrets engine
database = client.mount(path="database", type="database")

with BackgroundRefreshManager(
    database_secrets=database,
    role="postgres-role",
    pool_class=psycopg2.pool.ThreadedConnectionPool,
    pool_kwargs={"minconn": 2, "maxconn": 10, "host": "db.example.com"},
    check_interval=30,  # Check every 30 seconds
) as manager:
    # Credentials refresh in background, zero-latency for requests
    with manager.get_connection() as conn:
        # Your database operations
        pass

Error Handling and Resilience

from vault_agent.utils import SecretNotFoundError, AuthenticationError

# Mount secrets engine
kv = client.mount(path="secret", type="kv")

try:
    # Attempt to read secret with automatic retry
    config = kv.read("app/config")
except SecretNotFoundError:
    print("Secret not found - using defaults")
    config = {"api_key": "default"}
except AuthenticationError:
    print("Failed to authenticate with Vault")
    # Handle authentication failure
except Exception as e:
    print(f"Unexpected error: {e}")
    # Handle other errors

# Cache management
if client.get_cache_stats()["size"] > 500:
    client.clear_cache()  # Clear cache if getting too large

Integration with Existing Applications

Django Integration

# settings.py
import os
from vault_agent import VaultAgentClient

vault_client = VaultAgentClient.with_approle(
    url=os.getenv("VAULT_ADDR"),
    role_id=os.getenv("VAULT_ROLE_ID"),
    secret_id=os.getenv("VAULT_SECRET_ID"),
)

# Mount database secrets engine and get credentials
database = vault_client.mount(path="database", type="database")
db_creds = database.read("django-db-role")

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'myapp',
        'USER': db_creds['username'],
        'PASSWORD': db_creds['password'],
        'HOST': 'db.example.com',
        'PORT': '5432',
    }
}

Flask Integration

from flask import Flask
from vault_agent import VaultAgentClient
import os

app = Flask(__name__)

# Initialize Vault client (works in Kubernetes pod)
vault_client = VaultAgentClient.with_kubernetes(
    url=os.getenv("VAULT_ADDR"),
    role="flask-app-role",
)

@app.before_first_request
def setup():
    # Mount KV secrets engine and load configuration from Vault
    kv = vault_client.mount(path="secret", type="kv")
    config = kv.read("flask/config")
    app.config.update(config)

Development and Testing

Running Tests

# Install development dependencies
pip install -e ".[dev]"

# Run unit tests
pytest tests/

# Run functional tests (requires Vault dev server)
vault server -dev -dev-root-token-id="root"
VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=root pytest tests/functional/

# Run with coverage
pytest --cov=vault_agent tests/

Development Setup

# Clone the repository
git clone https://github.com/your-username/pyvault-agent.git
cd pyvault-agent

# Install in development mode
pip install -e ".[dev]"

# Run linting and formatting
black vault_agent/ tests/
ruff vault_agent/ tests/
mypy vault_agent/

Running Examples

# Set environment variables (for AppRole)
export VAULT_ADDR="http://127.0.0.1:8200"
export VAULT_ROLE_ID="your-role-id"
export VAULT_SECRET_ID="your-secret-id"

# Run basic example (AppRole)
python example.py

# Run multi-auth example (demonstrates all auth methods)
python example_multi_auth.py

# Run connection pool example
python example_pool.py

Performance Considerations

Cache Tuning

  • TTL: Set appropriate cache TTL based on secret sensitivity and change frequency
  • Size: Limit cache size to prevent memory growth in long-running applications
  • Hit Rate: Monitor cache hit rates to optimize TTL settings
# Monitor cache performance
stats = client.get_cache_stats()
hit_rate = stats['hits'] / (stats['hits'] + stats['misses'])
print(f"Cache hit rate: {hit_rate:.2%}")

# Adjust TTL based on performance needs
if hit_rate < 0.8:  # Less than 80% hit rate
    client.set_cache_ttl(600)  # Increase TTL

Connection Pool Best Practices

  • Buffer: Set refresh_buffer to 0.7-0.8 to refresh before expiry
  • Validation: Use connection validation to catch stale connections
  • Pool Size: Size pools appropriately for your application load
  • Monitoring: Monitor credential refresh frequency

Security Considerations

  1. Secure Storage: Store role_id and secret_id securely (environment variables, not in code)
  2. Network Security: Use HTTPS for Vault connections in production
  3. Credential Rotation: Regularly rotate AppRole credentials
  4. Audit Logging: Enable Vault audit logging to track secret access
  5. Least Privilege: Configure Vault policies with minimal required permissions

Troubleshooting

Common Issues

Authentication Failures

# Check Vault connectivity
try:
    client = VaultAgentClient.with_approle(
        url="https://vault.example.com",
        role_id="...",
        secret_id="..."
    )
except AuthenticationError as e:
    print(f"Auth failed: {e}")
    # Check credentials and auth method configuration

Cache Issues

# Clear cache if data seems stale
client.clear_cache()

# Check cache statistics
stats = client.get_cache_stats()
print(f"Cache size: {stats['size']}")

Connection Pool Problems

# Force credential refresh
manager.refresh_now()

# Check credential expiry
print(f"Credentials expire at: {manager.credentials_expire_at}")

Debug Logging

import logging
logging.basicConfig(level=logging.DEBUG)

# This will show cache hits/misses and authentication events
client = VaultAgentClient.with_approle(...)

Roadmap

Current Version (0.2.0)

  • Multiple authentication methods: AppRole, UserPass, Kubernetes
  • Flexible auth mount points for custom configurations
  • Automatic re-authentication on token expiry
  • KV secrets engine (v1 & v2) read-only access with caching
  • Database secrets engine read-only access with caching
  • Connection pool management
  • Thread-safe operations
  • Generic POST method for custom Vault endpoints
  • Identity delegation token support with caching

Planned Features

  • Token renewal: Proactive token refresh before expiry
  • Lease renewal: Automatic secret lease renewal
  • Additional auth methods: JWT, AWS IAM, Azure, GCP
  • More secret engines: PKI, Transit, SSH
  • Metrics integration: Prometheus metrics export
  • Configuration files: YAML/TOML configuration support
  • Async support: AsyncIO-compatible client

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

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

pyvault_agent-0.2.0.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyvault_agent-0.2.0-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file pyvault_agent-0.2.0.tar.gz.

File metadata

  • Download URL: pyvault_agent-0.2.0.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.13

File hashes

Hashes for pyvault_agent-0.2.0.tar.gz
Algorithm Hash digest
SHA256 92c04f6dce1f48c5dc81f7d585d512a9861f18406171f0635182cca920144ddc
MD5 cbc8b2382bb59f0502082bac29c870fc
BLAKE2b-256 652cef27aeeaac9e3faf090292273519ea57ba2d512802fbc2aa796516b4db18

See more details on using hashes here.

File details

Details for the file pyvault_agent-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pyvault_agent-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8b0544af82ada9d833b7abbaaf3850226c1b8b1e2c34c35952462cecca37ed89
MD5 227635b3bcd333b14aeef92581f41657
BLAKE2b-256 e04abf0c539222f561e8c4b61ee7505a708f71f923524d3b34e07f03400b0e5d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page