Secrets Management: Vault, AWS SM, Azure KV Compared
Compare secrets management solutions for enterprise applications. Covers HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, and implementation patterns for application secrets, API keys, and certificates.
Hardcoded secrets in source code remain the #1 cause of credential leaks. According to GitGuardian’s State of Secrets Sprawl report, over 10 million secrets are leaked in public Git repositories every year. Secrets management solutions centralize storage, automate rotation, provide audit trails, and enforce access policies. This guide compares the top three solutions and shows battle-tested implementation patterns, rotation strategies, and the anti-patterns that cause breaches.
What Counts as a Secret
Before comparing tools, be clear about what needs protection:
- Database credentials — Connection strings, usernames, passwords for PostgreSQL, MySQL, SQL Server, MongoDB
- API keys — Stripe, Twilio, SendGrid, AWS access keys, Azure service principal credentials
- TLS certificates — SSL/TLS certs for HTTPS, mTLS between services, client certificates
- Encryption keys — Keys used for data encryption at rest or in transit
- OAuth tokens — Client secrets for OAuth2 flows, refresh tokens, JWT signing keys
- SSH keys — Deployment keys, infrastructure access keys
- Environment-specific configuration — Connection strings, endpoints, feature flags that vary by environment
If any of these are in your source code, environment variables files checked into Git, or shared via Slack, you have a secrets management problem.
Solution Comparison
| Feature | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
|---|---|---|---|
| Hosting | Self-hosted or HCP (cloud) | AWS fully managed | Azure fully managed |
| Multi-cloud | ✅ Best for multi-cloud | AWS only | Azure only |
| Dynamic secrets | ✅ Database, cloud creds, PKI | ❌ Static only | ❌ Static only |
| Secret rotation | Built-in + custom policies | Built-in (Lambda-based) | Built-in (Azure Functions) |
| Encryption as a service | ✅ Transit engine (encrypt without seeing key) | ❌ Use KMS separately | ✅ Key management |
| PKI / certificates | ✅ Built-in Certificate Authority | Use ACM (separate service) | Certificate management |
| Authentication methods | 15+ (AppRole, K8s, LDAP, OIDC, AWS IAM) | AWS IAM only | Azure AD, managed identity |
| Audit logging | ✅ Detailed per-request | ✅ CloudTrail | ✅ Azure Monitor |
| HA/DR | Raft consensus or Consul | Managed (multi-AZ) | Managed (multi-region) |
| Cost | Open source free / HCP from $0.03/secret | $0.40/secret/month + $0.05/10K API calls | $0.03/10K operations |
| Best for | Multi-cloud, complex enterprise needs | AWS-native shops | Azure-native shops |
When to Choose Each
HashiCorp Vault: You are multi-cloud, need dynamic secrets (short-lived database credentials generated on demand), or need a built-in PKI. Vault is the most powerful but requires operational expertise — you must manage upgrades, backups, and high availability yourself (unless using HCP Vault).
AWS Secrets Manager: You are fully on AWS and want zero operational overhead. It integrates natively with RDS, Redshift, and DocumentDB for automated rotation. Pay per secret with no infrastructure to manage.
Azure Key Vault: You are fully on Azure and use managed identities. The tightest integration with Azure services (App Service, Functions, AKS). Cheapest per-operation pricing but limited to Azure ecosystem.
Implementation: AWS Secrets Manager
import boto3
import json
from functools import lru_cache
@lru_cache(maxsize=32)
def get_secret(secret_name: str, region: str = "us-east-1") -> dict:
"""Retrieve and cache a secret from AWS Secrets Manager."""
client = boto3.client('secretsmanager', region_name=region)
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
# Usage
db_creds = get_secret("prod/database/postgres")
connection = psycopg2.connect(
host=db_creds['host'],
user=db_creds['username'],
password=db_creds['password'],
dbname=db_creds['dbname']
)
Key considerations:
- Use
lru_cacheor a TTL cache to avoid hitting the API on every request (costs add up) - Tag secrets with
Environment,Service, andOwnerfor audit and cost allocation - Use resource-based policies to limit which IAM roles can access which secrets
Implementation: Azure Key Vault
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://garnet-prod.vault.azure.net/", credential=credential)
db_password = client.get_secret("prod-db-password").value
api_key = client.get_secret("stripe-api-key").value
Key considerations:
- Use managed identities instead of service principals where possible — no credentials to manage
- Enable soft delete and purge protection to prevent accidental or malicious deletion
- Use separate Key Vaults per environment (dev, staging, prod) — never share a vault
Implementation: HashiCorp Vault
import hvac
import os
client = hvac.Client(url='https://vault.internal:8200')
client.auth.approle.login(
role_id=os.environ['VAULT_ROLE_ID'],
secret_id=os.environ['VAULT_SECRET_ID']
)
# Read static secret
secret = client.secrets.kv.v2.read_secret_version(path='database/prod')
db_password = secret['data']['data']['password']
# Generate dynamic database credential (short-lived, auto-revoked)
creds = client.secrets.database.generate_credentials(name='readonly')
# Returns: {'username': 'v-approle-readonly-abc123', 'password': 'xyz789', 'ttl': 3600}
# Credential is automatically revoked after TTL expires
Key considerations:
- Dynamic secrets are Vault’s killer feature — each application instance gets unique, short-lived credentials that are automatically revoked. If compromised, blast radius is one instance for one hour.
- Use AppRole for machine-to-machine auth, OIDC for human access
- Set up Vault Agent as a sidecar for automatic token renewal and secret caching
Secret Rotation Patterns
Dual-Secret Rotation (Zero Downtime)
1. Create new secret version (v2) in secrets manager
2. Update credential at source (database password, API key)
3. Application reads new version automatically (or restart)
4. Verify v2 works in production
5. Mark v1 as deprecated (set expiration)
6. Remove v1 after grace period (24-48 hours)
Lambda-Based Rotation (AWS)
AWS Secrets Manager supports automatic rotation via Lambda functions. For RDS databases, AWS provides pre-built Lambda templates:
Rotation interval: 30-90 days
Lambda function: SecretsManagerRDSPostgreSQLRotationSingleUser
Steps: createSecret → setSecret → testSecret → finishSecret
Dynamic Secrets (Vault — Best Pattern)
With dynamic secrets, there is no rotation because credentials are short-lived:
1. Application requests credentials from Vault
2. Vault generates unique username/password on the database
3. Application uses credentials for its session (TTL: 1 hour)
4. Vault automatically revokes credentials when TTL expires
5. No shared credentials, no rotation needed, no credential sprawl
Anti-Patterns
| Anti-Pattern | Risk Level | Fix |
|---|---|---|
Secrets in .env files committed to Git | 🔴 Critical — exposed in repo history forever | Add .env to .gitignore, use secrets manager, run truffleHog to find existing leaks |
| Secrets in env vars (shared across processes) | 🟡 Medium — visible to all processes on host | Per-service secrets with vault injection or sidecar |
| Same secret in dev and prod | 🔴 Critical — dev compromise = prod compromise | Separate secrets per environment, different vaults |
| Never rotating secrets | 🟡 Medium — longer exposure window if leaked | Auto-rotate every 30-90 days, or use dynamic secrets |
| Sharing API keys via Slack/email | 🔴 Critical — logged in chat history, email servers | Use vault with short-lived tokens, share vault paths not values |
| One secret for all microservices | 🟡 Medium — compromise of one service compromises all | Per-service credentials with least privilege |
| No audit logging on secret access | 🟡 Medium — cannot detect unauthorized access | Enable audit logging, alert on anomalous access patterns |
| Long-lived service account keys | 🟡 Medium — keys that never expire accumulate risk | Use managed identities (Azure) or IAM roles (AWS) instead |
Rotation Frequency by Secret Type
| Secret Type | Rotation Interval | Automation | Risk if Not Rotated |
|---|---|---|---|
| Database passwords | 30 days | Lambda / Azure Function | Credential stuffing, lateral movement |
| API keys (third-party) | 90 days | Manual or webhook | Unauthorized API usage, cost exposure |
| TLS certificates | 365 days (auto-renew) | ACME / Let’s Encrypt | Service outage, security warnings |
| OAuth client secrets | 90 days | App registration automation | Token theft, impersonation |
| SSH deploy keys | 180 days | Ansible / Terraform | Unauthorized server access |
| JWT signing keys | 90 days | Rolling key rotation | Token forgery |
| Encryption keys (data at rest) | Annual | KMS automatic | Compliance violation |
Emergency Response: Leaked Secret
When a secret is confirmed leaked, follow this playbook:
- Revoke immediately — Disable the compromised credential at its source (database, API provider, cloud console)
- Generate replacement — Create a new secret in the vault and update all consumers
- Assess blast radius — Determine what the leaked secret had access to and what was accessed
- Audit logs — Review access logs for unauthorized activity during the exposure window
- Root cause — Identify how the leak occurred (code commit, log exposure, insider) and close the gap
- Post-mortem — Document the incident, timeline, and remediation for future reference
Checklist
- All secrets stored in a dedicated secrets manager (not code, not .env files, not config files)
- Rotation policy defined and automated (30 days for database passwords, 90 days for API keys, 365 days for certificates)
- Access policies enforced with least privilege per service (not blanket access)
- Audit logging enabled on all secret access with alerting for anomalous patterns
- Secrets scanner running in CI pipeline (truffleHog, git-secrets, Gitleaks)
- Separate secrets per environment (dev/staging/prod — never shared)
- Break-glass procedure documented for emergency secret access
- Secret inventory maintained (what secrets exist, who owns them, when they rotate)
- Incident response plan includes credential revocation steps
- Dynamic secrets evaluated for database access (if using Vault)
:::note[Source] This guide is derived from operational intelligence at Garnet Grid Consulting. For security consulting, visit garnetgrid.com. :::