Skip to content

HashiCorp Vault - Primer

Why This Matters

Every production system has secrets — database passwords, API keys, TLS certificates, encryption keys. The default approach (environment variables, config files, CI/CD secret stores) scatters secrets across dozens of systems with no audit trail, no rotation, and no access control beyond "you can see the repo." HashiCorp Vault centralizes secrets management with access control policies, full audit logging, automatic credential rotation, and encryption-as-a-service. It is the industry standard for organizations managing secrets at scale. If your company has more than a handful of services, you will encounter Vault.

Core Concepts

1. Architecture and Seal/Unseal

Who made it: HashiCorp Vault was first released in April 2015 by Mitchell Hashimoto and Armon Dadgar. The name "Vault" is a literal metaphor -- a secure vault where you lock up secrets.

Vault stores data encrypted at rest. On startup, it is in a sealed state — it has the encrypted data but cannot decrypt it. Unsealing provides Vault with the master key (split into shares via Shamir's Secret Sharing) so it can read its storage.

Under the hood: Shamir's Secret Sharing, used for Vault's unseal keys, was invented by Adi Shamir in 1979 -- the same Shamir who is the "S" in RSA encryption. The algorithm uses polynomial interpolation: any k points on a polynomial of degree k-1 can reconstruct it, but k-1 points reveal nothing. This is why Vault's default is 5 shares with a threshold of 3.

# Initialize Vault (first time only — creates master key and root token)
vault operator init -key-shares=5 -key-threshold=3
# Output: 5 unseal keys + 1 root token
# Store these securely and separately — losing them means losing Vault

# Unseal (need 3 of 5 keys)
vault operator unseal <key-1>
vault operator unseal <key-2>
vault operator unseal <key-3>

# Check seal status
vault status
# Sealed: false
# HA Enabled: true

# Seal Vault (emergency — locks everything immediately)
vault operator seal

Auto-unseal (production): Instead of manual unseal keys, delegate to a KMS:

# vault.hcl
seal "awskms" {
  region     = "us-east-1"
  kms_key_id = "alias/vault-unseal-key"
}

With auto-unseal, Vault automatically unseals on restart using the KMS key. No human intervention needed.

2. Secret Engines

Secret engines are plugins that store, generate, or encrypt data. Each is mounted at a path.

# Enable the KV (key-value) secrets engine v2
vault secrets enable -path=secret kv-v2

# Write a secret
vault kv put secret/myapp/database \
  username="dbadmin" \
  password="s3cur3_p@ss" \
  host="db.example.com"

# Read a secret
vault kv get secret/myapp/database
vault kv get -field=password secret/myapp/database

# List secrets at a path
vault kv list secret/myapp/

# Delete a secret (soft delete in v2 — can be undeleted)
vault kv delete secret/myapp/database

# View secret versions
vault kv metadata get secret/myapp/database

# Rollback to a previous version
vault kv rollback -version=2 secret/myapp/database

Dynamic secrets (database example):

# Enable the database secrets engine
vault secrets enable database

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

# Create a role that generates temporary credentials
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"

# Generate temporary credentials
vault read database/creds/readonly
# username: v-token-readonly-abc123
# password: A1b2C3d4E5f6
# lease_duration: 1h
# The credentials are automatically revoked after 1 hour

Interview tip: "Static secrets vs dynamic secrets" is a classic Vault interview question. Static secrets (KV store) are stored and retrieved -- you manage rotation. Dynamic secrets (database engine, AWS engine) are generated on demand with a TTL and auto-revoked. Dynamic secrets are strictly better for databases because a leaked credential expires automatically.

3. Authentication Methods

Auth methods verify identity and map it to Vault policies.

# Enable auth methods
vault auth enable userpass
vault auth enable approle
vault auth enable kubernetes

# Userpass — simple username/password (for humans)
vault write auth/userpass/users/alice \
  password="changeme" \
  policies="developer"

vault login -method=userpass username=alice password=changeme

# AppRole — for machines and CI/CD
vault write auth/approle/role/ci-pipeline \
  token_policies="ci-deploy" \
  token_ttl=1h \
  token_max_ttl=4h \
  secret_id_ttl=10m

# Get role ID (baked into app config)
vault read auth/approle/role/ci-pipeline/role-id

# Generate secret ID (delivered at runtime)
vault write -f auth/approle/role/ci-pipeline/secret-id

# Login with AppRole
vault write auth/approle/login \
  role_id="abc-123" \
  secret_id="def-456"

# Kubernetes auth — pods authenticate with their service account
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc"

vault write auth/kubernetes/role/myapp \
  bound_service_account_names=myapp-sa \
  bound_service_account_namespaces=production \
  policies=myapp-secrets \
  ttl=1h

4. Policies

Policies define what a token can access. They use HCL path-based rules.

# myapp-policy.hcl
# Read-only access to application secrets
path "secret/data/myapp/*" {
  capabilities = ["read", "list"]
}

# Allow generating database credentials
path "database/creds/readonly" {
  capabilities = ["read"]
}

# Deny access to other apps' secrets
path "secret/data/otherapp/*" {
  capabilities = ["deny"]
}

# Allow token self-management
path "auth/token/renew-self" {
  capabilities = ["update"]
}
path "auth/token/lookup-self" {
  capabilities = ["read"]
}
# Write a policy
vault policy write myapp-secrets myapp-policy.hcl

# List policies
vault policy list

# Read a policy
vault policy read myapp-secrets

# Test a policy (create a token with it)
vault token create -policy=myapp-secrets -ttl=1h

5. Vault Agent (Sidecar/Daemon)

Vault Agent handles authentication, token renewal, and secret rendering automatically. Essential for production applications.

# vault-agent.hcl
auto_auth {
  method "kubernetes" {
    mount_path = "auth/kubernetes"
    config = {
      role = "myapp"
    }
  }
  sink "file" {
    config = {
      path = "/tmp/vault-token"
    }
  }
}

template {
  source      = "/etc/vault/templates/database.tpl"
  destination = "/app/config/database.env"
  perms       = 0600
  command     = "systemctl reload myapp"  # run after render
}

vault {
  address = "https://vault.example.com:8200"
}

Template file (Consul Template syntax):

{{ with secret "secret/data/myapp/database" }}
DB_HOST={{ .Data.data.host }}
DB_USER={{ .Data.data.username }}
DB_PASS={{ .Data.data.password }}
{{ end }}

{{ with secret "database/creds/readonly" }}
DB_DYNAMIC_USER={{ .Data.username }}
DB_DYNAMIC_PASS={{ .Data.password }}
{{ end }}

# Run Vault Agent
vault agent -config=vault-agent.hcl

6. PKI Secrets Engine (Certificate Authority)

# Enable PKI engine
vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki  # 10 years

# Generate root CA
vault write pki/root/generate/internal \
  common_name="Example Root CA" \
  ttl=87600h

# Enable intermediate CA
vault secrets enable -path=pki_int pki
vault write pki_int/intermediate/generate/internal \
  common_name="Example Intermediate CA"

# Create a role for issuing certificates
vault write pki_int/roles/web-servers \
  allowed_domains="example.com" \
  allow_subdomains=true \
  max_ttl=720h

# Issue a certificate
vault write pki_int/issue/web-servers \
  common_name="api.example.com" \
  ttl=24h

7. Operational Patterns

# Audit logging (always enable in production)
vault audit enable file file_path=/var/log/vault/audit.log

# Token management
vault token lookup           # current token info
vault token renew            # extend TTL
vault token revoke <token>   # revoke a specific token
vault token revoke -accessor <accessor>  # revoke by accessor

# Lease management
vault lease lookup <lease-id>
vault lease renew <lease-id>
vault lease revoke <lease-id>
vault lease revoke -prefix database/creds/  # revoke all DB creds

# Vault status and health
vault status
curl -s https://vault.example.com:8200/v1/sys/health | jq
# 200 = initialized, unsealed, active
# 429 = unsealed, standby
# 472 = data recovery mode
# 501 = not initialized
# 503 = sealed

> **Remember:** Mnemonic for Vault health codes: **"200 happy, 429 waiting, 501 naked, 503 locked."** 200 = active and ready, 429 = standby (waiting its turn in HA), 501 = not initialized (naked -- no keys yet), 503 = sealed (locked up tight).

# Raft storage (integrated storage) status
vault operator raft list-peers
vault operator raft autopilot state

Quick Reference

# Environment setup
export VAULT_ADDR='https://vault.example.com:8200'
export VAULT_TOKEN='s.abc123'
# Or: vault login -method=userpass username=alice

# Secret CRUD
vault kv put secret/path key=value
vault kv get secret/path
vault kv get -field=key secret/path
vault kv list secret/
vault kv delete secret/path

# Auth
vault login                         # interactive
vault login -method=userpass username=alice
vault token create -policy=mypolicy -ttl=1h

# Status
vault status                        # seal status, version, cluster
vault operator raft list-peers      # raft cluster members
vault audit list                    # active audit devices
vault secrets list                  # mounted secret engines
vault auth list                     # enabled auth methods

Wiki Navigation

Prerequisites