319 lines
7.5 KiB
Markdown
319 lines
7.5 KiB
Markdown
# HashiCorp Vault KV Secret Engine Versions
|
|
|
|
## Overview
|
|
|
|
HashiCorp Vault has two versions of the Key-Value (KV) secret engine:
|
|
- **KV v1**: Simple key-value storage (legacy)
|
|
- **KV v2**: Versioned secrets with metadata (recommended)
|
|
|
|
This application supports both versions and automatically handles the path differences.
|
|
|
|
## Key Differences
|
|
|
|
| Feature | KV v1 | KV v2 |
|
|
|---------|-------|-------|
|
|
| **Versioning** | ❌ No | ✅ Yes |
|
|
| **Metadata** | ❌ No | ✅ Yes |
|
|
| **Soft Delete** | ❌ No | ✅ Yes |
|
|
| **Path Structure** | Simple | Nested (data/metadata) |
|
|
| **API Calls** | Direct | Through /data or /metadata |
|
|
| **Rollback** | ❌ No | ✅ Yes |
|
|
| **Check-and-Set** | ❌ No | ✅ Yes |
|
|
|
|
## Path Structures
|
|
|
|
### KV v1
|
|
```
|
|
secret/myapp/database
|
|
secret/myapp/api-keys
|
|
secret/team/config
|
|
```
|
|
|
|
Simple, direct paths. What you see is what you get.
|
|
|
|
### KV v2
|
|
```
|
|
secret/data/myapp/database # For reading/writing secrets
|
|
secret/metadata/myapp/database # For metadata operations
|
|
```
|
|
|
|
KV v2 uses special path prefixes:
|
|
- `/data/` for reading and writing secret data
|
|
- `/metadata/` for listing and metadata operations
|
|
|
|
## How Our Client Handles This
|
|
|
|
### Automatic Path Transformation
|
|
|
|
Our `VaultClient` automatically transforms paths based on the KV version:
|
|
|
|
```typescript
|
|
// You write this:
|
|
await client.read('secret/myapp/database');
|
|
|
|
// KV v1: Uses path as-is
|
|
// → GET /v1/secret/myapp/database
|
|
|
|
// KV v2: Automatically adds /data/
|
|
// → GET /v1/secret/data/myapp/database
|
|
```
|
|
|
|
### List Operations
|
|
|
|
**KV v1:**
|
|
```typescript
|
|
await client.list('secret/myapp/');
|
|
// → LIST /v1/secret/myapp/?list=true
|
|
```
|
|
|
|
**KV v2:**
|
|
```typescript
|
|
await client.list('secret/myapp/');
|
|
// → LIST /v1/secret/metadata/myapp/?list=true
|
|
// (uses /metadata/ for listing)
|
|
```
|
|
|
|
### Read Operations
|
|
|
|
**KV v1:**
|
|
```typescript
|
|
const data = await client.read('secret/myapp/config');
|
|
// Returns: { username: '...', password: '...' }
|
|
```
|
|
|
|
**KV v2:**
|
|
```typescript
|
|
const data = await client.read('secret/myapp/config');
|
|
// Automatically unwraps: data.data.data → { username: '...', password: '...' }
|
|
```
|
|
|
|
### Write Operations
|
|
|
|
**KV v1:**
|
|
```typescript
|
|
await client.write('secret/myapp/config', {
|
|
username: 'admin',
|
|
password: 'secret'
|
|
});
|
|
// POST /v1/secret/myapp/config
|
|
// Body: { username: '...', password: '...' }
|
|
```
|
|
|
|
**KV v2:**
|
|
```typescript
|
|
await client.write('secret/myapp/config', {
|
|
username: 'admin',
|
|
password: 'secret'
|
|
});
|
|
// POST /v1/secret/data/myapp/config
|
|
// Body: { data: { username: '...', password: '...' } }
|
|
// (wrapped in data object)
|
|
```
|
|
|
|
## Configuring KV Version
|
|
|
|
### In the UI
|
|
|
|
When adding a Vault server:
|
|
|
|
1. Click "Add Server"
|
|
2. Fill in the server details
|
|
3. Select **KV Secret Engine Version**:
|
|
- **KV v2 (recommended)** - Default, most common
|
|
- **KV v1 (legacy)** - For older Vault installations
|
|
|
|
### Detecting KV Version
|
|
|
|
If you're unsure which version your Vault uses:
|
|
|
|
```bash
|
|
# Check your Vault server
|
|
vault secrets list -detailed
|
|
|
|
# Look for the "Options" column
|
|
# version=2 means KV v2
|
|
# No version or version=1 means KV v1
|
|
```
|
|
|
|
Or use the API:
|
|
```bash
|
|
curl -H "X-Vault-Token: $VAULT_TOKEN" \
|
|
$VAULT_ADDR/v1/sys/internal/ui/mounts/secret
|
|
```
|
|
|
|
Look for `"options": {"version": "2"}` in the response.
|
|
|
|
## When to Use KV v1 vs KV v2
|
|
|
|
### Use KV v2 (Recommended) When:
|
|
- ✅ Starting a new Vault installation
|
|
- ✅ You need versioning and rollback
|
|
- ✅ You want soft delete (undelete capability)
|
|
- ✅ You need to track secret history
|
|
- ✅ You want check-and-set operations
|
|
|
|
### Use KV v1 Only When:
|
|
- Legacy Vault installation that can't be upgraded
|
|
- Specific requirement to not version secrets
|
|
- Very simple use case without versioning needs
|
|
|
|
## Example Workflows
|
|
|
|
### Reading a Secret
|
|
|
|
```typescript
|
|
// Same code works for both versions!
|
|
const data = await client.read('secret/myapp/database');
|
|
console.log(data.username);
|
|
console.log(data.password);
|
|
```
|
|
|
|
### Listing Secrets
|
|
|
|
```typescript
|
|
// Same code works for both versions!
|
|
const keys = await client.list('secret/myapp/');
|
|
console.log(keys);
|
|
// ['config', 'database/', 'api-keys/']
|
|
```
|
|
|
|
### Searching Recursively
|
|
|
|
```typescript
|
|
// Uses list() internally, works with both versions
|
|
const results = await vaultApi.searchPaths(
|
|
server,
|
|
credentials,
|
|
'secret/',
|
|
'database'
|
|
);
|
|
```
|
|
|
|
## KV v2 Specific Features
|
|
|
|
### Metadata Operations
|
|
|
|
```typescript
|
|
// Only available in KV v2
|
|
const metadata = await client.readMetadata('secret/myapp/database');
|
|
|
|
console.log(metadata.current_version); // 5
|
|
console.log(metadata.versions);
|
|
// {
|
|
// "1": { created_time: "...", destroyed: false },
|
|
// "2": { created_time: "...", destroyed: false },
|
|
// "3": { created_time: "...", destroyed: true },
|
|
// "4": { created_time: "...", destroyed: false },
|
|
// "5": { created_time: "...", destroyed: false }
|
|
// }
|
|
```
|
|
|
|
### Version History
|
|
|
|
KV v2 keeps track of all versions:
|
|
- Can read previous versions
|
|
- Can undelete soft-deleted secrets
|
|
- Can permanently destroy specific versions
|
|
- Can destroy all versions
|
|
|
|
### Soft Delete vs Hard Delete
|
|
|
|
**Soft Delete (KV v2):**
|
|
```typescript
|
|
await client.delete('secret/myapp/database');
|
|
// Secret is "deleted" but can be undeleted
|
|
// Metadata still exists
|
|
```
|
|
|
|
**Hard Delete (KV v1):**
|
|
```typescript
|
|
await client.delete('secret/myapp/database');
|
|
// Secret is permanently gone
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Error: "no handler for route"
|
|
|
|
```
|
|
Error: Vault API error: no handler for route 'secret/data/...'
|
|
```
|
|
|
|
**Cause**: Your Vault is using KV v1, but the client is configured for KV v2.
|
|
|
|
**Solution**: Edit the server configuration and change KV version to v1.
|
|
|
|
### Error: "1 error occurred: * permission denied"
|
|
|
|
This can happen if:
|
|
1. You don't have permission to the path
|
|
2. You're using the wrong KV version (v1 paths on v2 or vice versa)
|
|
|
|
**Solution**:
|
|
- Check your Vault policies
|
|
- Verify the KV version in server settings
|
|
|
|
### Paths Look Wrong
|
|
|
|
If you see paths like `secret/data/data/myapp`:
|
|
|
|
**Cause**: You're manually adding `/data/` when the client already does it for KV v2.
|
|
|
|
**Solution**: Use simple paths like `secret/myapp`. The client adds `/data/` or `/metadata/` automatically.
|
|
|
|
## Migration: KV v1 → KV v2
|
|
|
|
If you're migrating from KV v1 to KV v2:
|
|
|
|
1. **Backup all secrets** from KV v1
|
|
2. **Enable KV v2** on a new mount point
|
|
3. **Migrate secrets** to new paths
|
|
4. **Update application** to use new KV version
|
|
5. **Test thoroughly**
|
|
6. **Switch traffic** to new mount
|
|
|
|
In this GUI:
|
|
1. Add a new server entry with the same URL
|
|
2. Set KV version to v2
|
|
3. Use new mount path (e.g., `secretv2/` instead of `secret/`)
|
|
|
|
## Best Practices
|
|
|
|
1. **Use KV v2 for new installations**
|
|
2. **Configure correct version when adding servers**
|
|
3. **Don't mix KV v1 and v2 mount points** on same server without proper labeling
|
|
4. **Use simple paths** - let the client handle /data/ and /metadata/ prefixes
|
|
5. **Document which mounts use which version** for your team
|
|
|
|
## API Reference
|
|
|
|
All examples work transparently with both versions:
|
|
|
|
```typescript
|
|
// List
|
|
const keys = await client.list('secret/myapp/');
|
|
|
|
// Read
|
|
const data = await client.read('secret/myapp/config');
|
|
|
|
// Write
|
|
await client.write('secret/myapp/config', { key: 'value' });
|
|
|
|
// Delete
|
|
await client.delete('secret/myapp/config');
|
|
|
|
// Metadata (KV v2 only)
|
|
const meta = await client.readMetadata('secret/myapp/config');
|
|
```
|
|
|
|
## Summary
|
|
|
|
✅ **Both versions are fully supported**
|
|
✅ **Paths are automatically transformed**
|
|
✅ **Select correct version when adding server**
|
|
✅ **Use KV v2 for new installations**
|
|
✅ **Code is the same for both versions**
|
|
|
|
The client handles all the complexity, so you can focus on managing your secrets!
|
|
|