02. HashiCorp Vault Deep Dive
[!IMPORTANT] HashiCorp Vault is the industry-standard platform for secrets management, identity-based access control, and encryption as a service. It operates under a strict default-deny security posture: all paths are denied unless explicitly permitted by an attached access control list (ACL) policy.
1. Vault Architecture & Cryptographic Mechanics
Vault acts as a secure cryptographic barrier between untrusted storage backends and consuming workloads.
Key Architectural Components
- Storage Backend: Untrusted persistence layer (Raft Integrated Storage, Consul, PostgreSQL). Vault encrypts all data before sending it to the storage backend using AES-256-GCM. The storage backend never sees plaintext secrets.
- Cryptographic Barrier: The encryption boundary protecting Vault's internal state. Data passing through the barrier is decrypted only in volatile RAM.
- Barrier Unsealing & Key Management:
- Shamir's Secret Sharing: By default, Vault generates a Master Key split into
Nkey shares. A quorum ofKshares (e.g., 3 of 5) is required to reconstruct the Master Key and unseal the vault. - Cloud KMS Auto-Unseal: Production clusters use AWS KMS, GCP KMS, or Azure Key Vault to automatically unseal Vault without human intervention upon process restart.
- Shamir's Secret Sharing: By default, Vault generates a Master Key split into
- Transit Engine (Encryption as a Service): Handles encryption, decryption, signing, and hashing in-flight without exposing the underlying cryptographic keys to consuming applications (Envelope Encryption pattern).
2. Machine-to-Machine Authentication Methods
Applications must prove their identity before Vault issues an access token.
AppRole Authentication (CI/CD & Microservices)
AppRole models human username/password credentials into machine identities using a RoleID (static application identifier) and a SecretID (ephemeral credential acting as a password).
Provisioning an AppRole via Vault CLI
# 1. Enable AppRole auth backend
vault auth enable approle
# 2. Define application ACL policy (HCL)
vault policy write payment-service-policy - <<EOF
path "secret/data/production/payment/*" {
capabilities = ["read"]
}
path "database/creds/payment-role" {
capabilities = ["read"]
}
EOF
# 3. Create the AppRole with strict security parameters
vault write auth/approle/role/payment-service \
secret_id_ttl=10m \
token_num_uses=10 \
token_ttl=1h \
token_max_ttl=4h \
secret_id_num_uses=1 \
policies="payment-service-policy"
# 4. Fetch the static RoleID
vault read auth/approle/role/payment-service/role-id
# 5. Generate a response-wrapped SecretID (valid for 60 seconds)
vault write -wrap-ttl=60s -f auth/approle/role/payment-service/secret-id
3. Secrets Engines Deep Dive
Key/Value (KV) v2 Engine
KV v2 provides key-value storage with automatic versioning, rollback capabilities, and soft/hard deletion protection.
# Enable KV v2 engine at mount path 'secret/'
vault secrets enable -path=secret kv-v2
# Write version 1 of a secret
vault kv put secret/production/payment/config \
api_key="sk_live_9988776655443322" \
webhook_secret="whsec_abc123xyz"
# Read latest version of the secret
vault kv get secret/production/payment/config
# Read specific version (v1)
vault kv get -version=1 secret/production/payment/config
# Soft delete version 1 (can be restored)
vault kv delete -versions=1 secret/production/payment/config
# Permanently destroy version 1 metadata and key material
vault kv destroy -versions=1 secret/production/payment/config
Dynamic Secrets (Database Engine)
Instead of statically configuring database passwords, Vault generates short-lived, unique PostgreSQL users on demand.
# 1. Enable Database secrets engine
vault secrets enable database
# 2. Configure connection to target PostgreSQL database
vault write database/config/prod-postgres \
plugin_name=postgresql-database-plugin \
allowed_roles="payment-db-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres.internal:5432/payment_db?sslmode=verify-full" \
username="vault_admin" \
password="SuperSecureAdminPassword123!"
# 3. Define dynamic creation statements with 1-hour TTL
vault write database/roles/payment-db-role \
db_name=prod-postgres \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
# 4. App requests dynamic database credentials
vault read database/creds/payment-db-role
# Returns:
# username: v-approle-payment--a87f9b...
# password: A1a!8f...
Transit Engine (Encryption as a Service)
Perform in-memory envelope encryption without managing raw private keys in application processes.
# Enable transit secrets engine
vault secrets enable transit
# Create a named symmetric encryption key (AES-256-GCM)
vault write -f transit/keys/payment-card-key
# Encrypt plaintext payload ("4111111111111111" base64-encoded)
echo -n "4111111111111111" | base64 | vault write transit/encrypt/payment-card-key plaintext=-
# Output: ciphertext = vault:v1:8Fk2...
# Decrypt ciphertext payload
vault write transit/decrypt/payment-card-key ciphertext="vault:v1:8Fk2..."
4. Production Vault Configuration (vault.hcl)
Production Vault deployments require Raft Integrated Storage, TLS 1.3 listener hardening, KMS auto-unseal, and audit logging.
# /etc/vault.d/vault.hcl
ui = true
disable_mlock = false
storage "raft" {
path = "/var/lib/vault/data"
node_id = "vault-node-1"
retry_join {
leader_api_addr = "https://vault-node-2.internal:8200"
}
retry_join {
leader_api_addr = "https://vault-node-3.internal:8200"
}
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault.d/certs/vault.crt"
tls_key_file = "/etc/vault.d/certs/vault.key"
tls_min_version = "tls12"
tls_cipher_suites = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
}
# AWS KMS Auto-Unseal Configuration
seal "awskms" {
region = "us-east-1"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abc-123-def-456"
}
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
api_addr = "https://vault-node-1.internal:8200"
cluster_addr = "https://vault-node-1.internal:8201"
5. Multi-Language SDK Implementations
Python (hvac)
import os
import hvac
def get_vault_secret():
vault_url = os.getenv("VAULT_ADDR", "https://vault.internal:8200")
role_id = os.getenv("VAULT_ROLE_ID")
secret_id = os.getenv("VAULT_SECRET_ID")
# Initialize client with TLS verification
client = hvac.Client(url=vault_url, verify=True)
# Authenticate via AppRole
login_response = client.auth.approle.login(
role_id=role_id,
secret_id=secret_id
)
# Read KV v2 secret
secret_response = client.secrets.kv.v2.read_secret_version(
path="production/payment/config",
mount_point="secret"
)
api_key = secret_response["data"]["data"]["api_key"]
return api_key
if __name__ == "__main__":
key = get_vault_secret()
print("Successfully retrieved secret from Vault!")
Node.js (node-vault)
const vault = require('node-vault');
const process = require('process');
async function fetchSecret() {
const client = vault({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR || 'https://vault.internal:8200',
});
try {
// AppRole Login
const roleId = process.env.VAULT_ROLE_ID;
const secretId = process.env.VAULT_SECRET_ID;
const authResult = await client.approleLogin({
role_id: roleId,
secret_id: secretId,
});
client.token = authResult.auth.client_token;
// Read KV v2 Secret
const secret = await client.read('secret/data/production/payment/config');
const apiKey = secret.data.data.api_key;
console.log('Secret retrieved successfully.');
return apiKey;
} catch (error) {
console.error('Vault API error:', error.message);
throw error;
}
}
fetchSecret();
Go (hashicorp/vault/api)
package main
import (
"context"
"fmt"
"log"
"os"
vault "github.com/hashicorp/vault/api"
)
func main() {
config := vault.DefaultConfig()
config.Address = os.Getenv("VAULT_ADDR")
client, err := vault.NewClient(config)
if err != nil {
log.Fatalf("Failed to create Vault client: %v", err)
}
// AppRole Login Payload
roleID := os.Getenv("VAULT_ROLE_ID")
secretID := os.Getenv("VAULT_SECRET_ID")
data := map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
}
resp, err := client.Logical().Write("auth/approle/login", data)
if err != nil || resp == nil {
log.Fatalf("AppRole login failed: %v", err)
}
client.SetToken(resp.Auth.ClientToken)
// Read KV v2 secret path
secret, err := client.KVv2("secret").Get(context.Background(), "production/payment/config")
if err != nil {
log.Fatalf("Failed to read secret: %v", err)
}
apiKey := secret.Data["api_key"].(string)
fmt.Printf("Successfully fetched API Key: %s...\n", apiKey[:4])
}
Java (Spring Vault / Native Client)
package com.appsec.vault;
import org.springframework.vault.authentication.AppRoleAuthentication;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.core.VaultTemplate;
import org.springframework.vault.support.VaultResponseSupport;
import java.net.URI;
import java.util.Map;
public class VaultSecretManager {
public static class SecretConfig {
private Map<String, String> data;
public Map<String, String> getData() { return data; }
public void setData(Map<String, String> data) { this.data = data; }
}
public static void main(String[] args) {
String vaultAddr = System.getenv("VAULT_ADDR");
String roleId = System.getenv("VAULT_ROLE_ID");
String secretId = System.getenv("VAULT_SECRET_ID");
VaultEndpoint endpoint = VaultEndpoint.from(URI.create(vaultAddr));
AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
.roleId(AppRoleAuthenticationOptions.RoleId.provided(roleId))
.secretId(AppRoleAuthenticationOptions.SecretId.provided(secretId))
.build();
VaultTemplate vaultTemplate = new VaultTemplate(endpoint, new AppRoleAuthentication(options, null));
VaultResponseSupport<SecretConfig> response = vaultTemplate.read(
"secret/data/production/payment/config", SecretConfig.class);
if (response != null && response.getData() != null) {
String apiKey = response.getData().getData().get("api_key");
System.out.println("Java SDK successfully retrieved Vault Secret!");
}
}
}
[!NEXT] Move to Chapter 03: Cloud Native Secrets Managers to evaluate AWS Secrets Manager, GCP Secret Manager, and Azure Key Vault.