Enterprise Secrets Management: HashiCorp Vault, Kubernetes ESO, and SOPS

Secrets (API tokens, database passwords, TLS private keys, SSH keys) require centralized lifecycle governance, encryption-at-rest, dynamic generation, automated rotation, and strict access auditing. This guide covers HashiCorp Vault, dynamic database credentials, Kubernetes External Secrets Operator (ESO), and GitOps-friendly encryption with SOPS.


⚡ Quick Dive

Secrets Management Approaches Comparison

Solution Architecture Storage Location Rotation GitOps Friendly?
HashiCorp Vault Centralized API server with secret engines Encrypted backend (Raft, Consul) ⚡ Automated / Dynamic ✅ (Via ESO or Agent)
AWS Secrets Manager Managed Cloud Key-Value Store AWS Cloud Automated Lambda ✅ (Via ESO)
SOPS + age Client-side file encryption Encrypted in Git repo Manual / Scripted ⚡ Native GitOps
Sealed Secrets Asymmetric K8s Controller Encrypted in Git repo Cluster-bound key ⚡ Native GitOps
External Secrets (ESO) K8s Operator syncing external Vault/AWS Syncs to native K8s Secret Synced from upstream ⚡ Native GitOps

📖 Extended Guide

1. HashiCorp Vault: Dynamic Secrets Architecture

Static secrets (long-lived passwords) present permanent risk when leaked. Dynamic Secrets are generated on-demand with short Time-To-Live (TTL) leases and automatically revoked upon expiration.

[ App / Pod ] ── (Requests DB Access) ──► [ HashiCorp Vault ]
                                                   │
                                                   ▼ (Generates unique user & password)
                                          [ PostgreSQL Database ]
                                                   │
[ App receives: 'v-app-usr-8x2', lease 1h ] ◄──────┘
(After 1 hour, Vault automatically drops the user from PostgreSQL)

Vault CLI Quick Commands:

# Read static secret
vault kv get -mount=secret app/config

# Generate dynamic database credentials
vault read database/creds/read-only-role

# Revoke lease immediately
vault lease revoke database/creds/read-only-role/7f8a9...

2. Kubernetes External Secrets Operator (ESO)

In GitOps workflows, raw Kubernetes Secrets cannot be committed to Git. The External Secrets Operator (ESO) reads encrypted metadata from external Vault/AWS and generates native Kubernetes Secrets inside the cluster:

[ Git Repository ]
└── ExternalSecret YAML (Contains ONLY secret names & references)
          │ (Committed safely to Git)
          ▼
┌──────────────────────────────────────────────────────────┐
│ Kubernetes Cluster                                       │
│   [ External Secrets Operator ]                          │
│          ▲                                               │
│          │ (Fetches actual secret payload over mTLS)     │
│   [ HashiCorp Vault ]                                    │
│          │                                               │
│          ▼ (Creates inside cluster)                      │
│   [ Native Kubernetes Secret: 'db-credentials' ]         │
└──────────────────────────────────────────────────────────┘

ExternalSecret Resource Manifest:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: payment-db-secret
  namespace: production
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: payment-db-credentials # Name of created K8s Secret
  data:
    - secretKey: password
      remoteRef:
        key: secret/data/production/database
        property: password

3. Encrypted Secrets in Git with SOPS and age

SOPS (Secrets OPerationS) encrypts values inside YAML/JSON files while leaving the keys in plain text (allowing diffs and Git merges):

# 1. Generate an age keypair
age-keygen -o ~/.config/sops/age/keys.txt

# 2. Encrypt secrets.yaml with public key
sops --encrypt --age $(cat ~/.config/sops/age/keys.txt | grep "public key:" | cut -d: -f2 | tr -d ' ') \
  secrets.raw.yaml > secrets.enc.yaml

# 3. Edit encrypted file seamlessly (decrypts in memory, re-encrypts on save)
sops secrets.enc.yaml