539 lines
11 KiB
Markdown
539 lines
11 KiB
Markdown
|
|
# How to Test SOPS Integration with typedialog
|
||
|
|
|
||
|
|
Complete step-by-step guide to testing SOPS encryption backend with typedialog.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This guide shows how to:
|
||
|
|
1. Setup SOPS with Age (for local testing)
|
||
|
|
2. Test SOPS encryption manually
|
||
|
|
3. Integrate SOPS with typedialog
|
||
|
|
4. Verify encryption/decryption works
|
||
|
|
|
||
|
|
## Prerequisites
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Install tools
|
||
|
|
brew install sops age-keygen
|
||
|
|
|
||
|
|
# Verify installation
|
||
|
|
sops --version
|
||
|
|
age-keygen --version
|
||
|
|
|
||
|
|
# Check typedialog is available
|
||
|
|
which typedialog
|
||
|
|
typedialog --version
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 1: Setup SOPS Configuration
|
||
|
|
|
||
|
|
### 1.1 Generate Age Key
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Generate a key for SOPS to use
|
||
|
|
age-keygen -o ~/.age/sops-test-key.txt
|
||
|
|
|
||
|
|
# View the key
|
||
|
|
cat ~/.age/sops-test-key.txt
|
||
|
|
# Output:
|
||
|
|
# # created: 2025-12-21T12:34:56Z
|
||
|
|
# # public key: age1xxxxxxxxxxxxxxxxxxxxx
|
||
|
|
# AGE-SECRET-KEY-xxxxxxxxxxxxxxxxxxxxx
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.2 Create `.sops.yaml`
|
||
|
|
|
||
|
|
SOPS requires a configuration file that specifies which KMS to use.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Create .sops.yaml in your working directory
|
||
|
|
cat > .sops.yaml << 'EOF'
|
||
|
|
creation_rules:
|
||
|
|
- path_regex: .*
|
||
|
|
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxx # Replace with YOUR public key
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Extract your public key and replace it in .sops.yaml
|
||
|
|
PUBLIC_KEY=$(grep "^# public key:" ~/.age/sops-test-key.txt | sed 's/# public key: //')
|
||
|
|
cat > .sops.yaml << EOF
|
||
|
|
creation_rules:
|
||
|
|
- path_regex: .*
|
||
|
|
age: $PUBLIC_KEY
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Verify config is correct
|
||
|
|
cat .sops.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
### 1.3 Verify SOPS Configuration
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# SOPS should be able to find the config
|
||
|
|
ls -la .sops.yaml
|
||
|
|
# Output: -rw-r--r-- 1 user staff 45 Dec 21 12:34 .sops.yaml
|
||
|
|
|
||
|
|
# View the config
|
||
|
|
cat .sops.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
**Expected Output:**
|
||
|
|
```yaml
|
||
|
|
creation_rules:
|
||
|
|
- path_regex: .*
|
||
|
|
age: age1xxxxxxxxxxxxxxxxxxxxx
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 2: Test SOPS Encryption Directly
|
||
|
|
|
||
|
|
### 2.1 Create a Test File
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Create plaintext YAML
|
||
|
|
echo 'secret: my-super-secret-password' > test-secret.yaml
|
||
|
|
|
||
|
|
# Verify content
|
||
|
|
cat test-secret.yaml
|
||
|
|
# Output: secret: my-super-secret-password
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 Encrypt with SOPS
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Tell SOPS where to find your Age key
|
||
|
|
export SOPS_AGE_KEY_FILE=~/.age/sops-test-key.txt
|
||
|
|
|
||
|
|
# Encrypt the file in-place
|
||
|
|
sops -e -i test-secret.yaml
|
||
|
|
|
||
|
|
# Verify it's encrypted (should be YAML with ENC[...])
|
||
|
|
cat test-secret.yaml
|
||
|
|
# Output should show: secret: ENC[AES256_GCM,data:xxxxx,iv:xxxxx,...]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.3 Decrypt to Verify
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Decrypt and display (doesn't modify file)
|
||
|
|
sops -d test-secret.yaml
|
||
|
|
# Output:
|
||
|
|
# secret: my-super-secret-password
|
||
|
|
# sops:
|
||
|
|
# ...
|
||
|
|
|
||
|
|
# Extract just the secret value
|
||
|
|
sops -d test-secret.yaml | grep "^secret:" | sed 's/secret: //'
|
||
|
|
# Output: my-super-secret-password
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.4 Clean Up
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Remove test file
|
||
|
|
rm test-secret.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
**Key Points:**
|
||
|
|
- ✅ SOPS encrypts/decrypts correctly
|
||
|
|
- ✅ Plaintext is preserved during round-trip
|
||
|
|
- ✅ `.sops.yaml` controls which keys can decrypt
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 3: Test typedialog Redaction (No Encryption Service)
|
||
|
|
|
||
|
|
Redaction works without any encryption - just replaces sensitive fields with `[REDACTED]`.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Test redaction (no encryption service needed)
|
||
|
|
# Provide input via stdin: username then password
|
||
|
|
echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
|
||
|
|
--redact \
|
||
|
|
--format json
|
||
|
|
|
||
|
|
# You'll see prompts:
|
||
|
|
# Username *
|
||
|
|
# Password *
|
||
|
|
#
|
||
|
|
# Output:
|
||
|
|
# {
|
||
|
|
# "username": "alice",
|
||
|
|
# "password": "[REDACTED]"
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
**What's being tested:**
|
||
|
|
- ✅ typedialog can detect sensitive fields
|
||
|
|
- ✅ Redaction replaces secrets with `[REDACTED]`
|
||
|
|
- ✅ Non-sensitive fields remain visible
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 4: Test typedialog with Age Backend
|
||
|
|
|
||
|
|
Age backend encrypts locally without external service.
|
||
|
|
|
||
|
|
### 4.1 Ensure Age Key Exists
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Generate Age key if needed
|
||
|
|
if [ ! -f ~/.age/key.txt ]; then
|
||
|
|
age-keygen -o ~/.age/key.txt
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Verify key exists
|
||
|
|
cat ~/.age/key.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 Encrypt with Age Backend
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Provide input via stdin: username then password
|
||
|
|
echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
|
||
|
|
--encrypt --backend age \
|
||
|
|
--key-file ~/.age/key.txt \
|
||
|
|
--format json
|
||
|
|
|
||
|
|
# Output:
|
||
|
|
# {
|
||
|
|
# "username": "alice",
|
||
|
|
# "password": "age1muz6ah54ew9am7mzmy0m4w5arcegt056l9448sqy5ju27q5qaf3qjv35tr"
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
**What's being tested:**
|
||
|
|
- ✅ typedialog can encrypt with Age backend
|
||
|
|
- ✅ Ciphertext starts with `age1`
|
||
|
|
- ✅ Non-sensitive fields remain plaintext
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 5: Test typedialog with SOPS Backend ⭐
|
||
|
|
|
||
|
|
This is the main integration test.
|
||
|
|
|
||
|
|
### 5.1 Verify Setup
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Make sure .sops.yaml exists in current directory
|
||
|
|
cat .sops.yaml
|
||
|
|
# Should show your Age public key
|
||
|
|
|
||
|
|
# Set Age key for SOPS
|
||
|
|
export SOPS_AGE_KEY_FILE=~/.age/sops-test-key.txt
|
||
|
|
|
||
|
|
# Verify SOPS can find config
|
||
|
|
sops --version
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5.2 Encrypt with SOPS Backend
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Provide input via stdin: username then password
|
||
|
|
echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
|
||
|
|
--encrypt --backend sops \
|
||
|
|
--format json
|
||
|
|
|
||
|
|
# If .sops.yaml is not found, you'll see:
|
||
|
|
# SOPS encryption error: config file not found...
|
||
|
|
#
|
||
|
|
# If successful, output:
|
||
|
|
# {
|
||
|
|
# "username": "alice",
|
||
|
|
# "password": "sops:v1:4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a..."
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
**What's being tested:**
|
||
|
|
- ✅ typedialog can use SOPS backend
|
||
|
|
- ✅ SOPS backend respects `.sops.yaml` configuration
|
||
|
|
- ✅ Ciphertext format is `sops:v1:<hex>`
|
||
|
|
- ✅ Sensitive fields are encrypted, plaintext fields remain visible
|
||
|
|
|
||
|
|
### 5.3 Verify SOPS Ciphertext
|
||
|
|
|
||
|
|
The encrypted password is hex-encoded encrypted YAML:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Extract password from JSON
|
||
|
|
PASSWORD="sops:v1:4f5a6b7c8d9e0f1a2b3c..."
|
||
|
|
|
||
|
|
# Strip the version prefix
|
||
|
|
HEX_PART="4f5a6b7c8d9e0f1a2b3c..."
|
||
|
|
|
||
|
|
# Decode from hex to see raw encrypted content
|
||
|
|
echo "$HEX_PART" | xxd -r -p | head -c 100
|
||
|
|
# Shows SOPS-encrypted YAML structure
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 6: Compare All Backends
|
||
|
|
|
||
|
|
Run the same form with different backends to see the difference.
|
||
|
|
|
||
|
|
### 6.1 Redaction
|
||
|
|
|
||
|
|
```bash
|
||
|
|
echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
|
||
|
|
--redact --format json
|
||
|
|
|
||
|
|
# Output:
|
||
|
|
# {
|
||
|
|
# "username": "alice",
|
||
|
|
# "password": "[REDACTED]"
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.2 Age
|
||
|
|
|
||
|
|
```bash
|
||
|
|
echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
|
||
|
|
--encrypt --backend age --key-file ~/.age/key.txt --format json
|
||
|
|
|
||
|
|
# Output:
|
||
|
|
# {
|
||
|
|
# "username": "alice",
|
||
|
|
# "password": "age1xxxxxxxx..."
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
### 6.3 SOPS
|
||
|
|
|
||
|
|
```bash
|
||
|
|
export SOPS_AGE_KEY_FILE=~/.age/sops-test-key.txt
|
||
|
|
|
||
|
|
echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
|
||
|
|
--encrypt --backend sops --format json
|
||
|
|
|
||
|
|
# Output:
|
||
|
|
# {
|
||
|
|
# "username": "alice",
|
||
|
|
# "password": "sops:v1:4f5a6b..."
|
||
|
|
# }
|
||
|
|
```
|
||
|
|
|
||
|
|
**Comparison:**
|
||
|
|
| Backend | Format | Service Required | Use Case |
|
||
|
|
|---------|--------|------------------|----------|
|
||
|
|
| Redaction | `[REDACTED]` | No | Development, logging |
|
||
|
|
| Age | `age1...` | No (local key) | Local development |
|
||
|
|
| SOPS | `sops:v1:hex...` | No (Age) or Yes (AWS/GCP/Azure) | Team collaboration |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 7: Multi-Backend Form
|
||
|
|
|
||
|
|
Test a form with multiple encryption backends.
|
||
|
|
|
||
|
|
### 7.1 Create Test File
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Use the multi-backend example
|
||
|
|
cat examples/08-encryption/multi-backend-sops.toml
|
||
|
|
|
||
|
|
# This form demonstrates:
|
||
|
|
# - api_key: Age backend
|
||
|
|
# - db_password: SOPS backend
|
||
|
|
# - master_key: AWS KMS backend
|
||
|
|
```
|
||
|
|
|
||
|
|
### 7.2 Encrypt Different Fields with Different Backends
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Encrypt with SOPS (for db_password field)
|
||
|
|
echo -e "myapp\nproduction\nerror\ntestuser\ntestpass\napikey123" | \
|
||
|
|
typedialog form examples/08-encryption/multi-backend-sops.toml \
|
||
|
|
--encrypt --backend sops \
|
||
|
|
--format json
|
||
|
|
|
||
|
|
# Output shows:
|
||
|
|
# - api_key: field was encrypted (may show Error if backend not available)
|
||
|
|
# - db_password: encrypted with SOPS (sops:v1:...)
|
||
|
|
# - other fields: plain or encrypted based on field config
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Test 8: Integration with encrypt Crate
|
||
|
|
|
||
|
|
Test that the Rust integration is working.
|
||
|
|
|
||
|
|
### 8.1 Run Cargo Tests
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Test SOPS backend in encrypt crate
|
||
|
|
cargo test -p encrypt --features sops --lib backend::sops
|
||
|
|
|
||
|
|
# Expected output:
|
||
|
|
# test backend::sops::tests::test_sops_backend_name ... ok
|
||
|
|
# test backend::sops::tests::test_sops_backend_info ... ok
|
||
|
|
# ... more tests ...
|
||
|
|
# test result: ok. 10 passed; 0 failed
|
||
|
|
```
|
||
|
|
|
||
|
|
### 8.2 Test Feature-Gating
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Test without SOPS feature
|
||
|
|
cargo test -p encrypt --features age test_is_available_sops
|
||
|
|
|
||
|
|
# Should show SOPS is not available when feature disabled
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Problem: "config file not found"
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Error: config file not found, or has no creation rules
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cause**: `.sops.yaml` not found
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```bash
|
||
|
|
# Check if .sops.yaml exists
|
||
|
|
ls -la .sops.yaml
|
||
|
|
|
||
|
|
# Create it if missing
|
||
|
|
cat > .sops.yaml << 'EOF'
|
||
|
|
creation_rules:
|
||
|
|
- path_regex: .*
|
||
|
|
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxx
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Or make sure you're in the correct directory
|
||
|
|
pwd
|
||
|
|
ls examples/08-encryption/
|
||
|
|
```
|
||
|
|
|
||
|
|
### Problem: "no identity matched key"
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Error: no identity matched key
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cause**: Age key not found or not accessible
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```bash
|
||
|
|
# Verify key file exists
|
||
|
|
cat ~/.age/sops-test-key.txt
|
||
|
|
|
||
|
|
# Set the key for SOPS
|
||
|
|
export SOPS_AGE_KEY_FILE=~/.age/sops-test-key.txt
|
||
|
|
|
||
|
|
# Test SOPS directly
|
||
|
|
sops -d test-secret.yaml
|
||
|
|
```
|
||
|
|
|
||
|
|
### Problem: typedialog times out or hangs
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# typedialog seems to be waiting for input
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cause**: stdin not properly piped
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```bash
|
||
|
|
# Make sure to use echo -e with newlines
|
||
|
|
echo -e "username\npassword\nmore_input" | typedialog form ...
|
||
|
|
|
||
|
|
# Or use a here-document
|
||
|
|
typedialog form ... << EOF
|
||
|
|
alice
|
||
|
|
secretpass
|
||
|
|
EOF
|
||
|
|
```
|
||
|
|
|
||
|
|
### Problem: "Backend 'sops' not available"
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Error: Backend 'sops' not available
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cause**:
|
||
|
|
1. sops binary not installed
|
||
|
|
2. SOPS feature not compiled into typedialog
|
||
|
|
|
||
|
|
**Solution**:
|
||
|
|
```bash
|
||
|
|
# Install sops
|
||
|
|
brew install sops
|
||
|
|
|
||
|
|
# Rebuild typedialog with sops feature
|
||
|
|
cargo build --features encryption
|
||
|
|
# (should include sops by default)
|
||
|
|
|
||
|
|
# Or check if typedialog was built with SOPS
|
||
|
|
cargo build --features all
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Summary: What Each Test Verifies
|
||
|
|
|
||
|
|
| Test | Verifies | Command |
|
||
|
|
|------|----------|---------|
|
||
|
|
| 1 | `.sops.yaml` configuration | Manual file creation |
|
||
|
|
| 2 | SOPS encryption/decryption | `sops -e -i` / `sops -d` |
|
||
|
|
| 3 | typedialog redaction | `--redact` flag |
|
||
|
|
| 4 | typedialog + Age backend | `--encrypt --backend age` |
|
||
|
|
| 5 | **typedialog + SOPS backend** ⭐ | `--encrypt --backend sops` |
|
||
|
|
| 6 | Backend output format differences | Compare all three outputs |
|
||
|
|
| 7 | Multi-backend form support | Field-level backend config |
|
||
|
|
| 8 | Rust integration | `cargo test --features sops` |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Expected Timeline
|
||
|
|
|
||
|
|
- **Setup**: 5 minutes (create keys and .sops.yaml)
|
||
|
|
- **Tests 1-4**: 5 minutes each (quick manual tests)
|
||
|
|
- **Test 5**: 5 minutes (main SOPS integration test)
|
||
|
|
- **Tests 6-8**: 10 minutes (comparison and verification)
|
||
|
|
|
||
|
|
**Total**: ~45 minutes for complete integration testing
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
After verifying SOPS works:
|
||
|
|
|
||
|
|
1. **Production Setup**:
|
||
|
|
- Replace Age with AWS KMS in `.sops.yaml`
|
||
|
|
- Set up AWS credentials
|
||
|
|
- Deploy SOPS configuration
|
||
|
|
|
||
|
|
2. **Team Collaboration**:
|
||
|
|
- Share `.sops.yaml` in Git (does not contain secrets)
|
||
|
|
- Each team member has their own KMS access
|
||
|
|
- SOPS handles key rotation automatically
|
||
|
|
|
||
|
|
3. **CI/CD Integration**:
|
||
|
|
- Store encrypted secrets in Git
|
||
|
|
- Decrypt during CI/CD pipeline
|
||
|
|
- Never expose plaintext secrets
|
||
|
|
|
||
|
|
4. **Multi-Environment**:
|
||
|
|
- Dev: Age backend (local)
|
||
|
|
- Staging: SOPS (shared team KMS)
|
||
|
|
- Prod: AWS KMS (automated)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## See Also
|
||
|
|
|
||
|
|
- [SOPS GitHub](https://github.com/getsops/sops)
|
||
|
|
- [Age GitHub](https://github.com/FiloSottile/age)
|
||
|
|
- [typedialog Encryption Examples](./README.md)
|
||
|
|
- [Encryption Architecture](../../docs/ENCRYPTION-UNIFIED-ARCHITECTURE.md)
|