HashiCorp Vault vs AWS Secrets Manager vs Azure Key Vault
The Critical Importance of Secrets Management
Modern applications require secure storage and management of sensitive data including API keys, database credentials, certificates, and encryption keys. While traditional approaches involve environment variables or configuration files, dedicated secrets management platforms provide encryption, access control, audit logging, and automated rotation capabilities essential for enterprise security.
The choice between self-hosted solutions like HashiCorp Vault and cloud-native services like AWS Secrets Manager or Azure Key Vault affects security posture, operational complexity, and compliance requirements. Each platform offers distinct approaches to secrets lifecycle management, making selection critical for organizational security strategy.
Architecture and Security Models
Understanding the fundamental security architectures reveals each platform’s approach to protecting sensitive data:
Feature | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
---|---|---|---|
Deployment | Self-hosted/Cloud | AWS-managed | Azure-managed |
Encryption | AES-256-GCM | AWS KMS | Azure Key Vault HSM |
Access Control | Policy-based ACL | IAM + Resource policies | Azure RBAC + Access policies |
Audit Logging | Built-in audit devices | CloudTrail integration | Azure Monitor logs |
High Availability | Clustering/replication | Multi-AZ automatic | Multi-region automatic |
Compliance | SOC 2, FedRAMP | SOC 1/2/3, FIPS 140-2 | SOC 1/2, ISO 27001 |
HashiCorp Vault Security Architecture
Vault provides comprehensive secrets management with multiple authentication methods:
# Vault server configuration
storage "consul" {
address = "127.0.0.1:8500"
path = "vault/"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault/tls/vault.crt"
tls_key_file = "/etc/vault/tls/vault.key"
}
seal "awskms" {
region = "us-west-2"
kms_key_id = "alias/vault-unseal-key"
}
ui = true
api_addr = "https://vault.company.com:8200"
cluster_addr = "https://vault.company.com:8201"
# Vault policy configuration
vault policy write database-policy - <<EOF
path "database/creds/readonly" {
capabilities = ["read"]
}
path "database/creds/admin" {
capabilities = ["read"]
allowed_parameters = {
"ttl" = ["1h", "2h", "4h"]
}
}
path "secret/data/app/*" {
capabilities = ["create", "read", "update", "delete"]
}
EOF
AWS Secrets Manager Integration
AWS Secrets Manager provides native AWS service integration with automatic rotation:
# Python application with AWS Secrets Manager
import boto3
import json
from botocore.exceptions import ClientError
def get_secret(secret_name, region_name="us-west-2"):
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
except ClientError as e:
raise e
# Database connection with secrets
db_credentials = get_secret("prod/database/credentials")
connection = psycopg2.connect(
host=db_credentials['host'],
database=db_credentials['dbname'],
user=db_credentials['username'],
password=db_credentials['password']
)
# CloudFormation template for Secrets Manager
Resources:
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: prod/database/master
Description: Database master credentials
GenerateSecretString:
SecretStringTemplate: '{"username": "admin"}'
GenerateStringKey: "password"
PasswordLength: 32
ExcludeCharacters: '"@/\'
SecretRotationLambda:
Type: AWS::SecretsManager::RotationSchedule
Properties:
SecretId: !Ref DatabaseSecret
RotationLambdaArn: !GetAtt RotationLambda.Arn
RotationInterval: 30
Azure Key Vault Implementation
Azure Key Vault offers integrated Azure ecosystem management:
// C# application with Azure Key Vault
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
public class SecretService
{
private readonly SecretClient _secretClient;
public SecretService()
{
var keyVaultUrl = "https://company-vault.vault.azure.net/";
_secretClient = new SecretClient(
new Uri(keyVaultUrl),
new DefaultAzureCredential()
);
}
public async Task<string> GetSecretAsync(string secretName)
{
try
{
var secret = await _secretClient.GetSecretAsync(secretName);
return secret.Value.Value;
}
catch (Azure.RequestFailedException ex)
{
throw new SecretNotFoundException($"Secret {secretName} not found", ex);
}
}
}
// ARM template for Key Vault
{
"type": "Microsoft.KeyVault/vaults",
"apiVersion": "2021-10-01",
"name": "[parameters('keyVaultName')]",
"properties": {
"sku": {
"family": "A",
"name": "premium"
},
"tenantId": "[subscription().tenantId]",
"enabledForDeployment": true,
"enabledForTemplateDeployment": true,
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"accessPolicies": [
{
"tenantId": "[subscription().tenantId]",
"objectId": "[parameters('applicationObjectId')]",
"permissions": {
"keys": ["get", "list", "decrypt", "encrypt"],
"secrets": ["get", "list", "set"],
"certificates": ["get", "list", "create"]
}
}
]
}
}
Authentication and Authorization
Access Control Models
HashiCorp Vault authentication methods:
# Enable multiple auth methods
vault auth enable approle
vault auth enable kubernetes
vault auth enable ldap
# AppRole configuration for applications
vault write auth/approle/role/web-app \
token_policies="web-app-policy" \
token_ttl=1h \
token_max_ttl=4h \
bind_secret_id=true
# Kubernetes auth for pods
vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
vault write auth/kubernetes/role/web-app \
bound_service_account_names=web-app \
bound_service_account_namespaces=production \
policies=web-app-policy \
ttl=24h
AWS IAM integration:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SecretsManagerAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/application-role"
},
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:prod/database/*",
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/Environment": "production"
}
}
}
]
}
Azure RBAC and access policies:
# Azure CLI commands for access control
az keyvault set-policy \
--name "company-vault" \
--object-id "application-object-id" \
--secret-permissions get list set delete \
--key-permissions decrypt encrypt get list \
--certificate-permissions get list create
# Managed Identity assignment
az role assignment create \
--role "Key Vault Secrets User" \
--assignee "application-managed-identity" \
--scope "/subscriptions/sub-id/resourceGroups/rg/providers/Microsoft.KeyVault/vaults/company-vault"
Authentication Performance
Method | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
---|---|---|---|
Token Validation | 1-5ms (local) | 10-50ms (API call) | 10-50ms (API call) |
Certificate Auth | 5-15ms | Via IAM (10-30ms) | Via Azure AD (15-40ms) |
Service Principal | Via LDAP/OIDC | IAM roles | Managed Identity |
MFA Support | Multiple methods | AWS MFA | Azure MFA |
Session Management | Token renewal | AWS STS | Azure AD tokens |
Secrets Lifecycle Management
Dynamic Secrets and Rotation
Vault dynamic database credentials:
# Configure database secret engine
vault secrets enable database
vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres" \
allowed_roles="readonly,admin" \
username="vault-admin" \
password="admin-password"
# Create role with TTL
vault write database/roles/readonly \
db_name=postgresql \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# Application gets temporary credentials
vault read database/creds/readonly
AWS Secrets Manager automatic rotation:
# Lambda function for RDS password rotation
import boto3
import json
def lambda_handler(event, context):
secret_arn = event['Step1']['SecretArn']
token = event['Step1']['ClientRequestToken']
step = event['Step1']['Step']
secrets_client = boto3.client('secretsmanager')
rds_client = boto3.client('rds')
if step == "createSecret":
# Generate new password
response = secrets_client.get_random_password(
PasswordLength=32,
ExcludeCharacters='"@/\\'
)
new_password = response['RandomPassword']
# Store pending secret
secrets_client.put_secret_value(
SecretId=secret_arn,
VersionStage='AWSPENDING',
ClientRequestToken=token,
SecretString=json.dumps({
'username': current_secret['username'],
'password': new_password,
'host': current_secret['host']
})
)
Azure Key Vault with Event Grid:
// Azure Function for secret rotation
[FunctionName("SecretRotation")]
public static async Task Run(
[EventGridTrigger] EventGridEvent eventGridEvent,
ILogger log)
{
if (eventGridEvent.EventType == "Microsoft.KeyVault.SecretNearExpiry")
{
var secretName = eventGridEvent.Subject.Split('/').Last();
// Generate new secret
var newPassword = GenerateSecurePassword();
// Update external system
await UpdateExternalSystemPassword(secretName, newPassword);
// Store new version in Key Vault
await keyVaultClient.SetSecretAsync(
"https://company-vault.vault.azure.net/",
secretName,
newPassword
);
}
}
Secret Versioning and Rollback
Feature | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
---|---|---|---|
Versioning | KV v2 engine | Automatic | Automatic |
Version Retention | Configurable | Configurable | Configurable |
Rollback Capability | CLI/API | CLI/API/Console | CLI/API/Portal |
Metadata Tracking | Custom metadata | AWS tags | Azure tags |
Soft Delete | KV v2 | Yes (recovery window) | Yes (retention period) |
Integration and Ecosystem
Kubernetes Integration
Vault with External Secrets Operator:
# External Secrets Operator configuration
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.company.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "web-app"
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 5m
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
data:
- secretKey: database-password
remoteRef:
key: secret/database
property: password
AWS Secrets Manager CSI Driver:
# AWS Secrets Manager CSI driver
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: app-secrets
spec:
provider: aws
parameters:
objects: |
- objectName: "prod/database/credentials"
objectType: "secretsmanager"
jmesPath:
- path: "username"
objectAlias: "db-username"
- path: "password"
objectAlias: "db-password"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
template:
spec:
serviceAccountName: web-app-service-account
containers:
- name: app
image: web-app:latest
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "app-secrets"
Azure Key Vault CSI Driver:
# Azure Key Vault CSI provider
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kvname
spec:
provider: azure
parameters:
useVMManagedIdentity: "true"
userAssignedIdentityID: "client-id"
keyvaultName: "company-vault"
objects: |
array:
- |
objectName: database-password
objectType: secret
- |
objectName: api-key
objectType: secret
tenantId: "tenant-id"
CI/CD Pipeline Integration
GitLab CI with Vault:
# .gitlab-ci.yml with Vault integration
variables:
VAULT_ADDR: "https://vault.company.com"
before_script:
- apk add --no-cache curl jq
- export VAULT_TOKEN=$(curl -s -X POST $VAULT_ADDR/v1/auth/jwt/login \
-d "{\"jwt\":\"$CI_JOB_JWT\",\"role\":\"gitlab-ci\"}" | jq -r .auth.client_token)
deploy:
stage: deploy
script:
- DB_PASSWORD=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/database | jq -r .data.data.password)
- kubectl create secret generic app-secrets \
--from-literal=database-password="$DB_PASSWORD"
GitHub Actions with AWS Secrets:
# .github/workflows/deploy.yml
name: Deploy Application
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
- uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: us-west-2
- name: Get secrets
uses: aws-actions/aws-secretsmanager-get-secrets@v1
with:
secret-ids: |
DATABASE_PASSWORD,prod/database/credentials
API_KEY,prod/api/keys
- name: Deploy
run: |
kubectl create secret generic app-secrets \
--from-literal=db-password="$DATABASE_PASSWORD" \
--from-literal=api-key="$API_KEY"
Performance and Scalability
Throughput and Latency
Metric | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
---|---|---|---|
Read Latency | 1-10ms (local) | 50-200ms (API) | 50-200ms (API) |
Write Latency | 5-20ms | 100-300ms | 100-300ms |
Throughput | 10K+ ops/sec | 5K ops/sec | 5K ops/sec |
Rate Limits | Configurable | 5K/10 seconds | 2K/10 seconds |
Caching | Manual implementation | Client-side | Client-side |
Batch Operations | Limited | Single operations | Bulk operations |
High Availability Architecture
Vault Enterprise clustering:
# Vault cluster configuration
storage "raft" {
path = "/vault/data"
node_id = "node1"
retry_join {
leader_api_addr = "https://vault-0.vault-internal:8200"
}
retry_join {
leader_api_addr = "https://vault-1.vault-internal:8200"
}
retry_join {
leader_api_addr = "https://vault-2.vault-internal:8200"
}
}
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_cert_file = "/vault/tls/vault.crt"
tls_key_file = "/vault/tls/vault.key"
}
seal "awskms" {
region = "us-west-2"
kms_key_id = "alias/vault-unseal"
}
cluster_addr = "https://vault-0.vault-internal:8201"
api_addr = "https://vault-0.vault-internal:8200"
Cost Analysis and Licensing
Pricing Model Comparison
Factor | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
---|---|---|---|
Open Source | Free (basic features) | N/A | N/A |
Enterprise | $0.03/hour/seat | $0.40/secret/month | $0.03/10K operations |
Storage | Infrastructure cost | $0.40/secret/month | $0.03/secret/month |
API Calls | Included | $0.05/10K requests | $0.03/10K operations |
High Availability | Enterprise license | Included | Included |
Support | Enterprise | AWS Support | Azure Support |
Total Cost of Ownership (1000 secrets)
HashiCorp Vault self-hosted:
# Annual costs (1000 secrets)
Infrastructure: 3 x t3.medium = $1,000
Storage: 100GB SSD = $120
Staff: 0.25 FTE = $37,500
Vault Enterprise: 50 seats × $0.03 × 24 × 365 = $13,140
Total: ~$51,760/year
AWS Secrets Manager:
# Annual costs (1000 secrets)
Secret storage: 1000 × $0.40 × 12 = $4,800
API calls: 10M/month × $0.05/10K × 12 = $600
Rotation Lambda: $50/month × 12 = $600
Total: ~$6,000/year
Azure Key Vault:
# Annual costs (1000 secrets)
Standard tier: 1000 × $0.03 × 12 = $360
Operations: 1M/month × $0.03/10K × 12 = $36
Premium features: $500/month × 12 = $6,000
Total: ~$6,396/year
Compliance and Security Features
Audit and Compliance
Feature | HashiCorp Vault | AWS Secrets Manager | Azure Key Vault |
---|---|---|---|
Audit Logging | File, syslog, socket | CloudTrail | Azure Monitor |
Compliance | SOC 2, FedRAMP | SOC 1/2/3, PCI DSS | SOC 1/2, ISO 27001 |
Encryption | Transit + at-rest | KMS integration | HSM-backed |
Key Management | Transit secrets engine | AWS KMS | Azure Key Vault |
Secret Scanning | External tools | AWS Config rules | Azure Security Center |
Data Residency | Self-controlled | AWS regions | Azure regions |
Security Best Practices
Vault security configuration:
# Vault security policies
path "secret/*" {
capabilities = ["deny"]
}
path "secret/data/{{identity.entity.aliases.auth_kubernetes_*.metadata.service_account_namespace}}/*" {
capabilities = ["create", "read", "update", "delete"]
}
# Audit device configuration
audit "file" {
file_path = "/vault/logs/audit.log"
log_raw = false
hmac_accessor = true
mode = 0600
}
Migration and Decision Framework
Migration Strategies
Organizations often adopt multi-platform approaches during transitions:
# Python abstraction layer for multiple secret stores
class SecretManager:
def __init__(self, provider="vault"):
if provider == "vault":
self.client = VaultClient()
elif provider == "aws":
self.client = AWSSecretsClient()
elif provider == "azure":
self.client = AzureKeyVaultClient()
def get_secret(self, path):
return self.client.get_secret(path)
def set_secret(self, path, value):
return self.client.set_secret(path, value)
# Configuration-driven secret access
secrets = SecretManager(os.getenv("SECRET_PROVIDER", "vault"))
database_password = secrets.get_secret("database/password")
Decision Matrix
Choose HashiCorp Vault when:
- Multi-cloud or hybrid environment
- Complex authentication requirements
- Dynamic secrets capabilities needed
- Open-source flexibility preferred
- Advanced policy management required
Choose AWS Secrets Manager when:
- AWS-native environment
- Automatic rotation is critical
- Managed service preferred
- Simple pricing model desired
- Integration with AWS services needed
Choose Azure Key Vault when:
- Azure-centric environment
- HSM-backed security required
- Integration with Azure services needed
- Certificate management important
- Cost-effective operations desired
The secrets management landscape requires balancing security, operational complexity, and cost considerations. HashiCorp Vault provides the most flexibility and features for complex environments. AWS Secrets Manager excels in AWS-native deployments with automatic rotation. Azure Key Vault offers strong security with Azure ecosystem integration at competitive pricing.