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?

Feature Benefit
Dynamic secrets Generate short-lived credentials on-demand
Encryption as a service Encrypt data without managing keys
Audit logging Track every secret access
Lease management Automatic secret rotation
Multiple auth methods OIDC, 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

Practice Why
Use dynamic secrets Limits blast radius of compromise
Short TTLs 1h for dev, 15m for production
Audit everything Enable audit logging to SIEM
Principle of least privilege Minimal permissions per service
Separate Vault instances Dev/staging vs production

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

Quiz

Module 5: Secrets Management & Infrastructure Security

Take Quiz