nushell-plugins/nu_plugin_kms/TEST_VERIFICATION.md
Jesús Pérez be62c8701a feat: Add ARGUMENTS documentation and interactive update mode
- 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
2025-10-19 00:05:16 +01:00

11 KiB

nu_plugin_kms - Verification and Testing Guide

Date: 2025-10-08 Status: Ready for Testing

Quick Verification

1. Verify Binary Exists

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

# 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

# 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

# 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

# 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

# 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

# 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

# Test 4: Auto-detect Age backend
nu -c "kms status"

# Expected output:
# {
#   backend: "age",
#   available: true,
#   config: "recipient: age1..., identity: set"
# }

RustyVault Backend

Setup

# 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

# Test 1: Encrypt with RustyVault
nu -c "kms encrypt 'Secret data!' --backend rustyvault --key provisioning-main"

# Expected output: vault:v1:XXXXXXXXXX...

Test Decryption

# 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

# 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

# Test 4: Check RustyVault status
nu -c "kms status"

# Expected output:
# {
#   backend: "rustyvault",
#   available: true,
#   config: "addr: http://localhost:8200"
# }

Cleanup

# Stop and remove RustyVault container
docker stop rustyvault
docker rm rustyvault

HTTP Fallback Backend

Setup (Mock Server)

# 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

# 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

# Stop mock server
kill $MOCK_SERVER_PID

Integration Tests

Test Auto-Detection Priority

# 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

# 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

# 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

# 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

# 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

# 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

  • cargo check passes
  • cargo build --release succeeds
  • Binary created in target/release/nu_plugin_kms
  • 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

# 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

# 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

# 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