browser-vault-gui/KV_VERSIONS.md
2025-10-20 18:45:52 +02:00

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!