PS Product SecurityKnowledge Base

๐Ÿ” Secret Management on HashiCorp Vault

Vault secret management flow

Intro: Secret managers exist because CI variables, repo files, and host-level config files stop scaling safely once environments and teams multiply.

What Vault does

Vault is a secret-management control plane that supports:

  • static secrets through KV engines
  • dynamic secrets on demand
  • transit encryption and signing workflows
  • PKI issuance
  • policy-based access and audit logging

How it works

  1. a client authenticates through token, AppRole, Kubernetes auth, OIDC/JWT, or cloud auth
  2. Vault maps identity to policies
  3. Vault returns a secret, a lease, or performs a crypto action
  4. audit devices record the action
  5. credentials can expire and be revoked

Basic lab install

curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt-get update && sudo apt-get install -y vault
ui = true
listener "tcp" {
  address = "0.0.0.0:8200"
  tls_disable = 1
}
storage "file" {
  path = "/opt/vault/data"
}
api_addr = "http://127.0.0.1:8200"
disable_mlock = true

Basic init and KV v2 use

export VAULT_ADDR='http://127.0.0.1:8200'
vault operator init
vault operator unseal
vault login
vault secrets enable -path=secret kv-v2
vault kv put secret/payments/api username="svc-payments" password="REPLACE_ME"
vault kv patch secret/payments/api password="ROTATED_VALUE"

Kubernetes auth example

vault auth enable kubernetes
vault write auth/kubernetes/config   kubernetes_host="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT"
vault write auth/kubernetes/role/payments-api   bound_service_account_names=payments-api   bound_service_account_namespaces=payments   policies=payments-app   ttl=1h

HA with Raft

storage "raft" {
  path    = "/vault/data"
  node_id = "vault-1"
  retry_join { leader_api_addr = "https://vault-0.internal:8200" }
  retry_join { leader_api_addr = "https://vault-1.internal:8200" }
  retry_join { leader_api_addr = "https://vault-2.internal:8200" }
}
api_addr     = "https://vault-1.internal:8200"
cluster_addr = "https://vault-1.internal:8201"
disable_mlock = true

Common mistakes

  • using dev mode outside local experimentation
  • no TLS in production paths
  • one shared root token for operators
  • no audit devices
  • static secrets only where dynamic leases would help
  • no restore testing for snapshots

Footer