TypeDialog/examples/08-encryption/TEST-SOPS-INTEGRATION.md
Jesús Pérez aca491ba42
feat(encryption): integrate external encryption services with Nickel contracts
ADDED:
- encryption_bridge.rs: Service integration layer
- encryption_contract_parser.rs: Nickel contract parsing
- encryption_integration.rs: Integration tests (+442 lines)
- docs/ENCRYPTION-*.md: Quick start, setup, architecture
- examples/08-encryption: Usage examples
- scripts/encryption-test-setup.sh: Provisioning

MODIFIED:
- helpers.rs: +570 lines utility functions
- nickel/: Enhanced contract parsing & serialization
- form_parser.rs: Constraint interpolation improvements
- config/mod.rs: New configuration (+24 lines)
- typedialog/src/main.rs: CLI updates (+83 lines)
- Cargo.toml: encryption_bridge dependency
- Cargo.lock, SBOMs: Updated

AFFECTED BACKENDS: cli, tui, web (core-level changes)
2025-12-22 10:40:01 +00:00

11 KiB

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

# 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

# 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.

# 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

# 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:

creation_rules:
  - path_regex: .*
    age: age1xxxxxxxxxxxxxxxxxxxxx

Test 2: Test SOPS Encryption Directly

2.1 Create a Test File

# 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

# 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

# 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

# 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].

# 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

# 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

# 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

# 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

# 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:

# 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

echo -e "alice\nsecretpass123" | typedialog form examples/08-encryption/simple-login.toml \
  --redact --format json

# Output:
# {
#   "username": "alice",
#   "password": "[REDACTED]"
# }

6.2 Age

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

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

# 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

# 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

# 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

# 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"

# Error: config file not found, or has no creation rules

Cause: .sops.yaml not found

Solution:

# 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"

# Error: no identity matched key

Cause: Age key not found or not accessible

Solution:

# 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

# typedialog seems to be waiting for input

Cause: stdin not properly piped

Solution:

# 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"

# Error: Backend 'sops' not available

Cause:

  1. sops binary not installed
  2. SOPS feature not compiled into typedialog

Solution:

# 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