- Add `show-arguments` recipe documenting all version update commands - Add `complete-update-interactive` recipe for manual confirmations - Maintain `complete-update` as automatic mode (no prompts) - Update `update-help` to reference new recipes and modes - Document 7-step workflow and step-by-step differences Changes: - complete-update: Automatic mode (recommended for CI/CD) - complete-update-interactive: Interactive mode (with confirmations) - show-arguments: Complete documentation of all commands and modes - Both modes share same 7-step workflow with different behavior in Step 4
507 lines
11 KiB
Markdown
507 lines
11 KiB
Markdown
# nu_plugin_kms - Verification and Testing Guide
|
|
|
|
**Date**: 2025-10-08
|
|
**Status**: Ready for Testing
|
|
|
|
## Quick Verification
|
|
|
|
### 1. Verify Binary Exists
|
|
|
|
```bash
|
|
cd /Users/Akasha/project-provisioning/provisioning/core/plugins/nushell-plugins/nu_plugin_kms
|
|
|
|
# Check binary exists
|
|
ls -lh target/release/nu_plugin_kms
|
|
|
|
# Expected output:
|
|
# -rwxr-xr-x 1 user staff X.XM Oct 8 XX:XX target/release/nu_plugin_kms
|
|
```
|
|
|
|
### 2. Register Plugin with Nushell
|
|
|
|
```bash
|
|
# Register plugin
|
|
nu -c "plugin add target/release/nu_plugin_kms"
|
|
|
|
# Verify registration
|
|
nu -c "plugin list | where name =~ kms"
|
|
|
|
# Expected output shows commands:
|
|
# - kms encrypt
|
|
# - kms decrypt
|
|
# - kms generate-key
|
|
# - kms status
|
|
```
|
|
|
|
### 3. Test Plugin Help
|
|
|
|
```bash
|
|
# Check command help
|
|
nu -c "kms encrypt --help"
|
|
nu -c "kms decrypt --help"
|
|
nu -c "kms generate-key --help"
|
|
nu -c "kms status --help"
|
|
```
|
|
|
|
## Backend Testing
|
|
|
|
### Age Backend (Simplest - No External Dependencies)
|
|
|
|
#### Setup
|
|
|
|
```bash
|
|
# Install Age (if not already installed)
|
|
brew install age # macOS
|
|
# or
|
|
apt install age # Ubuntu/Debian
|
|
|
|
# Generate Age key
|
|
age-keygen -o ~/.age/test-key.txt
|
|
|
|
# Extract public key
|
|
export AGE_RECIPIENT=$(grep "public key:" ~/.age/test-key.txt | cut -d: -f2 | xargs)
|
|
export AGE_IDENTITY="$HOME/.age/test-key.txt"
|
|
|
|
echo "Age Recipient: $AGE_RECIPIENT"
|
|
echo "Age Identity: $AGE_IDENTITY"
|
|
```
|
|
|
|
#### Test Encryption
|
|
|
|
```bash
|
|
# Test 1: Encrypt with Age
|
|
nu -c "kms encrypt 'Hello, Age!' --backend age --key $AGE_RECIPIENT" > /tmp/encrypted.txt
|
|
|
|
cat /tmp/encrypted.txt
|
|
# Expected: -----BEGIN AGE ENCRYPTED FILE-----
|
|
# base64data...
|
|
# -----END AGE ENCRYPTED FILE-----
|
|
```
|
|
|
|
#### Test Decryption
|
|
|
|
```bash
|
|
# Test 2: Decrypt with Age
|
|
nu -c "kms decrypt '$(cat /tmp/encrypted.txt)' --backend age --key $AGE_IDENTITY"
|
|
|
|
# Expected output: Hello, Age!
|
|
```
|
|
|
|
#### Test Key Generation
|
|
|
|
```bash
|
|
# Test 3: Generate new Age key pair
|
|
nu -c "kms generate-key --backend age"
|
|
|
|
# Expected output (record):
|
|
# {
|
|
# plaintext: "AGE-SECRET-KEY-...",
|
|
# ciphertext: "age1..."
|
|
# }
|
|
```
|
|
|
|
#### Test Auto-Detection
|
|
|
|
```bash
|
|
# Test 4: Auto-detect Age backend
|
|
nu -c "kms status"
|
|
|
|
# Expected output:
|
|
# {
|
|
# backend: "age",
|
|
# available: true,
|
|
# config: "recipient: age1..., identity: set"
|
|
# }
|
|
```
|
|
|
|
### RustyVault Backend
|
|
|
|
#### Setup
|
|
|
|
```bash
|
|
# Start RustyVault in Docker
|
|
docker run -d --name rustyvault \
|
|
-p 8200:8200 \
|
|
-e VAULT_DEV_ROOT_TOKEN_ID=test-token \
|
|
tongsuo/rustyvault:latest
|
|
|
|
# Wait for startup
|
|
sleep 5
|
|
|
|
# Set environment
|
|
export RUSTYVAULT_ADDR="http://localhost:8200"
|
|
export RUSTYVAULT_TOKEN="test-token"
|
|
|
|
# Enable Transit engine
|
|
docker exec rustyvault \
|
|
vault secrets enable transit
|
|
|
|
# Create encryption key
|
|
docker exec rustyvault \
|
|
vault write -f transit/keys/provisioning-main
|
|
```
|
|
|
|
#### Test Encryption
|
|
|
|
```bash
|
|
# Test 1: Encrypt with RustyVault
|
|
nu -c "kms encrypt 'Secret data!' --backend rustyvault --key provisioning-main"
|
|
|
|
# Expected output: vault:v1:XXXXXXXXXX...
|
|
```
|
|
|
|
#### Test Decryption
|
|
|
|
```bash
|
|
# Test 2: Encrypt and then decrypt
|
|
ENCRYPTED=$(nu -c "kms encrypt 'RustyVault test' --backend rustyvault --key provisioning-main")
|
|
|
|
nu -c "kms decrypt '$ENCRYPTED' --backend rustyvault --key provisioning-main"
|
|
|
|
# Expected output: RustyVault test
|
|
```
|
|
|
|
#### Test Key Generation
|
|
|
|
```bash
|
|
# Test 3: Generate AES256 data key
|
|
nu -c "kms generate-key --backend rustyvault --spec AES256"
|
|
|
|
# Expected output (record):
|
|
# {
|
|
# plaintext: "base64-encoded-key",
|
|
# ciphertext: "vault:v1:..."
|
|
# }
|
|
```
|
|
|
|
#### Test Status
|
|
|
|
```bash
|
|
# Test 4: Check RustyVault status
|
|
nu -c "kms status"
|
|
|
|
# Expected output:
|
|
# {
|
|
# backend: "rustyvault",
|
|
# available: true,
|
|
# config: "addr: http://localhost:8200"
|
|
# }
|
|
```
|
|
|
|
#### Cleanup
|
|
|
|
```bash
|
|
# Stop and remove RustyVault container
|
|
docker stop rustyvault
|
|
docker rm rustyvault
|
|
```
|
|
|
|
### HTTP Fallback Backend
|
|
|
|
#### Setup (Mock Server)
|
|
|
|
```bash
|
|
# Create simple mock KMS server with Python
|
|
cat > /tmp/mock_kms_server.py << 'EOF'
|
|
#!/usr/bin/env python3
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
import json
|
|
import base64
|
|
|
|
class KMSHandler(BaseHTTPRequestHandler):
|
|
def do_POST(self):
|
|
content_length = int(self.headers['Content-Length'])
|
|
body = self.rfile.read(content_length)
|
|
data = json.loads(body)
|
|
|
|
if self.path == '/api/v1/kms/encrypt':
|
|
# Simple XOR "encryption"
|
|
plaintext = base64.b64decode(data['plaintext'])
|
|
ciphertext = base64.b64encode(bytes(b ^ 0x42 for b in plaintext)).decode()
|
|
response = {'ciphertext': ciphertext}
|
|
|
|
elif self.path == '/api/v1/kms/decrypt':
|
|
# Simple XOR "decryption"
|
|
ciphertext = base64.b64decode(data['ciphertext'])
|
|
plaintext = base64.b64encode(bytes(b ^ 0x42 for b in ciphertext)).decode()
|
|
response = {'plaintext': plaintext}
|
|
|
|
elif self.path == '/api/v1/kms/generate-data-key':
|
|
# Generate random key
|
|
import secrets
|
|
key = secrets.token_bytes(32)
|
|
plaintext = base64.b64encode(key).decode()
|
|
ciphertext = base64.b64encode(bytes(b ^ 0x42 for b in key)).decode()
|
|
response = {'plaintext': plaintext, 'ciphertext': ciphertext}
|
|
else:
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
return
|
|
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.end_headers()
|
|
self.wfile.write(json.dumps(response).encode())
|
|
|
|
if __name__ == '__main__':
|
|
server = HTTPServer(('localhost', 8081), KMSHandler)
|
|
print('Mock KMS server running on http://localhost:8081')
|
|
server.serve_forever()
|
|
EOF
|
|
|
|
chmod +x /tmp/mock_kms_server.py
|
|
|
|
# Start mock server in background
|
|
python3 /tmp/mock_kms_server.py &
|
|
MOCK_SERVER_PID=$!
|
|
|
|
# Set environment
|
|
export KMS_HTTP_URL="http://localhost:8081"
|
|
export KMS_HTTP_BACKEND="mock"
|
|
```
|
|
|
|
#### Test HTTP Backend
|
|
|
|
```bash
|
|
# Test 1: Encrypt
|
|
nu -c "kms encrypt 'HTTP test data' --backend mock"
|
|
|
|
# Test 2: Encrypt and decrypt
|
|
ENCRYPTED=$(nu -c "kms encrypt 'Round trip test' --backend mock")
|
|
nu -c "kms decrypt '$ENCRYPTED' --backend mock"
|
|
|
|
# Test 3: Generate key
|
|
nu -c "kms generate-key --backend mock --spec AES256"
|
|
|
|
# Test 4: Status
|
|
nu -c "kms status"
|
|
```
|
|
|
|
#### Cleanup
|
|
|
|
```bash
|
|
# Stop mock server
|
|
kill $MOCK_SERVER_PID
|
|
```
|
|
|
|
## Integration Tests
|
|
|
|
### Test Auto-Detection Priority
|
|
|
|
```bash
|
|
# Test 1: RustyVault has priority
|
|
export RUSTYVAULT_ADDR="http://localhost:8200"
|
|
export RUSTYVAULT_TOKEN="test-token"
|
|
export AGE_RECIPIENT="age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
|
|
|
|
nu -c "kms status"
|
|
# Expected: backend = "rustyvault"
|
|
|
|
# Test 2: Age when RustyVault not set
|
|
unset RUSTYVAULT_ADDR
|
|
unset RUSTYVAULT_TOKEN
|
|
|
|
nu -c "kms status"
|
|
# Expected: backend = "age"
|
|
|
|
# Test 3: HTTP fallback when nothing set
|
|
unset AGE_RECIPIENT
|
|
|
|
nu -c "kms status"
|
|
# Expected: backend = "cosmian" (or configured HTTP backend)
|
|
```
|
|
|
|
### Test Error Handling
|
|
|
|
```bash
|
|
# Test 1: Missing required flags
|
|
nu -c "kms encrypt 'data' --backend age"
|
|
# Expected: Error about missing --key recipient
|
|
|
|
# Test 2: Invalid recipient format
|
|
nu -c "kms encrypt 'data' --backend age --key invalid"
|
|
# Expected: Error about invalid Age recipient format
|
|
|
|
# Test 3: Missing environment variable
|
|
unset RUSTYVAULT_TOKEN
|
|
nu -c "kms encrypt 'data' --backend rustyvault"
|
|
# Expected: Error about RUSTYVAULT_TOKEN not set
|
|
|
|
# Test 4: Invalid key spec
|
|
nu -c "kms generate-key --backend rustyvault --spec INVALID"
|
|
# Expected: Error about invalid key spec
|
|
```
|
|
|
|
### Test Binary Data
|
|
|
|
```bash
|
|
# Test handling of non-UTF8 data
|
|
dd if=/dev/urandom bs=1024 count=1 | base64 > /tmp/random.b64
|
|
|
|
# Encrypt binary data
|
|
ENCRYPTED=$(cat /tmp/random.b64 | nu -c "kms encrypt $in --backend age --key $AGE_RECIPIENT")
|
|
|
|
# Decrypt and compare
|
|
DECRYPTED=$(nu -c "kms decrypt '$ENCRYPTED' --backend age --key $AGE_IDENTITY")
|
|
|
|
# Verify round-trip
|
|
if [ "$(cat /tmp/random.b64)" = "$DECRYPTED" ]; then
|
|
echo "✅ Binary data round-trip successful"
|
|
else
|
|
echo "❌ Binary data round-trip failed"
|
|
fi
|
|
```
|
|
|
|
## Performance Benchmarks
|
|
|
|
### Age Performance
|
|
|
|
```bash
|
|
# Benchmark encryption (1000 iterations)
|
|
time for i in {1..1000}; do
|
|
nu -c "kms encrypt 'test data' --backend age --key $AGE_RECIPIENT" > /dev/null
|
|
done
|
|
|
|
# Expected: ~10-20ms per operation
|
|
```
|
|
|
|
### RustyVault Performance
|
|
|
|
```bash
|
|
# Benchmark encryption (1000 iterations)
|
|
time for i in {1..1000}; do
|
|
nu -c "kms encrypt 'test data' --backend rustyvault --key provisioning-main" > /dev/null
|
|
done
|
|
|
|
# Expected: ~20-50ms per operation (includes HTTP overhead)
|
|
```
|
|
|
|
### Memory Usage
|
|
|
|
```bash
|
|
# Monitor memory during operations
|
|
while true; do
|
|
nu -c "kms encrypt 'test data' --backend age --key $AGE_RECIPIENT" > /dev/null
|
|
ps aux | grep nu_plugin_kms | grep -v grep
|
|
sleep 1
|
|
done
|
|
|
|
# Expected: Stable memory usage, no leaks
|
|
```
|
|
|
|
## Verification Checklist
|
|
|
|
### Compilation
|
|
- [x] `cargo check` passes
|
|
- [x] `cargo build --release` succeeds
|
|
- [x] Binary created in `target/release/nu_plugin_kms`
|
|
- [x] File size reasonable (< 50MB)
|
|
|
|
### Plugin Registration
|
|
- [ ] Plugin registers with Nushell
|
|
- [ ] All 4 commands visible in `plugin list`
|
|
- [ ] Help text accessible for each command
|
|
|
|
### Age Backend
|
|
- [ ] Encryption works with recipient
|
|
- [ ] Decryption works with identity file
|
|
- [ ] Key generation produces valid key pair
|
|
- [ ] Status shows correct backend
|
|
- [ ] Auto-detection works when env vars set
|
|
|
|
### RustyVault Backend
|
|
- [ ] Encryption works with Transit engine
|
|
- [ ] Decryption works correctly
|
|
- [ ] Data key generation works
|
|
- [ ] Status shows correct backend
|
|
- [ ] Auto-detection works when env vars set
|
|
|
|
### HTTP Fallback
|
|
- [ ] Encryption works with HTTP service
|
|
- [ ] Decryption works correctly
|
|
- [ ] Data key generation works
|
|
- [ ] Status shows correct backend
|
|
- [ ] Auto-detection works as fallback
|
|
|
|
### Error Handling
|
|
- [ ] Missing flags produce clear errors
|
|
- [ ] Invalid inputs rejected gracefully
|
|
- [ ] Network errors handled properly
|
|
- [ ] Missing env vars reported clearly
|
|
|
|
### Integration
|
|
- [ ] Auto-detection priority correct
|
|
- [ ] Multiple backends can coexist
|
|
- [ ] Environment switching works
|
|
- [ ] Binary data handled correctly
|
|
|
|
## Success Criteria
|
|
|
|
✅ **Basic Functionality**
|
|
- All backends encrypt and decrypt successfully
|
|
- Key generation works for all backends
|
|
- Status command reports correctly
|
|
|
|
✅ **Robustness**
|
|
- Error messages are clear and actionable
|
|
- No panics or crashes
|
|
- Memory usage is stable
|
|
|
|
✅ **Performance**
|
|
- Operations complete in reasonable time
|
|
- No memory leaks
|
|
- Concurrent operations work
|
|
|
|
✅ **Usability**
|
|
- Auto-detection works as expected
|
|
- Environment configuration is straightforward
|
|
- Help text is clear
|
|
|
|
## Troubleshooting
|
|
|
|
### Plugin Not Loading
|
|
|
|
```bash
|
|
# Check plugin is registered
|
|
nu -c "plugin list" | grep kms
|
|
|
|
# If not registered, add it
|
|
nu -c "plugin add /path/to/nu_plugin_kms"
|
|
|
|
# Check for errors
|
|
nu -c "plugin list --version"
|
|
```
|
|
|
|
### Environment Variables Not Working
|
|
|
|
```bash
|
|
# Check env vars are set
|
|
env | grep -E '(RUSTYVAULT|AGE|KMS)'
|
|
|
|
# Test in new shell
|
|
bash -c 'export AGE_RECIPIENT=...; nu -c "kms status"'
|
|
```
|
|
|
|
### Compilation Errors
|
|
|
|
```bash
|
|
# Clean build
|
|
cargo clean
|
|
|
|
# Update dependencies
|
|
cargo update
|
|
|
|
# Rebuild
|
|
cargo build --release
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
After successful verification:
|
|
|
|
1. **Documentation**: Update user guides with examples
|
|
2. **Integration**: Connect to config encryption module
|
|
3. **CI/CD**: Add automated tests to pipeline
|
|
4. **Deployment**: Package for distribution
|
|
5. **Monitoring**: Add telemetry and logging
|