fix(e2e): never skip onion suites; unpin entry guards in test envs

The Playwright onion gate probed the app onion over Tor and silently
SKIPPED the whole suite when it did not answer — a broken onion could
go green. Remove the gate: the probe now waits hard (40x15s, covering
normal 2-5 min descriptor publication) and an unreachable onion fails
the deploy loudly. Reachability is part of what is under test.

The readiness probe treated transport-level failures (status: -1) as
'< 500', satisfying its until condition on attempt 1 and never using
its retries (nextcloud [0,1] died to TLSV1_UNRECOGNIZED_NAME this way).
Require a real HTTP status >= 100 so transport errors retry.

Tor pins one long-lived entry guard; its own log showed ours degraded
('failing more circuits than usual ... 38 collapsed, 21 timed out'),
which throttles every flow of the node at once — the most plausible
cause of the 89-minute descriptor-publication outage and the
intermittent ERR_SOCKS_CONNECTION_FAILED in E2E. New config key
services.tor.use_entry_guards (default true keeps the Tor default and
production anonymity posture); the matrix variant pins it false so test
deployments render UseEntryGuards 0 and a single bad relay cannot stall
the node. Anonymity is not under test there, only reachability.

Verified: full svc-net-tor closure deploy over onion with the gateless
semantics, failed=0, every onion suite executed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 4.8 committed 2026-07-16 20:02:29 +02:00
1 parent d492cc038e
commit 7511852bd1
6 files changed
+23 -19

No files matched your search

+3
View File
@@ -17,6 +17,9 @@ tor:
version: trixie-slim
name: tor
node: ""
# Tor default (guard pinning). Test inventories disable it so one degraded
# entry guard cannot stall the whole node; anonymity is not under test there.
use_entry_guards: true
ports:
local:
socks: 9050
+9
View File
@@ -0,0 +1,9 @@
---
# Matrix-deploy variant overrides for svc-net-tor.
# See docs/contributing/artefact/files/role/variants.md for the file format.
# Test deployments run without guard pinning so a single degraded entry guard
# cannot stall the whole node; production keeps the Tor default (true).
- services:
tor:
use_entry_guards: false # nocheck: variants-static-override
+4
View File
@@ -17,6 +17,10 @@ SocksPolicy reject *
ControlPort 127.0.0.1:9051
CookieAuthentication 1
{% if not (TOR_USE_ENTRY_GUARDS | bool) %}
UseEntryGuards 0
{% endif %}
{% if TOR_EGRESS_ROUTER_ENABLED | bool %}
AutomapHostsOnResolve 1
AutomapHostsSuffixes .onion
+1
View File
@@ -14,6 +14,7 @@ TOR_CUSTOM_IMAGE: "tor_custom"
TOR_CUSTOM_VERSION: "{{ TOR_VERSION }}"
TOR_NODE_ONION: "{{ lookup('config', application_id, 'services.tor.node', '') }}"
TOR_USE_ENTRY_GUARDS: "{{ lookup('config', application_id, 'services.tor.use_entry_guards') | bool }}"
TOR_TRANS_PORT: "{{ lookup('config', application_id, 'services.tor.ports.local.trans') }}"
TOR_DNS_PORT: "{{ lookup('config', application_id, 'services.tor.ports.local.dns') }}"
TOR_DNSMASQ_CONF: "/etc/dnsmasq.d/tor-onion.conf"
+5 -18
View File
@@ -263,7 +263,10 @@
register: _wait_result
retries: "{{ TEST_E2E_PLAYWRIGHT_WAIT_RETRIES }}"
delay: "{{ TEST_E2E_PLAYWRIGHT_WAIT_DELAY }}"
until: _wait_result.status is defined and _wait_result.status | int < 500
until: >-
_wait_result.status is defined and
_wait_result.status | int >= 100 and
_wait_result.status | int < 500
when:
- TEST_E2E_PLAYWRIGHT_WAIT_ENABLED | bool
# .onion targets are only reachable via Tor; the HTTP readiness probe cannot
@@ -291,7 +294,7 @@
{{ ('socks5://' ~ lookup('tor_socks', 'host.docker.internal')) if (_pw_is_onion | bool)
else '' }}
- name: Probe onion reachability over Tor
- name: Wait until the onion answers over Tor
ansible.builtin.command:
argv:
- curl
@@ -307,26 +310,11 @@
- "http://{{ lookup('domain', application_id) }}/" # nocheck: literal-protocol-lookup -- .onion is plaintext-only (no TLS), via Tor SOCKS
register: _pw_onion_probe
changed_when: false
failed_when: false
retries: "{{ TEST_E2E_PLAYWRIGHT_ONION_PROBE_RETRIES | int }}"
delay: "{{ TEST_E2E_PLAYWRIGHT_ONION_PROBE_DELAY | int }}"
until: _pw_onion_probe.rc == 0
ignore_errors: true
when: _pw_is_onion | bool
- name: Decide whether to run Playwright (skip onion apps Tor cannot reach)
ansible.builtin.set_fact:
_pw_run: "{{ (not (_pw_is_onion | bool)) or (_pw_onion_probe.rc | default(1) == 0) }}"
- name: Note skipped Playwright run (onion unreachable over Tor)
ansible.builtin.debug:
msg: >-
SKIP Playwright for {{ application_id }}: onion
{{ lookup('domain', application_id) }} did not answer over Tor after
{{ TEST_E2E_PLAYWRIGHT_ONION_PROBE_RETRIES }} attempts (Tor not
bootstrapped or hidden service not yet published). Deploy not failed.
when: (_pw_is_onion | bool) and not (_pw_run | bool)
- name: Run Playwright container
ansible.builtin.command:
argv:
@@ -378,7 +366,6 @@
register: _playwright_run
changed_when: false
failed_when: false
when: _pw_run | bool
- name: Print Playwright output (on failure)
ansible.builtin.debug:
+1 -1
View File
@@ -31,7 +31,7 @@ TEST_E2E_PLAYWRIGHT_WORKERS: "{{ lookup('playwright_workers') }}"
TEST_E2E_PLAYWRIGHT_WAIT_ENABLED: true
TEST_E2E_PLAYWRIGHT_WAIT_RETRIES: 30
TEST_E2E_PLAYWRIGHT_WAIT_DELAY: 5
TEST_E2E_PLAYWRIGHT_ONION_PROBE_RETRIES: 8
TEST_E2E_PLAYWRIGHT_ONION_PROBE_RETRIES: 40
TEST_E2E_PLAYWRIGHT_ONION_PROBE_DELAY: 15
TEST_E2E_PLAYWRIGHT_NAVIGATION_TIMEOUT: 60000
TEST_E2E_PLAYWRIGHT_ONION_NAVIGATION_TIMEOUT: 180000