Secret Management â HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets, and SOPS Encryption
In this tutorial, you'll learn about Secret Management. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Secret management is the practice of securely storing, accessing, rotating, and auditing sensitive information â database credentials, API tokens, encryption keys, and certificates â throughout their lifecycle.
What You'll Learn
Why It Matters
Hardcoded secrets in source code, configuration files, or environment variables are the most common attack vector in production breaches. A single committed API key can expose an entire cloud account. Dedicated secret management tools provide encryption at rest and in transit, automated rotation, granular access control, and complete audit trails.
Real-World Use
Durga Antivirus Pro uses HashiCorp Vault for dynamic database credentials, AWS Secrets Manager for cloud API keys with automatic rotation, Kubernetes External Secrets Operator to sync secrets into pods, and SOPS-encrypted files in Git for GitOps workflows â ensuring secrets never appear in plaintext anywhere in the pipeline.
flowchart LR
A[Application] --> B{Secret Source}
B --> C[HashiCorp Vault]
B --> D[AWS Secrets Manager]
B --> E[K8s External Secrets]
B --> F[SOPS + Git]
C --> G[Dynamic DB Creds]
D --> H[Auto-rotated API Keys]
E --> I[Synced to Pods]
F --> J[Encrypted in Repo]
style C fill:#FFD814,color:#000
style D fill:#ff9900,color:#fff
Prerequisites: Basic understanding of Cloud Security concepts, Kubernetes fundamentals, encryption vs hashing, and Terraform for infrastructure examples.
HashiCorp Vault
Vault provides a unified secrets management platform with dynamic secrets, leasing, revocation, and detailed audit logging.
# Install Vault in dev mode (for learning only)
vault server -dev
# Expected output:
# ==> Vault server configuration:
# Api Address: http://127.0.0.1:8200
# Cgo: disabled
# Cluster Address: https://127.0.0.1:8201
# Go Version: go1.21.0
# Root Token: hvs.xxxxxx
#
# You may need to set the following environment variable:
# export VAULT_ADDR='http://127.0.0.1:8200'
# The unauthenticated requests will fail with a 401 error.
# Set environment and authenticate
export VAULT_ADDR='http://127.0.0.1:8200'
vault login hvs.xxxxxx
# Store a secret
vault kv put secret/myapp/database \
username=admin \
password=s3cret! \
host=postgres.prod.svc.cluster.local \
port=5432
# Expected output:
# Success! Data written to: secret/myapp/database
# Read the secret
vault kv get secret/myapp/database
# Expected output:
# ====== Metadata ======
# Key Value
# --- -----
# created_time 2026-06-22T10:00:00Z
# deletion_time n/a
# destroyed false
# version 1
#
# ====== Data ======
# Key Value
# --- -----
# host postgres.prod.svc.cluster.local
# password s3cret!
# port 5432
# username admin
Dynamic Secrets
Vault can generate short-lived, dynamically-scoped credentials on demand â a database user that exists only for the lifetime of a lease.
# Configure Vault for PostgreSQL dynamic secrets
vault write database/config/postgres-db \
plugin_name=postgresql-database-plugin \
allowed_roles="myapp-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/myapp" \
username="vault-admin" \
password="vault-admin-pass"
vault write database/roles/myapp-role \
db_name=postgres-db \
creation_statements="CREATE USER \"{{name}}\" WITH PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# Request dynamic credentials
vault read database/creds/myapp-role
# Expected output:
# Key Value
# --- -----
# lease_id database/creds/myapp-role/xxxx
# lease_duration 1h
# lease_renewable true
# password xxxxxx
# username v-token-myapp-role-abc123
Expected behavior: Each call to database/creds/myapp-role creates a unique PostgreSQL user with a 1-hour TTL. The user is automatically revoked when the lease expires. No long-lived database credentials exist anywhere.
AWS Secrets Manager
AWS Secrets Manager manages secrets with automatic rotation, fine-grained IAM policies, and cross-region Replication.
# Store a secret
aws secretsmanager create-secret \
--name production/db-password \
--secret-string '{"username":"admin","password":"s3cret!"}'
# Expected output:
# {
# "ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:production/db-password-abc123",
# "Name": "production/db-password",
# "VersionId": "xxxx-xxxx-xxxx"
# }
# Retrieve the secret
aws secretsmanager get-secret-value \
--secret-id production/db-password
# Expected output:
# {
# "ARN": "arn:aws:secretsmanager:...",
# "Name": "production/db-password",
# "SecretString": "{\"username\":\"admin\",\"password\":\"s3cret!\"}",
# "VersionId": "xxxx-xxxx-xxxx"
# }
# Terraform â reference an existing secret
data "aws_secretsmanager_secret" "db_password" {
name = "production/db-password"
}
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = data.aws_secretsmanager_secret.db_password.id
}
resource "aws_ecs_task_definition" "app" {
container_definitions = jsonencode([
{
name = "myapp]
image = "myapp:latest"
secrets = [
{
name = "DATABASE_PASSWORD]
valueFrom = data.aws_secretsmanager_secret_version.db_password.arn
}
]
}
])
}
Expected behavior: The secret is referenced by ARN, never by value. IAM policies control which roles can read which secrets. The ECS task definition references the secret â ECS injects it as an environment variable at runtime.
Kubernetes External Secrets
The External Secrets Operator synchronizes secrets from external providers into Kubernetes Secrets, keeping them in sync automatically.
# Create an ExternalSecret resource
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: "1h"
secretStoreRef:
name: aws-secretsmanager
kind: SecretStore
target:
name: db-credentials # Name of the K8s Secret to create
creationPolicy: Owner
data:
- secretKey: username # Key in the K8s Secret
remoteRef:
key: production/db-password
property: username
- secretKey: password
remoteRef:
key: production/db-password
property: password
# Deployment consuming the synced secret
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
envFrom:
- secretRef:
name: db-credentials
Expected behavior: When the ExternalSecret is applied, the operator reads the secret from AWS Secrets Manager and creates a Kubernetes Secret named db-credentials. If the secret in AWS is rotated, the operator updates the Kubernetes Secret within one hour.
SOPS â Encrypted Files for GitOps
SOPS encrypts individual values in YAML, JSON, and .env files so they can be safely committed to Git repositories.
# Create an encrypted values file
cat > values.enc.yaml << 'EOF'
apiKey: my-super-secret-api-key
dbPassword: changeme
EOF
# Encrypt with age key
sops --encrypt --age age1xxxxx... values.enc.yaml
# Expected output: (file is now encrypted)
# apiKey: ENC[AES256_GCM,data:xxxxx,type:str]
# dbPassword: ENC[AES256_GCM,data:xxxxx,type:str]
# sops:
# kms: []
# gcp_kms: []
# azure_kv: []
# hc_vault: []
# age:
# - recipient: age1xxxxx...
# enc: |
# -----BEGIN AGE ENCRYPTED FILE-----
# xxxxx
# -----END AGE ENCRYPTED FILE-----
# lastmodified: '2026-06-22T10:00:00Z'
# Decrypt for use
sops --decrypt values.enc.yaml
# Expected output:
# apiKey: my-super-secret-api-key
# dbPassword: changeme
# Edit encrypted file in place
sops values.enc.yaml
Expected behavior: The file is encrypted at rest in the Git Repository. Only holders of the age private key can decrypt it. CI/CD pipelines decrypt the file at deploy time using the key stored in the CI/CD secrets store.
Best Practices
Secret Rotation
Every secret should have a maximum lifetime. Automatic rotation reduces the Blast Radius of a compromised secret.
# AWS Secrets Manager rotation schedule
RotationRules:
AutomaticallyAfterDays: 30
Expected behavior: AWS rotates the secret automatically every 30 days. Applications using the secret must handle rotation gracefully â either by reloading secrets periodically or by reading the latest version on every connection.
Least Privilege Access
Secrets should be accessible only to the services and humans that absolutely need them.
# Vault policy â restrict to reading only production database secrets
path "secret/data/production/database" {
capabilities = ["read"]
}
path "secret/metadata/production/database" {
capabilities = ["read", "list"]
}
Expected behavior: An application with this policy can read the production database secret but cannot list all secrets, write secrets, or delete them.
Common Errors
Committing secrets to Git: Once a secret is in Git history, it is compromised forever. Use
.gitignorepatterns, pre-commit hooks (trufflehog, git-secrets), and SOPS-encrypted files for any configuration that must be version-controlled.Hardcoding secrets in environment variables: Environment variables are visible in
/proc, debugging tools, and CI/CD logs. Use dedicated secret stores and inject secrets at runtime.Not rotating secrets regularly: A secret that never rotates becomes more valuable to attackers over time. Set automated rotation on all production secrets.
Overly permissive access policies: Granting read access to all secrets in a Vault path or AWS KMS key means one compromised application exposes every secret in the system.
Storing secrets in Terraform state: Terraform state contains plaintext values of all resource attributes, including secrets. Always use remote state with encryption and restrict state file access.
Not planning for rotation: Applications that read a secret once at startup break when the secret rotates. Implement secrets refreshing or use SDKs that auto-rotate.
Practice Questions
What is the difference between static and dynamic secrets? Answer: Static secrets (e.g., a database password in a file) are long-lived and never change until manually rotated. Dynamic secrets (e.g., Vault-generated database credentials) are short-lived, automatically revoked, and created on demand.
How does the External Secrets Operator keep Kubernetes Secrets in sync? Answer: ESO periodically polls the external secrets provider (AWS Secrets Manager, Vault, GCP Secret Manager) and updates the Kubernetes Secret to match. The refresh interval is configurable.
Why is SOPS useful for GitOps workflows? Answer: SOPS encrypts secret values while leaving file structure intact. Encrypted files can be committed to Git alongside plaintext configurations, enabling GitOps without exposing secrets.
What is the impact of not rotating secrets? Answer: A compromised secret that never rotates gives an attacker permanent access. Rotation limits the exposure window to the rotation period â a 30-day rotation means the secret is valid for at most 30 days.
Challenge
Implement a complete secret management workflow: store an API key in AWS Secrets Manager with 30-day automatic rotation, create a Vault dynamic database role that generates 1-hour credentials, configure the External Secrets Operator to sync the AWS secret into Kubernetes, encrypt a Helm values file with SOPS and commit it to Git, restrict access with Vault policies, and verify that pods consuming the synced secret receive updates after rotation.
Mini Project
Set up a production-grade secret management infrastructure. Deploy Vault in dev mode and configure a dynamic PostgreSQL role. Store a static API key in AWS Secrets Manager. Install External Secrets Operator on a Kubernetes cluster and create a SecretStore that connects to both Vault and AWS Secrets Manager. Create an ExternalSecret that syncs the database credentials into a Kubernetes Secret. Consume that Secret in a Deployment. Finally, encrypt a Helm values.yaml file with SOPS using an age key, commit it to a Git Repository, and verify that sops --decrypt produces the original plaintext values.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro