Skip to content

Secret Management — HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets, and SOPS Encryption

DodaTech Updated 2026-06-22 8 min read

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
â„šī¸ Info

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

  1. Committing secrets to Git: Once a secret is in Git history, it is compromised forever. Use .gitignore patterns, pre-commit hooks (trufflehog, git-secrets), and SOPS-encrypted files for any configuration that must be version-controlled.

  2. 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.

  3. Not rotating secrets regularly: A secret that never rotates becomes more valuable to attackers over time. Set automated rotation on all production secrets.

  4. 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.

  5. 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.

  6. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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