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>
116 lines
4.7 KiB
Bash
Executable File
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
|