fix: federation client accepts self-signed TLS certificates

httpx.AsyncClient used default SSL verification, rejecting self-signed
certs from muxplex setup-tls. Remote HTTPS instances (e.g., setup-tls
with self-signed fallback) were unreachable — all federation requests
failed with CERTIFICATE_VERIFY_FAILED. Fix: verify=False on the
federation client. Bearer token auth still protects authorization.
This commit is contained in:
Brian Krabach
2026-04-06 04:57:23 -07:00
parent 7fb803c6b5
commit 2c21dfeb3e
2 changed files with 34 additions and 1 deletions
+8 -1
View File
@@ -176,7 +176,14 @@ async def lifespan(app: FastAPI):
except Exception: except Exception:
pass # tmux not running at startup is OK; hook will be set on first poll pass # tmux not running at startup is OK; hook will be set on first poll
app.state.federation_client = httpx.AsyncClient(timeout=5.0, follow_redirects=False) app.state.federation_client = httpx.AsyncClient(
timeout=5.0,
follow_redirects=False,
verify=False, # nosec B501 — muxplex is a dev tool for LAN/Tailscale use;
# self-signed certs from `muxplex setup-tls` must be accepted for federation.
# Bearer token auth handles authorization. Users who need cert verification
# should use mkcert (CA-trusted) or Tailscale (LE-trusted) certs.
)
yield yield
+26
View File
@@ -1583,6 +1583,32 @@ def test_federation_client_exists_on_app_state(monkeypatch):
) )
def test_federation_client_disables_ssl_verification(monkeypatch):
"""Federation httpx client must disable SSL verification for self-signed certs.
muxplex setup-tls can generate self-signed certificates. When a remote instance
uses such a cert, httpx's default SSL verification rejects it with
CERTIFICATE_VERIFY_FAILED, making the remote unreachable in federation.
The client must use verify=False so self-signed certs on LAN/Tailscale remotes
are accepted. Bearer token auth still protects authorization.
"""
import ssl
monkeypatch.setenv("MUXPLEX_PASSWORD", "test-password")
with TestClient(app):
client = app.state.federation_client
# httpx.AsyncClient(verify=False) creates a transport whose SSL context
# has verify_mode=ssl.CERT_NONE (0). If verify=True (default), it would
# use CERT_REQUIRED (2).
ssl_context = client._transport._pool._ssl_context
assert ssl_context is not None, "federation_client SSL context must be present"
assert ssl_context.verify_mode == ssl.CERT_NONE, (
"federation_client must use verify=False (CERT_NONE) "
"to support self-signed certificates on remote instances"
)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# GET /api/federation/sessions (task-5) # GET /api/federation/sessions (task-5)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------