Secrets Management & Infrastructure Security

HashiCorp Vault: Dynamic Secrets Management

4 min read

HashiCorp Vault is the industry standard for secrets management. It provides dynamic secrets, encryption as a service, and fine-grained access control.

Why Vault?

FeatureBenefit
Dynamic secretsGenerate short-lived credentials on-demand
Encryption as a serviceEncrypt data without managing keys
Audit loggingTrack every secret access
Lease managementAutomatic secret rotation
Multiple auth methodsOIDC, Kubernetes, AWS, GitHub

Core Concepts

┌─────────────────────────────────────────────────────┐
│                    Vault Server                      │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ┌──────────────┐  ┌──────────────┐                │
│  │ Auth Methods │  │ Secret       │                │
│  │ • Kubernetes │  │ Engines      │                │
│  │ • OIDC       │  │ • KV         │                │
│  │ • AWS IAM    │  │ • Database   │                │
│  │ • GitHub     │  │ • AWS        │                │
│  └──────────────┘  │ • PKI        │                │
│         │          └──────────────┘                │
│         ▼                 │                         │
│  ┌──────────────┐        │                         │
│  │   Policies   │◀───────┘                         │
│  │ (ACL Rules)  │                                  │
│  └──────────────┘                                  │
│                                                      │
└─────────────────────────────────────────────────────┘

Getting Started

Development Mode

# Start Vault in dev mode (NOT for production)
vault server -dev

# In another terminal
export VAULT_ADDR='http://127.0.0.1:8200'
export VAULT_TOKEN='root'

# Verify
vault status

KV Secrets Engine

# Enable KV v2 secrets engine
vault secrets enable -path=secret kv-v2

# Store a secret
vault kv put secret/myapp/config \
  api_key="sk-1234567890" \
  db_password="secure_password"

# Retrieve a secret
vault kv get secret/myapp/config

# Get specific field
vault kv get -field=api_key secret/myapp/config

Dynamic Database Credentials

Instead of static passwords, Vault generates temporary credentials:

# Enable database secrets engine
vault secrets enable database

# Configure PostgreSQL connection
vault write database/config/mydb \
  plugin_name=postgresql-database-plugin \
  connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/myapp" \
  allowed_roles="readonly" \
  username="vault_admin" \
  password="admin_password"

# Create a role for read-only access
vault write database/roles/readonly \
  db_name=mydb \
  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"

# Get dynamic credentials (new creds each time!)
vault read database/creds/readonly

Output:

Key                Value
---                -----
lease_id           database/creds/readonly/abc123
lease_duration     1h
username           v-token-readonly-xyz789
password           A1B2C3D4E5F6G7H8

Policies for Access Control

# policy.hcl - Developer policy
path "secret/data/myapp/*" {
  capabilities = ["read"]
}

path "database/creds/readonly" {
  capabilities = ["read"]
}

# Deny access to production secrets
path "secret/data/production/*" {
  capabilities = ["deny"]
}
# Create policy
vault policy write developer policy.hcl

# Assign policy to auth method
vault write auth/kubernetes/role/myapp \
  bound_service_account_names=myapp-sa \
  bound_service_account_namespaces=default \
  policies=developer \
  ttl=1h

CI/CD Integration

GitHub Actions with Vault

# .github/workflows/deploy.yml
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # Required for OIDC

    steps:
      - uses: actions/checkout@v4

      - name: Import Secrets from Vault
        uses: hashicorp/vault-action@v2
        with:
          url: https://vault.example.com
          method: jwt
          role: github-actions
          secrets: |
            secret/data/myapp/config api_key | API_KEY ;
            database/creds/readonly username | DB_USER ;
            database/creds/readonly password | DB_PASS

      - name: Deploy
        run: |
          # Secrets available as env vars
          ./deploy.sh
        env:
          API_KEY: ${{ env.API_KEY }}
          DB_USER: ${{ env.DB_USER }}
          DB_PASS: ${{ env.DB_PASS }}

Kubernetes Integration

# Pod with Vault Agent sidecar
apiVersion: v1
kind: Pod
metadata:
  name: myapp
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "myapp"
    vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
spec:
  serviceAccountName: myapp-sa
  containers:
    - name: app
      image: myapp:latest
      # Secrets available at /vault/secrets/config

Best Practices

PracticeWhy
Use dynamic secretsLimits blast radius of compromise
Short TTLs1h for dev, 15m for production
Audit everythingEnable audit logging to SIEM
Principle of least privilegeMinimal permissions per service
Separate Vault instancesDev/staging vs production

Next, we'll explore GitHub Secrets and OIDC authentication for simpler use cases. :::

Quick check: how does this lesson land for you?

Quiz

Module 5: Secrets Management & Infrastructure Security

Take Quiz
FREE WEEKLY NEWSLETTER

Stay on the Nerd Track

One email per week — courses, deep dives, tools, and AI experiments.

No spam. Unsubscribe anytime.