TypeDialog/docs/encryption/encryption-services-setup.md

741 lines
17 KiB
Markdown
Raw Permalink Normal View History

2025-12-24 03:11:32 +00:00
# HOW-TO: Configure and Run Encryption Services for typedialog
## Overview
This guide walks through setting up **Age** (local file-based encryption) and **RustyVault** (HTTP-based encryption service) to test the typedialog encryption pipeline end-to-end.
**Service Matrix:**
| Backend | Type | Setup Complexity | Network | Requires |
| --------- | ------ | ------------------ | --------- | ---------- |
| **Age** | Local file-based | Trivial | None | age CLI tool |
| **RustyVault** | HTTP vault server | Moderate | localhost:8200 | Docker or manual build |
| **SOPS** | External tool | Complex | Varies | sops CLI + backends |
This guide covers Age (trivial) and RustyVault (moderate). SOPS is skipped for now.
---
## Part 1: Age Backend (Local File Encryption)
### What is Age?
Age is a simple, modern encryption tool using X25519 keys. Perfect for development because:
- No daemon/service required
- Keys stored as plaintext files
- Single binary
### Installation
**macOS (via Homebrew):**
```bash
brew install age
```
**Linux (Ubuntu/Debian):**
```bash
sudo apt-get install age
```
**Manual (any OS):**
```bash
# Download from https://github.com/FiloSottile/age/releases
# Extract and add to PATH
tar xzf age-v1.1.1-linux-amd64.tar.gz
sudo mv age/age /usr/local/bin/
sudo mv age/age-keygen /usr/local/bin/
```
**Verify installation:**
```bash
age --version
# age v1.1.1
```
### Generate Age Key Pair
Age uses a single private key file that contains both public and private components. The public key is derived from the private key.
**Generate keys for testing:**
```bash
# Create a test directory
mkdir -p ~/.age
# Generate private key
age-keygen -o ~/.age/key.txt
# Output will show:
# Public key: age1...xxx (save this, shown in file)
# Written to /home/user/.age/key.txt
```
**Verify key generation:**
```bash
# Check private key exists
cat ~/.age/key.txt
# Output: AGE-SECRET-KEY-1XXXX...
# Extract public key (age CLI does this automatically)
grep "^public key:" ~/.age/key.txt | cut -d' ' -f3
```
### Test Age Encryption Locally
**Create a test plaintext file:**
```bash
echo "This is a secret message" > test_message.txt
```
**Encrypt with age:**
```bash
# Get public key from private key
PUBLIC_KEY=$(grep "^public key:" ~/.age/key.txt | cut -d' ' -f3)
# Encrypt
age -r "$PUBLIC_KEY" test_message.txt > test_message.age
# Verify ciphertext is unreadable
cat test_message.age
# Output: AGE-ENCRYPTION-V1...binary...
```
**Decrypt with age:**
```bash
# Decrypt (will prompt for passphrase if key is encrypted)
age -d -i ~/.age/key.txt test_message.age
# Output: This is a secret message
```
### Configure typedialog to Use Age
**Environment variables:**
```bash
export AGE_KEY_FILE="$HOME/.age/key.txt"
```
**CLI flags:**
```bash
# Redact mode (no encryption needed)
typedialog form examples/08-encryption/simple-login.toml --redact --format json
# Encrypt mode (requires Age backend)
typedialog form examples/08-encryption/simple-login.toml --encrypt --backend age --key-file ~/.age/key.txt --format json
```
See `examples/08-encryption/README.md` for more example forms and test cases.
**TOML form configuration:**
```toml
[[fields]]
name = "password"
type = "password"
prompt = "Enter password"
sensitive = true
encryption_backend = "age"
[fields.encryption_config]
key = "~/.age/key.txt"
```
---
## Part 2: RustyVault Backend (HTTP Service)
### What is RustyVault?
RustyVault is a Rust implementation of HashiCorp Vault's Transit API:
- HTTP-based encryption/decryption service
- Suitable for production environments
- API-compatible with Vault Transit secrets engine
### Installation & Setup
**Option A: Docker (Recommended for testing)**
RustyVault provides official Docker images. Check availability:
```bash
# Search Docker Hub
docker search rustyvault
# Or build from source
git clone https://github.com/Tongsuo-Project/RustyVault.git
cd RustyVault
docker build -t rustyvault:latest .
```
**Option B: Manual Build (if Docker not available)**
```bash
# Prerequisites: Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Clone and build
git clone https://github.com/Tongsuo-Project/RustyVault.git
cd RustyVault
cargo build --release
# Binary at: target/release/rustyvault
```
### Run RustyVault Service
**Using Docker (single command):**
```bash
docker run -d \
--name rustyvault \
-p 8200:8200 \
-e RUSTYVAULT_LOG_LEVEL=info \
rustyvault:latest
# Verify it started
docker logs rustyvault | head -20
```
**Using local binary:**
```bash
# Create config directory
mkdir -p ~/.rustyvault
cd ~/.rustyvault
# Create minimal config (rustyvault.toml)
cat > config.toml <<'EOF'
[server]
address = "127.0.0.1:8200"
tls_disable = true
[backend]
type = "inmem" # In-memory storage (ephemeral)
EOF
# Run service
~/RustyVault/target/release/rustyvault server -c config.toml
```
**Verify service is running:**
```bash
# In another terminal
curl -s http://localhost:8200/v1/sys/health | jq .
# Should return health status JSON
```
### Configure RustyVault for Encryption
**Initialize RustyVault (first time only):**
```bash
# Generate initial token
VAULT_INIT=$(curl -s -X POST http://localhost:8200/v1/sys/init \
-d '{"secret_shares": 1, "secret_threshold": 1}' | jq -r .keys[0])
# Unseal vault
curl -s -X PUT http://localhost:8200/v1/sys/unseal \
-d "{\"key\": \"$VAULT_INIT\"}" > /dev/null
# Save root token
ROOT_TOKEN=$(curl -s -X POST http://localhost:8200/v1/sys/unseal \
-d "{\"key\": \"$VAULT_INIT\"}" | jq -r .auth.client_token)
export VAULT_TOKEN="$ROOT_TOKEN"
```
**Enable Transit secrets engine:**
```bash
curl -s -X POST http://localhost:8200/v1/sys/mounts/transit \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d '{"type": "transit"}' | jq .
```
**Create encryption key:**
```bash
curl -s -X POST http://localhost:8200/v1/transit/keys/typedialog-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d '{}' | jq .
# Verify key created
curl -s http://localhost:8200/v1/transit/keys/typedialog-key \
-H "X-Vault-Token: $VAULT_TOKEN" | jq .
```
### Test RustyVault Encryption
**Encrypt data via HTTP:**
```bash
# Plaintext (base64 encoded)
PLAINTEXT=$(echo -n "my-secret-password" | base64)
curl -s -X POST http://localhost:8200/v1/transit/encrypt/typedialog-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d "{\"plaintext\": \"$PLAINTEXT\"}" | jq .data.ciphertext
```
**Decrypt data via HTTP:**
```bash
# From encryption output above
CIPHERTEXT="vault:v1:..."
curl -s -X POST http://localhost:8200/v1/transit/decrypt/typedialog-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d "{\"ciphertext\": \"$CIPHERTEXT\"}" | jq -r .data.plaintext | base64 -d
```
### Configure typedialog to Use RustyVault
**Environment variables:**
```bash
export VAULT_ADDR="http://localhost:8200"
export VAULT_TOKEN="s.xxxx..." # Token from above
```
**CLI flags:**
```bash
typedialog form examples/08-encryption/credentials.toml \
--encrypt \
--backend rustyvault \
--vault-addr http://localhost:8200 \
--vault-token "s.xxxx..." \
--vault-key-path "transit/keys/typedialog-key" \
--format json
```
This form includes field-level RustyVault configuration in the `vault_token` field.
**TOML form configuration:**
```toml
[[fields]]
name = "password"
type = "password"
prompt = "Enter password"
sensitive = true
encryption_backend = "rustyvault"
[fields.encryption_config]
vault_addr = "http://localhost:8200"
vault_token = "s.xxxx..."
key_path = "transit/keys/typedialog-key"
```
---
## Part 3: Complete Integration Test Workflow
### Script: Setup Everything
Create `scripts/encryption-test-setup.sh`:
```bash
#!/usr/bin/env bash
set -e
echo "=== typedialog Encryption Services Setup ==="
# Age Setup
echo "1. Setting up Age..."
if ! command -v age &> /dev/null; then
echo " ✗ age not installed. Run: brew install age"
exit 1
fi
mkdir -p ~/.age
if [ ! -f ~/.age/key.txt ]; then
echo " → Generating Age keys..."
age-keygen -o ~/.age/key.txt
fi
export AGE_KEY_FILE="$HOME/.age/key.txt"
echo " ✓ Age configured at: $AGE_KEY_FILE"
# RustyVault Setup (Docker)
echo ""
echo "2. Setting up RustyVault (Docker)..."
if ! command -v docker &> /dev/null; then
echo " ⚠ Docker not installed, skipping RustyVault"
echo " → Install Docker or skip RustyVault tests"
else
if ! docker ps | grep -q rustyvault; then
echo " → Starting RustyVault container..."
docker run -d \
--name rustyvault \
-p 8200:8200 \
-e RUSTYVAULT_LOG_LEVEL=info \
rustyvault:latest
sleep 2
fi
# Initialize vault
echo " → Initializing RustyVault..."
VAULT_INIT=$(curl -s -X POST http://localhost:8200/v1/sys/init \
-d '{"secret_shares": 1, "secret_threshold": 1}' | jq -r .keys[0])
curl -s -X PUT http://localhost:8200/v1/sys/unseal \
-d "{\"key\": \"$VAULT_INIT\"}" > /dev/null
# Get root token
RESPONSE=$(curl -s -X GET http://localhost:8200/v1/sys/unseal \
-H "X-Vault-Token: $VAULT_INIT")
export VAULT_TOKEN=$(echo "$RESPONSE" | jq -r .auth.client_token // "root")
export VAULT_ADDR="http://localhost:8200"
# Enable transit
curl -s -X POST http://localhost:8200/v1/sys/mounts/transit \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d '{"type": "transit"}' > /dev/null 2>&1 || true
# Create key
curl -s -X POST http://localhost:8200/v1/transit/keys/typedialog-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d '{}' > /dev/null 2>&1 || true
echo " ✓ RustyVault running at: http://localhost:8200"
echo " ✓ Token: $VAULT_TOKEN"
fi
echo ""
echo "=== Setup Complete ==="
echo ""
echo "Test Age encryption:"
echo " typedialog form test.toml --encrypt --backend age --key-file ~/.age/key.txt"
echo ""
echo "Test RustyVault encryption:"
echo " export VAULT_ADDR='http://localhost:8200'"
echo " export VAULT_TOKEN='$VAULT_TOKEN'"
echo " typedialog form test.toml --encrypt --backend rustyvault --vault-key-path 'transit/keys/typedialog-key'"
```
**Make executable and run:**
```bash
chmod +x scripts/encryption-test-setup.sh
./scripts/encryption-test-setup.sh
```
### Test Case 1: Age Redaction (No Service Required)
**Option A: Use pre-built example (Recommended)**
```bash
typedialog form examples/08-encryption/simple-login.toml --redact --format json
# Expected output:
# {"username": "alice", "password": "[REDACTED]"}
```
**Option B: Create form manually**
```bash
# Create test form
cat > test_redaction.toml <<'EOF'
name = "test_form"
display_mode = "complete"
[[fields]]
name = "username"
type = "text"
prompt = "Username"
[[fields]]
name = "password"
type = "password"
prompt = "Password"
sensitive = true
EOF
# Test redaction (requires no service)
typedialog form test_redaction.toml --redact --format json
```
### Test Case 2: Age Encryption (Service Not Required, Key File Required)
**Option A: Use pre-built example (Recommended)**
```bash
# Prerequisites: Age key generated (from setup script)
./scripts/encryption-test-setup.sh
# Test with simple form
typedialog form examples/08-encryption/simple-login.toml \
--encrypt --backend age --key-file ~/.age/key.txt --format json
# Expected output: password field contains age ciphertext
# {"username": "alice", "password": "age1muz6ah54ew9am7mzmy0m4w5..."}
# Or test with full credentials form
typedialog form examples/08-encryption/credentials.toml \
--encrypt --backend age --key-file ~/.age/key.txt --format json
```
**Option B: Create form manually**
```bash
# Generate Age key if not exists
mkdir -p ~/.age
if [ ! -f ~/.age/key.txt ]; then
age-keygen -o ~/.age/key.txt
fi
# Create test form
cat > test_age_encrypt.toml <<'EOF'
name = "test_form"
display_mode = "complete"
[[fields]]
name = "username"
type = "text"
prompt = "Username"
[[fields]]
name = "password"
type = "password"
prompt = "Password"
sensitive = true
encryption_backend = "age"
[fields.encryption_config]
key = "~/.age/key.txt"
EOF
# Test encryption (requires Age key file)
typedialog form test_age_encrypt.toml --encrypt --backend age --key-file ~/.age/key.txt --format json
```
### Test Case 3: RustyVault Encryption (Service Required)
**Prerequisites: RustyVault running**
```bash
# Start RustyVault and setup (requires Docker)
./scripts/encryption-test-setup.sh
# Verify service is healthy
curl http://localhost:8200/v1/sys/health | jq .
```
**Option A: Use pre-built example (Recommended)**
```bash
# Export Vault credentials
export VAULT_ADDR="http://localhost:8200"
export VAULT_TOKEN="root"
# Test with simple form
typedialog form examples/08-encryption/simple-login.toml \
--encrypt --backend rustyvault \
--vault-key-path "transit/keys/typedialog-key" \
--format json
# Expected output: password field contains vault ciphertext
# {"username": "alice", "password": "vault:v1:K8..."}
# Or test with full credentials form (demonstrates field-level config)
typedialog form examples/08-encryption/credentials.toml \
--encrypt --backend rustyvault \
--vault-key-path "transit/keys/typedialog-key" \
--format json
```
**Option B: Create form manually**
```bash
cat > test_vault_encrypt.toml <<'EOF'
name = "test_form"
display_mode = "complete"
[[fields]]
name = "username"
type = "text"
prompt = "Username"
[[fields]]
name = "password"
type = "password"
prompt = "Password"
sensitive = true
encryption_backend = "rustyvault"
[fields.encryption_config]
vault_addr = "http://localhost:8200"
key_path = "transit/keys/typedialog-key"
EOF
# Test encryption with RustyVault
export VAULT_TOKEN="s.xxxx" # From setup output
export VAULT_ADDR="http://localhost:8200"
typedialog form test_vault_encrypt.toml \
--encrypt \
--backend rustyvault \
--vault-addr http://localhost:8200 \
--vault-token "$VAULT_TOKEN" \
--vault-key-path "transit/keys/typedialog-key" \
--format json
# Expected output: password field contains vault ciphertext
# {"username": "alice", "password": "vault:v1:..."}
```
---
## Part 4: Run Actual Integration Tests
### Test Case: Age Roundtrip (Encrypt → Decrypt)
Once Age is set up, these test scenarios validate the pipeline:
**Scenario 1: Redaction works (no encryption service)**
```bash
cargo test --test nickel_integration test_encryption_roundtrip_with_redaction -- --nocapture
# Expected: PASS - redacts sensitive fields
```
**Scenario 2: Metadata mapping works**
```bash
cargo test --test nickel_integration test_encryption_metadata_to_field_definition -- --nocapture
# Expected: PASS - EncryptionMetadata maps to FieldDefinition
```
**Scenario 3: Auto-detection of password fields**
```bash
cargo test --test nickel_integration test_encryption_auto_detection_from_field_type -- --nocapture
# Expected: PASS - Password fields auto-marked as sensitive
```
### Run All Encryption Tests
```bash
cargo test --test nickel_integration test_encryption -- --nocapture
```
**Current status:**
- ✅ 5 tests passing (redaction, metadata mapping)
- ⏳ 0 tests for actual Age encryption roundtrip (not yet implemented)
- ⏳ 0 tests for RustyVault integration (backend not implemented)
---
## Part 5: Troubleshooting
### Age Issues
**Problem: `age: command not found`**
```bash
# Install age
brew install age # macOS
sudo apt install age # Linux
```
**Problem: Permission denied on ~/.age/key.txt**
```bash
chmod 600 ~/.age/key.txt
```
**Problem: Invalid key format**
```bash
# Regenerate keys
rm ~/.age/key.txt
age-keygen -o ~/.age/key.txt
```
### RustyVault Issues
**Problem: Docker container won't start**
```bash
# Check logs
docker logs rustyvault
# Remove and restart
docker rm -f rustyvault
docker run -d --name rustyvault -p 8200:8200 rustyvault:latest
```
**Problem: Vault initialization fails**
```bash
# Check if vault is responding
curl -s http://localhost:8200/v1/sys/health
# If not, restart container
docker restart rustyvault
```
**Problem: Transit API not working**
```bash
# Verify token
echo $VAULT_TOKEN
# Check auth
curl -s http://localhost:8200/v1/sys/mounts \
-H "X-Vault-Token: $VAULT_TOKEN"
```
**Problem: Can't connect from typedialog**
```bash
# Verify network
curl -s http://localhost:8200/v1/sys/health | jq .
# Check environment variables
echo $VAULT_ADDR
echo $VAULT_TOKEN
# Test encryption endpoint
curl -s -X POST http://localhost:8200/v1/transit/encrypt/typedialog-key \
-H "X-Vault-Token: $VAULT_TOKEN" \
-d '{"plaintext": "dGVzdA=="}' | jq .
```
---
## Part 6: Next Steps
Once services are running, implement:
1. **test_age_encrypt_roundtrip** - Encrypt with Age, decrypt, verify plaintext
2. **test_rustyvault_encrypt_roundtrip** - Encrypt with RustyVault, decrypt, verify
3. **test_cli_encrypt_age** - Run `typedialog form --encrypt --backend age`, verify output is ciphertext
4. **test_cli_encrypt_rustyvault** - Run `typedialog form --encrypt --backend rustyvault`, verify output is ciphertext
5. **Integration test script** - Single script that tests all pipelines end-to-end
---
## References
- **Age**: <https://github.com/FiloSottile/age>
- **RustyVault**: <https://github.com/Tongsuo-Project/RustyVault>
- **HashiCorp Vault Transit**: <https://www.vaultproject.io/api-docs/secret/transit>