feat: fix Tor rotation to handle system Tor and hostname caching
read_onion_address() now checks tor-hostnames readable cache first, clears cache before wait_for_hostname, updates it after rotation. Rotation restarts system Tor (not just archy-tor container). Created test-tor-rotation.sh with 10 automated checks (INSTALL-03). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
115
scripts/test-tor-rotation.sh
Executable file
115
scripts/test-tor-rotation.sh
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-tor-rotation.sh — Validate Tor address rotation end-to-end
|
||||
#
|
||||
# Tests: rotation, old/new address comparison, cache update, cleanup,
|
||||
# federation propagation (fire-and-forget), Nostr publish (fire-and-forget).
|
||||
#
|
||||
# Usage: ./scripts/test-tor-rotation.sh [target-ip]
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
TARGET="${1:-192.168.1.228}"
|
||||
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
|
||||
SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o ConnectTimeout=10 archipelago@$TARGET"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
check() {
|
||||
local name="$1"
|
||||
local ok="$2"
|
||||
if [ "$ok" = "true" ]; then
|
||||
echo " ✅ $name"
|
||||
((PASS++))
|
||||
else
|
||||
echo " ❌ $name"
|
||||
((FAIL++))
|
||||
fi
|
||||
}
|
||||
|
||||
json_get() {
|
||||
python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',{}); print(r.get('$1','') if isinstance(r,dict) else '')" 2>/dev/null
|
||||
}
|
||||
|
||||
echo "🔄 Tor Address Rotation Test — $TARGET"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Login
|
||||
echo ""
|
||||
echo "Authenticating..."
|
||||
$SSH "curl -s -c /tmp/cookiejar http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{\"method\":\"auth.login\",\"params\":{\"password\":\"password123\"}}'" >/dev/null 2>&1
|
||||
CSRF=$($SSH "grep csrf_token /tmp/cookiejar 2>/dev/null | awk '{print \$NF}'" 2>/dev/null)
|
||||
|
||||
rpc() {
|
||||
local method="$1"
|
||||
local params="${2:-}"
|
||||
local body
|
||||
if [ -n "$params" ]; then
|
||||
body="{\"method\":\"$method\",\"params\":$params}"
|
||||
else
|
||||
body="{\"method\":\"$method\"}"
|
||||
fi
|
||||
$SSH "curl -s -b /tmp/cookiejar -H 'Content-Type: application/json' -H 'X-CSRF-Token: $CSRF' http://localhost:5678/rpc/v1 -d '$body'" 2>/dev/null
|
||||
}
|
||||
|
||||
# 1. Record current address
|
||||
echo ""
|
||||
echo "1. Current Tor address"
|
||||
BEFORE_RESP=$(rpc "node.tor-address")
|
||||
OLD_ADDR=$(echo "$BEFORE_RESP" | json_get "tor_address")
|
||||
check "Has valid .onion address" "$(echo "$OLD_ADDR" | grep -q '.onion$' && echo true || echo false)"
|
||||
echo " Address: ${OLD_ADDR:-<none>}"
|
||||
|
||||
# 2. Rotate service
|
||||
echo ""
|
||||
echo "2. Rotating address (may take up to 60s)..."
|
||||
ROTATE_RESP=$(rpc "tor.rotate-service" "{\"name\":\"archipelago\"}")
|
||||
ROTATED=$(echo "$ROTATE_RESP" | json_get "rotated")
|
||||
NEW_ADDR=$(echo "$ROTATE_RESP" | json_get "new_onion")
|
||||
OLD_REPORTED=$(echo "$ROTATE_RESP" | json_get "old_onion")
|
||||
check "Rotation succeeded" "$([ "$ROTATED" = "True" ] || [ "$ROTATED" = "true" ] && echo true || echo false)"
|
||||
check "New address different from old" "$([ -n "$NEW_ADDR" ] && [ "$NEW_ADDR" != "$OLD_ADDR" ] && echo true || echo false)"
|
||||
check "Old address reported correctly" "$([ "$OLD_REPORTED" = "$OLD_ADDR" ] && echo true || echo false)"
|
||||
echo " Old: $OLD_ADDR"
|
||||
echo " New: $NEW_ADDR"
|
||||
|
||||
# 3. Verify address updated in node.tor-address
|
||||
echo ""
|
||||
echo "3. Address updated everywhere"
|
||||
AFTER_RESP=$(rpc "node.tor-address")
|
||||
AFTER_ADDR=$(echo "$AFTER_RESP" | json_get "tor_address")
|
||||
check "node.tor-address returns new address" "$([ "$AFTER_ADDR" = "$NEW_ADDR" ] && echo true || echo false)"
|
||||
|
||||
# Check tor-hostnames cache
|
||||
CACHE_ADDR=$($SSH "cat /var/lib/archipelago/tor-hostnames/archipelago 2>/dev/null" 2>/dev/null | tr -d '[:space:]')
|
||||
check "tor-hostnames cache updated" "$([ "$CACHE_ADDR" = "$NEW_ADDR" ] && echo true || echo false)"
|
||||
|
||||
# Check actual hostname file
|
||||
ACTUAL_ADDR=$($SSH "sudo cat /var/lib/archipelago/tor/hidden_service_archipelago/hostname 2>/dev/null" 2>/dev/null | tr -d '[:space:]')
|
||||
check "Actual hostname file matches" "$([ "$ACTUAL_ADDR" = "$NEW_ADDR" ] && echo true || echo false)"
|
||||
|
||||
# 4. Old directory preserved for transition
|
||||
echo ""
|
||||
echo "4. Transition period"
|
||||
OLD_DIRS=$($SSH "sudo ls /var/lib/archipelago/tor/ 2>/dev/null | grep '_old_' | wc -l" 2>/dev/null | tr -d '[:space:]')
|
||||
check "Old service directory preserved" "$([ "$OLD_DIRS" -ge 1 ] && echo true || echo false)"
|
||||
|
||||
# 5. Cleanup (should not remove non-expired dirs)
|
||||
echo ""
|
||||
echo "5. Cleanup (non-expired)"
|
||||
CLEANUP_RESP=$(rpc "tor.cleanup-rotated")
|
||||
CLEANED=$(echo "$CLEANUP_RESP" | json_get "count")
|
||||
check "Cleanup skips non-expired dirs" "$([ "$CLEANED" = "0" ] && echo true || echo false)"
|
||||
|
||||
# 6. Federation peer propagation (verify it was attempted)
|
||||
echo ""
|
||||
echo "6. Propagation (fire-and-forget)"
|
||||
FED_RESP=$(rpc "federation.list-nodes")
|
||||
PEER_COUNT=$(echo "$FED_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('result',{}).get('nodes',[])))" 2>/dev/null)
|
||||
check "Federation peers exist for propagation ($PEER_COUNT peers)" "$([ "$PEER_COUNT" -ge 1 ] && echo true || echo false)"
|
||||
echo " (Propagation is fire-and-forget — peers notified via old Tor address)"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Results: $PASS passed, $FAIL failed"
|
||||
|
||||
[ $FAIL -eq 0 ] && exit 0 || exit 1
|
||||
Reference in New Issue
Block a user