Secrets Management: HashiCorp Vault vs AWS Secrets Manager vs SOPS

Secrets Management in Production: Vault, AWS Secrets Manager, and SOPS

Hardcoded credentials in configuration files and environment variables are the number one cause of security breaches in cloud-native applications. Secrets management tools like HashiCorp Vault, AWS Secrets Manager, and Mozilla SOPS solve this by centralizing, encrypting, and automating access to sensitive data. Therefore, this guide covers practical implementation patterns for each tool and helps you choose the right one for your infrastructure.

Why Secrets Management Matters

The traditional approach — storing database passwords in .env files, API keys in CI/CD variables, and TLS certificates on disk — creates several problems. Secrets sprawl across dozens of systems with no audit trail. Rotation requires manual updates across every service. A single leaked .env file exposes everything. Moreover, compliance frameworks like SOC 2, PCI DSS, and HIPAA require centralized secrets management with rotation and audit logging.

A proper secrets management system provides four capabilities: centralized storage with encryption at rest, access control with audit logging, automatic rotation, and dynamic secrets that are created on-demand and expire automatically. Additionally, it should integrate with your deployment pipeline so applications never see secrets in plaintext configuration files.

HashiCorp Vault: The Full-Featured Solution

Vault is the most flexible secrets management tool, supporting static secrets, dynamic credentials, PKI certificates, and encryption as a service. It runs as a standalone server (or cluster) and provides HTTP API, CLI, and UI access. However, this flexibility comes with operational complexity — running Vault in production requires careful attention to storage backends, unsealing, and high availability.

# Initialize Vault with auto-unseal (AWS KMS)
vault operator init -recovery-shares=5 -recovery-threshold=3

# Enable secrets engines
vault secrets enable -path=database database
vault secrets enable -path=kv-v2 kv-v2

# Configure dynamic database credentials
vault write database/config/myapp-db \
    plugin_name=postgresql-database-plugin \
    allowed_roles="myapp-readonly,myapp-readwrite" \
    connection_url="postgresql://{{username}}:{{password}}@db.internal:5432/myapp" \
    username="vault_admin" \
    password="vault_admin_password"

# Create a role that generates time-limited credentials
vault write database/roles/myapp-readonly \
    db_name=myapp-db \
    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 requests credentials — gets unique, time-limited creds
vault read database/creds/myapp-readonly
# Key                Value
# lease_id           database/creds/myapp-readonly/abc123
# lease_duration     1h
# username           v-token-myapp-r-xyz789
# password           A1b2C3d4E5-random-generated

Dynamic secrets are Vault’s killer feature. Instead of sharing a single database password across all application instances, each instance gets its own credentials that expire automatically. If an instance is compromised, only its credentials are affected, and they expire within the hour. Consequently, credential rotation happens automatically without any application changes.

Secrets management security infrastructure
Dynamic secrets generate unique, time-limited credentials per application instance

AWS Secrets Manager: Cloud-Native Simplicity

If your infrastructure is primarily AWS, Secrets Manager is the path of least resistance. It integrates natively with RDS, Lambda, ECS, and EKS. Automatic rotation for RDS credentials works with a single configuration. Furthermore, IAM policies control access, so you use the same permission model as the rest of your AWS infrastructure.

import boto3
import json
from botocore.exceptions import ClientError

# Retrieve a secret — with caching for performance
class SecretCache:
    def __init__(self, region="us-east-1"):
        self.client = boto3.client("secretsmanager", region_name=region)
        self._cache = {}

    def get_secret(self, secret_name):
        if secret_name in self._cache:
            return self._cache[secret_name]

        try:
            response = self.client.get_secret_value(SecretId=secret_name)
            secret = json.loads(response["SecretString"])
            self._cache[secret_name] = secret
            return secret
        except ClientError as e:
            if e.response["Error"]["Code"] == "ResourceNotFoundException":
                raise ValueError(f"Secret {secret_name} not found")
            raise

    def invalidate(self, secret_name):
        self._cache.pop(secret_name, None)

# Usage in application
secrets = SecretCache()
db_creds = secrets.get_secret("prod/myapp/database")
connection_string = f"postgresql://{db_creds['username']}:{db_creds['password']}@{db_creds['host']}/myapp"

# Enable automatic rotation for RDS secrets
# AWS provides Lambda rotation functions out of the box
# aws secretsmanager rotate-secret --secret-id prod/myapp/database \
#     --rotation-lambda-arn arn:aws:lambda:us-east-1:123456:function:SecretsManagerRotation \
#     --rotation-rules AutomaticallyAfterDays=30

Mozilla SOPS: Encrypted Files in Git

SOPS (Secrets OPerationS) takes a different approach — it encrypts secret values within configuration files while leaving keys visible. This means you can store encrypted secrets directly in Git, review diffs, and use your existing deployment pipeline. SOPS supports AWS KMS, GCP KMS, Azure Key Vault, and age/PGP for encryption.

# secrets.enc.yaml — encrypted with SOPS
# Keys are visible (for diffs), values are encrypted
database:
    host: ENC[AES256_GCM,data:abc123...==,tag:xyz...]
    port: ENC[AES256_GCM,data:def456...==,tag:uvw...]
    username: ENC[AES256_GCM,data:ghi789...==,tag:rst...]
    password: ENC[AES256_GCM,data:jkl012...==,tag:opq...]
api_keys:
    stripe: ENC[AES256_GCM,data:mno345...==,tag:lmn...]
    sendgrid: ENC[AES256_GCM,data:pqr678...==,tag:ijk...]
sops:
    kms:
        - arn: arn:aws:kms:us-east-1:123456:key/abc-def-ghi
    version: 3.8.1
# Encrypt a file
sops --encrypt --kms arn:aws:kms:us-east-1:123456:key/abc secrets.yaml > secrets.enc.yaml

# Edit encrypted file (decrypts in temp editor, re-encrypts on save)
sops secrets.enc.yaml

# Decrypt for deployment
sops --decrypt secrets.enc.yaml > /tmp/secrets.yaml

# Use with Kubernetes — decrypt and apply
sops --decrypt k8s-secrets.enc.yaml | kubectl apply -f -

# Use with Terraform
# terraform plan -var-file=<(sops --decrypt terraform.enc.tfvars)

SOPS works exceptionally well for GitOps workflows where all configuration lives in Git. You get version history, pull request reviews, and audit trails through Git itself. However, SOPS doesn't handle dynamic secrets or automatic rotation — it's purely a storage and encryption solution.

Encrypted secrets management workflow
SOPS encrypts values while keeping keys readable for Git diffs and code reviews

Kubernetes External Secrets and Secret Rotation

In Kubernetes environments, the External Secrets Operator bridges the gap between your secrets store (Vault, AWS Secrets Manager, GCP Secret Manager) and Kubernetes Secrets. It periodically syncs secrets from the external store into Kubernetes, so applications consume standard Kubernetes Secrets while the actual values live in a centralized, managed store.

# ExternalSecret resource — syncs from AWS Secrets Manager to K8s Secret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-database
  namespace: production
spec:
  refreshInterval: 5m      # Check for updates every 5 minutes
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: myapp-db-credentials     # Kubernetes Secret name
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: prod/myapp/database
        property: username
    - secretKey: password
      remoteRef:
        key: prod/myapp/database
        property: password
    - secretKey: host
      remoteRef:
        key: prod/myapp/database
        property: host

For secret rotation, the pattern depends on your tool. Vault handles rotation through dynamic secrets with TTLs. AWS Secrets Manager uses Lambda rotation functions. For application-managed rotation, implement a two-credential pattern: generate new credentials, update the secret store, wait for all instances to pick up the new credentials, then revoke the old ones.

Choosing the Right Tool

Choose Vault when: You need dynamic secrets, multi-cloud support, PKI management, or encryption as a service. Be prepared for operational overhead.

Choose AWS Secrets Manager when: Your infrastructure is AWS-centric and you want managed simplicity with automatic RDS rotation.

Choose SOPS when: You want encrypted secrets in Git for GitOps workflows, your team is small, and you don't need dynamic secrets.

Combine them: Many teams use SOPS for infrastructure-as-code secrets (Terraform variables, Kubernetes manifests) and Vault or AWS Secrets Manager for application runtime secrets.

Cloud security architecture
Combine SOPS for IaC secrets with Vault or AWS Secrets Manager for runtime secrets

Related Reading:

Resources:

In conclusion, secrets management is a non-negotiable part of production infrastructure. Start with the tool that fits your existing stack — AWS Secrets Manager for AWS-heavy environments, SOPS for GitOps, Vault for complex multi-cloud setups. The critical principle is that secrets should never exist in plaintext configuration files, environment variables, or CI/CD settings.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top