Secrets Management
Decision Guide
Migration Plan
18 min read

Secrets Management: Vault vs SSM Decision Guide

Complete comparison of HashiCorp Vault vs AWS Systems Manager Parameter Store with decision framework, cost analysis, Kubernetes integration patterns, and step-by-step migration playbook.

Published: January 2, 2025

Updated: January 2, 2025

The Secrets Management Problem

Every application needs secrets: database passwords, API keys, TLS certificates, encryption keys. The worst place to store them? Hardcoded in code, committed to Git, or in plain-text environment variables.

The two most popular solutions for proper secrets management are:

  • HashiCorp Vault: Platform-agnostic, feature-rich, self-hosted or managed (HCP Vault)
  • AWS Systems Manager Parameter Store (+ Secrets Manager): AWS-native, deeply integrated, fully managed

This guide helps you choose the right one for your organization, provides a detailed comparison, and includes migration plans for moving from one to the other.

What You'll Learn
  • Feature Comparison: Vault vs SSM vs Secrets Manager (all features)
  • Decision Framework: Which one for your use case?
  • Cost Analysis: Real pricing at scale ($100/mo vs $1,000/mo)
  • Kubernetes Integration: External Secrets Operator patterns
  • Migration Playbook: Step-by-step guides for moving between systems
  • Best Practices: Secret rotation, access patterns, auditing

Feature Comparison Matrix

┌─────────────────────────────┬───────────────────┬──────────────────┬──────────────────────┐ │ Feature │ HashiCorp Vault │ SSM Parameter │ AWS Secrets Manager │ │ │ │ Store │ │ ├─────────────────────────────┼───────────────────┼──────────────────┼──────────────────────┤ │ **Platform Support** │ Any cloud, on-prem│ AWS only │ AWS only │ │ Multi-cloud/hybrid │ ✅ Excellent │ ❌ AWS locked-in │ ❌ AWS locked-in │ │ Self-hosted option │ ✅ Open source │ ❌ Managed only │ ❌ Managed only │ │ │ │ │ │ │ **Secret Storage** │ │ │ │ │ Max secret size │ 1 MB │ 4 KB (standard) │ 64 KB │ │ │ │ 8 KB (advanced) │ │ │ Versioning │ ✅ Full history │ ✅ Up to 100 │ ✅ Full history │ │ Secret rotation │ ✅ Built-in │ ❌ Manual │ ✅ Automated (RDS, │ │ │ │ │ RedShift, etc.) │ │ │ │ │ │ │ **Dynamic Secrets** │ │ │ │ │ Database credentials │ ✅ Ephemeral │ ❌ Static only │ ⚠️ Rotation only │ │ AWS IAM credentials │ ✅ Temporary │ ❌ No │ ❌ No │ │ PKI/TLS certificates │ ✅ CA + issuance │ ❌ No │ ⚠️ Import only │ │ │ │ │ │ │ **Access Control** │ │ │ │ │ Fine-grained policies │ ✅ Path-based ACL │ ✅ IAM policies │ ✅ IAM policies │ │ Identity federation │ ✅ OIDC, LDAP, │ ⚠️ IAM only │ ⚠️ IAM only │ │ │ K8s, etc. │ │ │ │ Leasing & TTL │ ✅ Yes │ ❌ No │ ❌ No │ │ │ │ │ │ │ **Auditability** │ │ │ │ │ Audit logging │ ✅ Detailed │ ✅ CloudTrail │ ✅ CloudTrail │ │ Who accessed what/when │ ✅ Full trail │ ✅ Yes │ ✅ Yes │ │ │ │ │ │ │ **Operations** │ │ │ │ │ High Availability │ ⚠️ Self-manage │ ✅ AWS managed │ ✅ AWS managed │ │ Disaster Recovery │ ⚠️ Manual backup │ ✅ Multi-region │ ✅ Multi-region │ │ Operational complexity │ 🔴 High (if self- │ 🟢 Low │ 🟢 Low │ │ │ hosted) │ │ │ │ │ │ │ │ │ **Cost (approx)** │ │ │ │ │ 1,000 secrets, 1M reads/mo │ $0-$50 (self) │ $1-$5 │ $400-$500 │ │ │ or $300 (HCP) │ │ │ │ 10,000 secrets, 10M reads │ $0-$200 (self) │ $10-$50 │ $4,000-$5,000 │ │ │ or $1,500 (HCP) │ │ │ └─────────────────────────────┴───────────────────┴──────────────────┴──────────────────────┘

Deep Dive: HashiCorp Vault

When Vault Wins

Choose Vault if you need:

  • Multi-cloud or on-premises: Secrets for AWS, GCP, Azure, bare metal
  • Dynamic secrets: Ephemeral database credentials, AWS IAM tokens, PKI certificates
  • Advanced access control: Identity-based auth (OIDC, K8s service accounts, LDAP)
  • Encryption as a Service: Application-level encryption without managing keys
  • Compliance: Detailed audit logs, lease management, policy as code
Vault Setup (Self-Hosted)

# Install Vault on Kubernetes (production-ready with HA) helm repo add hashicorp https://helm.releases.hashicorp.com helm install vault hashicorp/vault \ --namespace vault --create-namespace \ --set server.ha.enabled=true \ --set server.ha.replicas=3 \ --set ui.enabled=true \ --set server.dataStorage.size=10Gi # Initialize Vault (do this ONCE) kubectl exec -n vault vault-0 -- vault operator init -key-shares=5 -key-threshold=3 # Output: 5 unseal keys + 1 root token # STORE THESE SECURELY (split across team members or use PGP encryption) # Unseal Vault on each pod (requires 3 of 5 keys) kubectl exec -n vault vault-0 -- vault operator unseal <KEY1> kubectl exec -n vault vault-0 -- vault operator unseal <KEY2> kubectl exec -n vault vault-0 -- vault operator unseal <KEY3> # Repeat for vault-1 and vault-2 # Verify status kubectl exec -n vault vault-0 -- vault status

Enable Dynamic Database Secrets

# Enable database secrets engine vault secrets enable database # Configure PostgreSQL connection vault write database/config/prod-postgres \ plugin_name=postgresql-database-plugin \ allowed_roles="readonly","readwrite" \ connection_url="postgresql://{{username}}:{{password}}@postgres.prod.svc:5432/app?sslmode=require" \ username="vault-admin" \ password="vault-admin-password" # Create a role that generates 1-hour credentials vault write database/roles/readonly \ db_name=prod-postgres \ 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" # Test: Generate ephemeral credentials vault read database/creds/readonly # Output: # Key Value # lease_id database/creds/readonly/abc123 # lease_duration 1h # username v-root-readonly-xyz456 # password A1b2C3d4E5f6... # After 1 hour, these credentials are automatically revoked!

Key benefit: Applications never store long-lived database passwords. They fetch new credentials on startup, use them, and they expire automatically.

Deep Dive: AWS Systems Manager Parameter Store

When SSM Parameter Store Wins

Choose SSM if you need:

  • AWS-only infrastructure: No plans for multi-cloud
  • Simplicity over features: Just need key-value storage with encryption
  • Low cost: 10,000 standard parameters = $0/month (plus API call costs)
  • Deep AWS integration: EC2, ECS, Lambda, CloudFormation native support
  • Minimal operations: Fully managed, no servers to maintain
SSM Setup & Usage

# Store a secret (encrypted with KMS) aws ssm put-parameter \ --name "/production/database/password" \ --value "super-secret-password" \ --type SecureString \ --kms-key-id alias/production-secrets \ --description "Production database password" \ --tags Key=Environment,Value=production Key=Owner,Value=platform-team # Retrieve a secret aws ssm get-parameter \ --name "/production/database/password" \ --with-decryption # Output (JSON): { "Parameter": { "Name": "/production/database/password", "Type": "SecureString", "Value": "super-secret-password", "Version": 1, "LastModifiedDate": "2025-01-02T10:00:00.000Z", "ARN": "arn:aws:ssm:us-east-1:123456789012:parameter/production/database/password" } } # Get multiple secrets by path aws ssm get-parameters-by-path \ --path "/production/database" \ --with-decryption \ --recursive # Update a secret (creates new version) aws ssm put-parameter \ --name "/production/database/password" \ --value "new-super-secret-password" \ --type SecureString \ --overwrite

SSM vs Secrets Manager: When to Use Each

AWS has TWO secret storage services. Here's when to use each:

SSM Parameter Store (Standard Tier)

Use for: Configuration values, feature flags, non-sensitive data, infrequently-changing secrets

Cost: $0 for up to 10,000 parameters + $0.05 per 10,000 API calls

AWS Secrets Manager

Use for: Secrets requiring automatic rotation (RDS, Redshift, DocumentDB passwords), secrets with built-in integrations

Cost: $0.40/secret/month + $0.05 per 10,000 API calls

Kubernetes Integration: External Secrets Operator

Both Vault and SSM can inject secrets into Kubernetes using the External Secrets Operator. This is the modern approach (replaces older methods like Vault Agent Injector or aws-secret-operator).

# Install External Secrets Operator helm repo add external-secrets https://charts.external-secrets.io helm install external-secrets external-secrets/external-secrets \ --namespace external-secrets --create-namespace # Verify installation kubectl get pods -n external-secrets

Example 1: Sync from Vault

# Create SecretStore (points to Vault) apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: vault-backend namespace: production spec: provider: vault: server: "https://vault.vault.svc:8200" path: "secret" version: "v2" auth: kubernetes: mountPath: "kubernetes" role: "production-app" --- # Create ExternalSecret (syncs specific secrets) apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-database-secret namespace: production spec: refreshInterval: 1h secretStoreRef: name: vault-backend kind: SecretStore target: name: app-database-secret # Name of resulting K8s Secret creationPolicy: Owner data: - secretKey: DB_PASSWORD # Key in K8s Secret remoteRef: key: production/database # Path in Vault property: password # Field in Vault secret

Example 2: Sync from AWS SSM

# Create SecretStore (points to SSM) apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: aws-parameter-store namespace: production spec: provider: aws: service: ParameterStore region: us-east-1 auth: jwt: # Uses IRSA (IAM Roles for Service Accounts) serviceAccountRef: name: external-secrets-sa --- # Create ExternalSecret apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app-config namespace: production spec: refreshInterval: 5m secretStoreRef: name: aws-parameter-store kind: SecretStore target: name: app-config dataFrom: - extract: key: /production/app/ # Syncs ALL parameters under this path

Result: Secrets in Vault/SSM are automatically synced to Kubernetes Secrets. Update the source, and External Secrets Operator refreshes the K8s Secret within `refreshInterval`.

Decision Framework

Use this flowchart to decide:

Question 1: Are you multi-cloud or on-premises?

Yes:Use Vault (SSM is AWS-only)

No (AWS-only): → Continue to Question 2

Question 2: Do you need dynamic secrets (ephemeral credentials)?

Yes:Use Vault (SSM doesn't support dynamic secrets)

No: → Continue to Question 3

Question 3: Do you need automatic secret rotation?

Yes, for RDS/Redshift/etc:Use AWS Secrets Manager (built-in rotation)

No / Custom rotation: → Continue to Question 4

Question 4: How many secrets do you have?

🔹 <1,000 secrets:Use SSM Parameter Store (simple, free tier)

🔹 1,000-10,000 secrets:Use SSM or Vault (cost similar, choose based on team expertise)

🔹 >10,000 secrets:Use Vault (AWS Secrets Manager becomes expensive)

Cost Analysis at Scale

┌────────────────────────┬──────────────┬──────────────┬──────────────────┐ │ Scenario │ Vault │ SSM Param │ Secrets Manager │ │ │ (self-hosted)│ Store │ │ ├────────────────────────┼──────────────┼──────────────┼──────────────────┤ │ 100 secrets │ $0-$10 │ $0 │ $40/mo │ │ 100K reads/mo │ │ │ │ │ │ │ │ │ │ 1,000 secrets │ $0-$50 │ $1-$5 │ $400/mo │ │ 1M reads/mo │ │ │ │ │ │ │ │ │ │ 5,000 secrets │ $0-$100 │ $5-$25 │ $2,000/mo │ │ 5M reads/mo │ │ │ │ │ │ │ │ │ │ 10,000 secrets │ $0-$200 │ $10-$50 │ $4,000/mo │ │ 10M reads/mo │ │ │ │ │ │ │ │ │ │ 50,000 secrets │ $0-$500 │ $50-$250 │ $20,000/mo │ │ 50M reads/mo │ │ │ │ └────────────────────────┴──────────────┴──────────────┴──────────────────┘ Note: Vault costs assume self-hosted on existing infrastructure. Add $300-$1,500/mo for HCP Vault (managed) depending on scale.

Key takeaway: AWS Secrets Manager is expensive at scale ($0.40/secret/month adds up fast). SSM Parameter Store is nearly free for storage, only charging for API calls. Vault is free if self-hosted, but adds operational complexity.

Migration Plan: SSM → Vault

If you're outgrowing SSM and need Vault's advanced features, here's a phased migration plan:

Phase 1: Setup Vault (Parallel Operation)
  1. Deploy Vault on Kubernetes (HA mode, 3 replicas)
  2. Configure auto-unseal with AWS KMS (no manual unsealing)
  3. Set up audit logging to S3
  4. Create initial policies and authentication methods
Phase 2: Bulk Secret Migration
Use this script to copy all secrets from SSM to Vault:

#!/bin/bash # migrate-ssm-to-vault.sh set -e VAULT_ADDR="https://vault.example.com" VAULT_TOKEN="your-vault-token" SSM_PATH="/production/" echo "Fetching all SSM parameters under $SSM_PATH..." PARAMETERS=$(aws ssm get-parameters-by-path \ --path "$SSM_PATH" \ --with-decryption \ --recursive \ --output json) echo "$PARAMETERS" | jq -r '.Parameters[] | @json' | while read -r param; do NAME=$(echo "$param" | jq -r '.Name') VALUE=$(echo "$param" | jq -r '.Value') # Convert SSM path to Vault path # /production/database/password -> secret/data/production/database VAULT_PATH=$(echo "$NAME" | sed 's|^/||' | sed 's|/[^/]*$||') KEY=$(basename "$NAME") echo "Migrating $NAME -> secret/data/$VAULT_PATH (key: $KEY)" # Write to Vault (KV v2) vault kv put "secret/$VAULT_PATH" "$KEY=$VALUE" done echo "Migration complete!"

Phase 3: Update Applications (Gradual)
  1. Update External Secrets Operator config to point to Vault
  2. Test in dev/staging environments first
  3. Migrate prod apps one service at a time
  4. Monitor for errors, have SSM fallback ready
Phase 4: Deprecate SSM (After 2-4 weeks)
  1. Verify all apps are using Vault
  2. Check SSM CloudTrail logs (should show no recent API calls)
  3. Delete SSM parameters (keep backups for 30 days)
  4. Remove SSM IAM policies from apps

Migration Plan: Vault → SSM

Moving from Vault to SSM (simplification/cost reduction scenario):

#!/bin/bash # migrate-vault-to-ssm.sh set -e VAULT_ADDR="https://vault.example.com" VAULT_TOKEN="your-vault-token" VAULT_PATH="secret/production" echo "Fetching all secrets from Vault at $VAULT_PATH..." SECRETS=$(vault kv list -format=json "$VAULT_PATH") echo "$SECRETS" | jq -r '.[]' | while read -r secret; do echo "Processing $VAULT_PATH/$secret..." # Get secret data SECRET_DATA=$(vault kv get -format=json "$VAULT_PATH/$secret") # Extract key-value pairs echo "$SECRET_DATA" | jq -r '.data.data | to_entries[] | @json' | while read -r kv; do KEY=$(echo "$kv" | jq -r '.key') VALUE=$(echo "$kv" | jq -r '.value') # Convert to SSM path SSM_NAME="/production/$secret/$KEY" echo "Writing to SSM: $SSM_NAME" aws ssm put-parameter \ --name "$SSM_NAME" \ --value "$VALUE" \ --type SecureString \ --overwrite done done echo "Migration complete!"

Best Practices (Both Systems)

1. Never Store Secrets in Code or Git

Use git-secrets or gitleaks to scan commits. Configure pre-commit hooks to prevent accidental commits.

2. Rotate Secrets Regularly

High-risk secrets (prod DB): Every 30-90 days
Medium-risk (API keys): Every 6 months
Low-risk (feature flags): As needed

3. Use Least-Privilege Access

Vault: Path-based policies (apps can only read /production/their-app/)
SSM: IAM policies with parameter name prefixes (Allow: GetParameter on /production/app-name/*)

4. Enable Audit Logging

Vault: Configure audit backend (file, syslog, or AWS CloudWatch)
SSM: Enable CloudTrail for all GetParameter API calls

Conclusion: The Right Tool for the Job

There's no universal winner between Vault and SSM—it depends on your requirements:

  • Starting from scratch, AWS-only? → SSM Parameter Store (simple, free)
  • Need automatic RDS rotation? → AWS Secrets Manager
  • Multi-cloud or advanced features? → HashiCorp Vault
  • Growing fast (10,000+ secrets)? → Vault (cost-effective at scale)

Pro tip: You can use both! Store config in SSM, dynamic secrets in Vault. Use External Secrets Operator to abstract the backend from your apps. This gives you flexibility to migrate later without changing application code.

Need Help With Secrets Management?

We design and implement production-grade secrets management: Vault setup, SSM architecture, rotation automation, Kubernetes integration, and migration support.

Related Articles

logo

HostingX IL

Scalable automation & integration platform accelerating modern B2B product teams.

michael@hostingx.co.il
+972544810489

Connect

EmailIcon

Subscribe to our newsletter

Get monthly email updates about improvements.


Copyright © 2025 HostingX IL. All Rights Reserved.

Terms

Privacy

Cookies

Manage Cookies

Data Rights

Unsubscribe