Files
archy/scripts/test-tor-rotation.sh
Dorian 1068267c5a 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>
2026-03-13 03:32:21 +00:00

116 lines
4.7 KiB
Bash
Executable File

#!/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