Managing secrets securely is fundamental to modern DevOps practices. HashiCorp Vault provides powerful CLI commands and REST API endpoints for reading and writing secrets. This guide covers all the essential operations you need for day-to-day secrets management.
Prerequisites
Before working with Vault secrets, ensure you have:
- Vault CLI installed and configured
- Authentication token with appropriate permissions
VAULT_ADDRenvironment variable set to your Vault server
# Set environment variable
export VAULT_ADDR='https://vault.example.com:8200'
# Verify connectivity
vault status
# Authenticate (if not already)
vault login
Understanding KV Secrets Engines
Vault's Key-Value (KV) secrets engine comes in two versions:
| Feature | KV v1 | KV v2 |
|---|---|---|
| Versioning | No | Yes |
| Soft delete | No | Yes |
| Metadata | No | Yes |
| Check-and-set | No | Yes |
| CLI commands | vault read/write | vault kv get/put |
Most modern deployments use KV v2 for its versioning capabilities. This guide covers both versions.
Writing Secrets to Vault
Using Vault CLI (KV v2)
Write a secret with the vault kv put command:
# Write a single key-value pair
vault kv put secret/myapp password='mysecretpassword'
# Write multiple key-value pairs
vault kv put secret/myapp \
username='admin' \
password='mysecretpassword' \
api_key='abc123xyz'
Using Vault CLI (KV v1)
For KV v1 secrets engines, use vault write:
vault write secret/myapp password='mysecretpassword'
Writing from Files
Write secrets from a JSON file:
# Create a JSON file with your secrets
cat > secrets.json << 'EOF'
{
"username": "admin",
"password": "mysecretpassword",
"connection_string": "postgres://user:pass@host:5432/db"
}
EOF
# Write from file
vault kv put secret/database @secrets.json
# Clean up the file
rm secrets.json
Writing from Standard Input
For secure handling without files:
# Pipe from environment variable (no command history)
echo -n "$DB_PASSWORD" | vault kv put secret/database password=-
# Generate and store a random password
openssl rand -base64 32 | vault kv put secret/api-key value=-
Using CURL API
For API-based integration:
curl -X POST \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d '{"data": {"username": "admin", "password": "secret"}}' \
$VAULT_ADDR/v1/secret/data/myapp
Note: For KV v2, the API path includes /data/ after the mount point.
Reading Secrets from Vault
Basic Read Operations
# Read entire secret (KV v2)
vault kv get secret/myapp
# Read entire secret (KV v1)
vault read secret/myapp
Example output:
====== Secret Path ======
secret/data/myapp
======= Metadata =======
Key Value
--- -----
created_time 2024-01-15T10:30:00.000000Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
====== Data ======
Key Value
--- -----
password mysecretpassword
username admin
Reading Specific Fields
Extract a single field value:
# Get only the password field
vault kv get -field=password secret/myapp
# Use in scripts
DB_PASS=$(vault kv get -field=password secret/database)
JSON Output for Scripting
Get structured JSON output:
# Full JSON output
vault kv get -format=json secret/myapp
# Parse with jq
vault kv get -format=json secret/myapp | jq -r '.data.data.password'
Using CURL API
# KV v2 API read
curl -s \
-H "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/myapp | jq '.data.data'
Listing Secrets
List secret names at a path (not values):
# List secrets in a path (KV v2)
vault kv list secret/
# List secrets in a path (KV v1)
vault list secret/
# JSON output
vault kv list -format=json secret/
Deleting Secrets
Soft Delete (KV v2)
Soft delete preserves the data but marks it as deleted:
# Soft delete (can be recovered)
vault kv delete secret/myapp
# Delete specific version
vault kv delete -versions=2 secret/myapp
Permanent Deletion (KV v2)
Destroy permanently removes the data:
# Destroy specific versions permanently
vault kv destroy -versions=1,2,3 secret/myapp
# Destroy all versions and metadata
vault kv metadata delete secret/myapp
Delete (KV v1)
KV v1 deletion is always permanent:
vault delete secret/myapp
Response Wrapping for Secure Sharing
Response wrapping provides a secure method to share secrets without granting Vault access to recipients.
Creating a Wrapped Response
# Wrap a secret read with 5-minute TTL
vault kv get -wrap-ttl=5m secret/myapp
# Output includes a wrapping token
Key Value
--- -----
wrapping_token hvs.CAESI...
wrapping_accessor abc123...
wrapping_token_ttl 5m
wrapping_token_creation_time 2024-01-15T10:30:00.000Z
wrapping_token_creation_path secret/data/myapp
Unwrapping the Secret
The recipient uses the one-time token:
vault unwrap hvs.CAESI...
After unwrapping, the token becomes invalid, ensuring one-time access.
Response Wrapping Best Practices
- Use short TTLs (5-15 minutes) for sensitive secrets
- Verify the wrapping token's creation path before unwrapping
- Monitor for wrapped tokens that are never unwrapped (potential security concern)
Working with Secret Metadata (KV v2)
Reading Metadata
vault kv metadata get secret/myapp
Setting Custom Metadata
vault kv metadata put \
-custom-metadata=environment=production \
-custom-metadata=owner=platform-team \
secret/myapp
Configuring Secret Settings
# Set max versions to keep
vault kv metadata put -max-versions=5 secret/myapp
# Require check-and-set
vault kv metadata put -cas-required=true secret/myapp
Best Practices
1. Never Store Secrets in Command History
# BAD: Password visible in history
vault kv put secret/app password=mypassword
# GOOD: Read from environment variable
vault kv put secret/app password="$PASSWORD"
# GOOD: Read from stdin
read -s PASSWORD && vault kv put secret/app password="$PASSWORD"
2. Use Field-Specific Reads in Scripts
# Efficient: Only retrieve what you need
DB_HOST=$(vault kv get -field=host secret/database)
DB_PASS=$(vault kv get -field=password secret/database)
3. Implement Proper Error Handling
#!/bin/bash
if ! SECRET=$(vault kv get -field=api_key secret/myapp 2>/dev/null); then
echo "Error: Failed to retrieve secret" >&2
exit 1
fi
4. Use Response Wrapping for Secret Distribution
Instead of copying secrets directly, wrap them:
# Generate wrapped token for deployment
WRAPPED_TOKEN=$(vault kv get -wrap-ttl=10m -format=json secret/deploy-key | jq -r '.wrap_info.token')
echo "Deploy with: vault unwrap $WRAPPED_TOKEN"
Troubleshooting
Permission Denied
Error: permission denied
Solution: Check your token's policies with vault token lookup and ensure they grant access to the secret path.
Path Not Found
No value found at secret/data/myapp
Solution: Verify the path exists with vault kv list secret/ and check the secrets engine version (v1 vs v2 use different paths).
Invalid Path
Error: Invalid path for a versioned K/V secrets engine
Solution: For KV v2, use vault kv commands instead of vault read/write.
Command Reference
| Operation | KV v2 Command | KV v1 Command |
|---|---|---|
| Write | vault kv put secret/path key=value | vault write secret/path key=value |
| Read | vault kv get secret/path | vault read secret/path |
| List | vault kv list secret/ | vault list secret/ |
| Delete | vault kv delete secret/path | vault delete secret/path |
| Destroy | vault kv destroy -versions=N secret/path | N/A |
Next Steps
- Learn about KV v2 versioning and advanced features
- Configure authentication methods for your team
- Understand token management for secure access
For more advanced secrets management strategies, explore our complete HashiCorp Vault guide series.