# Migration Guide: v1.1.0 → >=v1.1.1 This guide documents **two successful real-world migration paths** validated in production: 1. **Guard/Middle Relay** (mounted torrc) - Zero issues 2. **Bridge from Official Image** (thetorproject/obfs4-bridge) - Requires ownership fix --- ## 📋 What's New in v1.1.1 ### Security Fixes - ✅ **Fixed**: Command injection via OBFS4V_* environment variables (CWE-78, CWE-94) - ✅ **Fixed**: Health check failure on ENV-based deployments (CWE-703) - ✅ **Fixed**: Privilege escalation attempt with silent failure (CWE-250) - ✅ **Fixed**: Inadequate input validation (CWE-20) - ✅ **Fixed**: Workflow permission over-granting (CWE-269) - ✅ **Fixed**: Temporary file race condition (CWE-377) - ✅ **Fixed**: TOR_CONTACT_INFO validation false positives ### Improvements - ✅ Smart healthcheck script works with both mounted and ENV configs - ✅ Comprehensive input validation with whitespace trimming - ✅ Better error messages and debugging - ✅ Removed non-functional chown attempt - ✅ Cleaned up bridge configuration (removed redundant ExitPolicy) ### Breaking Changes **None** - All changes are backward compatible! --- ## ✅ Migration Path 1: Guard/Middle Relay (Mounted Config) **Difficulty**: Easy **Downtime**: < 2 minutes **Fingerprint Preserved**: Yes ✅ **Volume Changes Required**: None ### Prerequisites ```bash # Verify you're using a mounted torrc file docker inspect --format='{{range .Mounts}}{{if eq .Destination "/etc/tor/torrc"}}MOUNTED{{end}}{{end}}' # Should output: MOUNTED ``` ### Step 1: Backup ```bash # Stop container docker stop # Backup volumes docker run --rm \ -v tor-guard-data:/data \ -v /tmp:/backup \ alpine:3.23.3 tar czf /backup/tor-guard-data-backup-$(date +%Y%m%d).tar.gz /data docker run --rm \ -v tor-guard-logs:/data \ -v /tmp:/backup \ alpine:3.23.3 tar czf /backup/tor-guard-logs-backup-$(date +%Y%m%d).tar.gz /data # Save fingerprint docker run --rm -v tor-guard-data:/data alpine:3.23.3 cat /data/fingerprint > /tmp/fingerprint-backup.txt ``` ### Step 2: Update Configuration **Cosmos JSON changes**: ```json { "image": "r3bo0tbx1/onion-relay:latest", // ← Update from :latest or :1.1.0 "cap_add": [ "NET_BIND_SERVICE" // ← Removed unnecessary CHOWN, SETUID, SETGID, DAC_OVERRIDE ], "labels": { "cosmos-version": "1.1.1" // ← Update version } } ``` ### Step 3: Remove Old Container ```bash docker rm ``` ### Step 4: Deploy Updated Container - Import updated JSON in Cosmos UI - Or redeploy with `docker-compose up -d` ### Step 5: Verify ```bash # Check logs docker logs -f # Expected: ✅ Using mounted configuration: /etc/tor/torrc # Verify fingerprint matches docker exec fingerprint cat /tmp/fingerprint-backup.txt # Must match! # Run diagnostics docker exec status docker exec health | jq . ``` ### ✅ Success Criteria - [ ] Container starts successfully - [ ] Logs show "Using mounted configuration" - [ ] Fingerprint matches backup - [ ] Bootstrap reaches 100% - [ ] Relay is reachable --- ## ⚠️ Migration Path 2: Bridge from Official Image **Difficulty**: Moderate **Downtime**: 5-10 minutes **Fingerprint Preserved**: Yes ✅ (after fix) **Volume Changes Required**: Yes - UID ownership fix ### The Challenge **UID Mismatch**: - Official `thetorproject/obfs4-bridge` (Debian): UID **101** - `r3bo0tbx1/onion-relay` (Alpine): UID **100** **Symptom**: `Permission denied` errors if not fixed. ### Prerequisites ```bash # Verify current image docker inspect --format='{{.Config.Image}}' # Should show: thetorproject/obfs4-bridge or similar # Check ENV variables docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' | grep -E "OR_PORT|PT_PORT|EMAIL|NICKNAME" ``` ### Recommended Approach: Use Mounted Config **Why?** - ✅ More reliable than ENV variables - ✅ Bypasses validation complexity - ✅ Same as guard relay (consistent approach) - ✅ Easier to troubleshoot ### Step 1: Backup Everything ```bash # Stop container docker stop obfs4-bridge # Backup volume (CRITICAL!) docker run --rm \ -v obfs4-data:/data \ -v /tmp:/backup \ alpine:3.23.3 tar czf /backup/obfs4-data-backup-$(date +%Y%m%d).tar.gz /data # Verify backup ls -lh /tmp/obfs4-data-backup-*.tar.gz # Save fingerprint docker run --rm -v obfs4-data:/data alpine:3.23.3 cat /data/fingerprint > /tmp/bridge-fingerprint-backup.txt cat /tmp/bridge-fingerprint-backup.txt ``` ### Step 2: Fix Volume Ownership (CRITICAL!) ```bash # Check current ownership (should be 101:101) docker run --rm -v obfs4-data:/data alpine:3.23.3 ls -ldn /data # Output: drwx------ ... 101 101 ... # Fix ownership: 101 → 100 docker run --rm -v obfs4-data:/data alpine:3.23.3 chown -R 100:101 /data # Verify fix docker run --rm -v obfs4-data:/data alpine:3.23.3 ls -ldn /data # Output: drwx------ ... 100 101 ... ← MUST show 100! # Verify key files are readable docker run --rm -v obfs4-data:/data alpine:3.23.3 ls -la /data/keys/ # Should show files owned by 100:101 ``` ### Step 3: Create Bridge Config File ```bash # Create config directory sudo mkdir -p /home/$(whoami)/onion # Create bridge.conf sudo tee /home/$(whoami)/onion/bridge.conf > /dev/null << 'EOF' # Tor obfs4 Bridge Configuration Nickname MyObfs4Bridge ContactInfo admin@email.org # Network configuration ORPort 9001 SocksPort 0 # Data directories DataDirectory /var/lib/tor Log notice file /var/log/tor/notices.log # Bridge relay configuration BridgeRelay 1 PublishServerDescriptor bridge # obfs4 pluggable transport ServerTransportPlugin obfs4 exec /usr/bin/lyrebird ServerTransportListenAddr obfs4 0.0.0.0:9005 ExtORPort auto # Additional options MaxMemInQueues 1024 MB AddressDisableIPv6 0 EOF # Customize with your values sudo nano /home/$(whoami)/onion/bridge.conf ``` ### Step 4: Remove Old Container ```bash docker rm obfs4-bridge ``` ### Step 5: Deploy with Mounted Config **Cosmos JSON**: ```json { "minVersion": "0.8.0", "services": { "obfs4-bridge": { "image": "r3bo0tbx1/onion-relay:latest", "container_name": "obfs4-bridge", "restart": "unless-stopped", "network_mode": "host", "environment": [ "TZ=UTC" ], "volumes": [ { "type": "volume", "source": "obfs4-data", "target": "/var/lib/tor" }, { "type": "bind", "source": "/home/youruser/onion/bridge.conf", "target": "/etc/tor/torrc", "read_only": true } ], "security_opt": [ "no-new-privileges:true" ], "labels": { "cosmos-stack": "TorBridge", "cosmos-stack-main": "obfs4-bridge", "cosmos-description": "🌉 Hardened obfs4 Bridge v1.1.1", "cosmos-icon": "https://iili.io/KsXP2Y7.png", "cosmos-auto-update": "true", "cosmos-auto-update-type": "registry", "cosmos-force-network-secured": "false", "cosmos-version": "1.1.1" } } }, "volumes": { "obfs4-data": {} } } ``` **Key changes**: - ✅ Removed ALL ENV variables - ✅ Added mounted bridge.conf - ✅ Updated image to 1.1.1 ### Step 6: Deploy and Verify ```bash # Import JSON in Cosmos UI and start # Check logs docker logs -f obfs4-bridge # Expected: # ✅ Using mounted configuration: /etc/tor/torrc # ✅ Configuration is valid # 🚀 Tor relay started # Wait 30 seconds, then verify fingerprint docker exec obfs4-bridge fingerprint cat /tmp/bridge-fingerprint-backup.txt # MUST MATCH! # Check configuration docker exec obfs4-bridge cat /etc/tor/torrc # Should show BridgeRelay 1, obfs4 config, etc. # Run health check docker exec obfs4-bridge health | jq . # Run full status docker exec obfs4-bridge status ``` ### Step 7: Get Bridge Line (After 24-48 Hours) ```bash # Bridge line appears after Tor publishes to BridgeDB docker exec obfs4-bridge bridge-line ``` ### ✅ Success Criteria - [ ] Container starts successfully - [ ] Logs show "Using mounted configuration" - [ ] Fingerprint matches backup (CRITICAL!) - [ ] Bootstrap reaches 100% - [ ] Bridge line available after 24-48 hours --- ## 🚨 Troubleshooting ### Issue 1: "Permission denied" on startup **Symptom**: ``` Directory /var/lib/tor cannot be read: Permission denied ``` **Cause**: Volume ownership not fixed (still UID 101) **Fix**: ```bash docker stop obfs4-bridge docker run --rm -v obfs4-data:/data alpine:3.23.3 chown -R 100:101 /data docker start obfs4-bridge ``` ### Issue 2: Different fingerprint after migration **Symptom**: Fingerprint changed, new bridge identity **Cause**: Bridge keys not preserved **Fix**: Restore from backup: ```bash docker stop obfs4-bridge docker run --rm -v obfs4-data:/data -v /tmp:/backup alpine:3.23.3 \ sh -c 'rm -rf /data/* && tar xzf /backup/obfs4-data-backup-*.tar.gz -C / && chown -R 100:101 /data' docker start obfs4-bridge ``` ### Issue 3: "TOR_CONTACT_INFO contains invalid characters" **Symptom**: Container in restart loop with validation error **Cause**: Fixed in v1.1.1 - validation was too strict **Solution**: Use mounted config instead of ENV variables (see Step 3-5 above) ### Issue 4: Container restart loop (general) **Debug**: ```bash # Check logs docker logs obfs4-bridge --tail 50 # Check if it's actually using new image docker inspect obfs4-bridge --format='{{.Image}}' docker images r3bo0tbx1/onion-relay:latest --format='{{.ID}}' # IDs must match! # If IDs don't match: remove and recreate container docker stop obfs4-bridge docker rm obfs4-bridge # Then redeploy ``` ### Issue 5: Health check failing **Symptom**: Container marked unhealthy **Cause**: Old health check used hardcoded path **Fix**: v1.1.1 includes smart healthcheck.sh that works with both mounted and ENV configs. Just update to 1.1.1 image. --- ## 📊 Comparison: ENV vs Mounted Config | Aspect | ENV Variables | Mounted Config | |--------|---------------|----------------| | **Complexity** | Medium | Simple | | **Validation** | Strict (can cause issues) | Minimal (tor validates) | | **Debugging** | Harder | Easier (just cat file) | | **Updates** | Restart container | Edit file + restart | | **Recommended** | No ⚠️ | **Yes** ✅ | | **Use Case** | Quick testing | Production | **Recommendation**: Always use mounted config for production deployments. --- ## 🎯 Pre-Migration Checklist ### For All Migrations - [ ] Current fingerprint saved to file - [ ] Volumes backed up with timestamps - [ ] Old container can be removed (stopped first) - [ ] Firewall rules documented - [ ] Testing plan prepared ### For Bridge from Official Image - [ ] Volume ownership fix command prepared - [ ] bridge.conf file created and customized - [ ] Backup includes pt_state/obfs4_state.json - [ ] Expected fingerprint documented ### Post-Migration - [ ] Fingerprint matches backup - [ ] Container starts without errors - [ ] Bootstrap reaches 100% - [ ] Diagnostic tools work (status, health, fingerprint, gen-family) - [ ] Monitoring updated (if applicable) --- ## 📚 Additional Resources - **Security Policy**: `../SECURITY.md` - **General Migration Guide**: `MIGRATION.md` - **Troubleshooting**: `docs/TROUBLESHOOTING-BRIDGE-MIGRATION.md` - **Tools Documentation**: `docs/TOOLS.md` - **Deployment Guide**: `docs/DEPLOYMENT.md` --- ## 🔒 Security Notes ### What's Fixed in v1.1.1 1. **Command Injection** (CRITICAL): OBFS4V_* variables now validated with whitelist 2. **Health Check** (CRITICAL): Works with both mounted and ENV configs 3. **Input Validation** (CRITICAL): Comprehensive validation with whitespace trimming 4. **Permissions** (HIGH): Removed non-functional chown attempt 5. **Contact Info Validation** (HIGH): Fixed false positives ### Risk Assessment **Before Migration**: MEDIUM (using v1.1.0 with known vulnerabilities) **After Migration**: LOW (all critical vulnerabilities patched) **Recommendation**: Migrate as soon as possible to address security fixes. --- *Last Updated: 2025-12-05* *Validated with production deployments*