Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/actions/live-verification/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,12 @@ runs:
BOOTSTRAPPED=false
START=$SECONDS
while [ $((SECONDS - START)) -lt 180 ]; do
STATUS=$(docker exec torproxy-canary-gateway curl -fsS --connect-timeout 2 --max-time 5 http://127.0.0.1:8080/status 2>/dev/null | jq -r '.status // empty' || true)
if [ "$STATUS" = "connected" ]; then
# Bootstrap progress, not /status "connected": the latter
# requires the external check.torproject.org egress probe,
# whose intermittent unreachability flaked this step
# although Tor itself bootstrapped in seconds (PR #121).
PROGRESS=$(docker exec torproxy-canary-gateway curl -fsS --connect-timeout 2 --max-time 5 http://127.0.0.1:8080/health 2>/dev/null | jq -r '.tor.progress // empty' || true)
if [ "$PROGRESS" = "100" ]; then
echo "✅ Gateway Tor successfully connected!"
BOOTSTRAPPED=true
break
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/pr-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,14 @@ jobs:
BOOTSTRAPPED=false
START=$SECONDS
while [ $((SECONDS - START)) -lt 180 ]; do
STATUS=$(docker exec torproxy-gateway-smoke curl -fsS --connect-timeout 2 --max-time 5 http://127.0.0.1:8080/status 2>/dev/null | jq -r '.status // empty' || true)
if [ "$STATUS" = "connected" ]; then
# Wait for Tor bootstrap progress (100%), not /status
# "connected": the latter additionally requires the
# external check.torproject.org egress probe, whose
# intermittent unreachability from CI runners flaked
# this step although Tor itself bootstrapped in seconds
# (PR #121). Egress is verified separately below.
PROGRESS=$(docker exec torproxy-gateway-smoke curl -fsS --connect-timeout 2 --max-time 5 http://127.0.0.1:8080/health 2>/dev/null | jq -r '.tor.progress // empty' || true)
if [ "$PROGRESS" = "100" ]; then
echo "✅ Gateway container bootstrapped!"
BOOTSTRAPPED=true
break
Expand Down
20 changes: 17 additions & 3 deletions tests/integration/verify_gateway.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,24 @@ fi
echo "✅ No ESTABLISHED fail-closed bypass rule present."

echo "=== 2. Transparent Tor Egress Verification ==="
CLIENT_OUTPUT=$(docker run --rm --network "container:${GATEWAY_CONTAINER}" curlimages/curl:latest -sS --connect-timeout 15 --max-time 30 https://check.torproject.org/api/ip)
# Retry like the other canary egress checks: a single attempt against
# check.torproject.org over a freshly built circuit is flaky and
# produced a false red in the daily canary (curl exit 55) although
# the gateway itself was healthy.
CLIENT_OUTPUT=""
IS_TOR=""
EXIT_IP=""
for attempt in 1 2 3 4; do
echo "Transparent egress check attempt $attempt/4..."
CLIENT_OUTPUT=$(docker run --rm --network "container:${GATEWAY_CONTAINER}" curlimages/curl:latest -sS --connect-timeout 15 --max-time 30 --retry 2 --retry-all-errors https://check.torproject.org/api/ip 2>/dev/null || true)
IS_TOR=$(echo "$CLIENT_OUTPUT" | jq -r '.IsTor // empty' 2>/dev/null || true)
EXIT_IP=$(echo "$CLIENT_OUTPUT" | jq -r '.IP // empty' 2>/dev/null || true)
if [ "$IS_TOR" = "true" ] && [ -n "$EXIT_IP" ]; then
break
fi
sleep 3
done
echo "Client Response: $CLIENT_OUTPUT"
IS_TOR=$(echo "$CLIENT_OUTPUT" | jq -r '.IsTor // empty')
EXIT_IP=$(echo "$CLIENT_OUTPUT" | jq -r '.IP // empty')

if [ "$IS_TOR" != "true" ]; then
echo "❌ CRITICAL: Outgoing traffic was NOT identified as Tor egress!"
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/test_ci_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ def test_gateway_bootstrap_is_polled_inside_the_container_in_live_action() -> No
).read_text(encoding="utf-8")
assert "docker exec torproxy-canary-gateway curl" in live
assert "http://localhost:8089/status" not in live
# Bootstrap waits must use Tor progress (100%), not /status
# "connected", which additionally depends on the external
# check.torproject.org egress probe (PR #121 flake).
assert ".tor.progress" in live
assert (
"8080/status"
not in live.split("Verify Transparent Tor Gateway Mode")[1].split(
"Verify GeoIP Upstream Size Drift Guard"
)[0]
)


def test_pr_gate_runs_live_verification_before_merge() -> None:
Expand Down
Loading