From 0aaaec6e3830d4fb8c7ff38f63d67921d5973d37 Mon Sep 17 00:00:00 2001 From: mp0rta <3p0rta26@gmail.com> Date: Thu, 8 Oct 2026 03:17:49 +0900 Subject: [PATCH] test(e2e): drop C-era leftovers from the integration scripts - e2e_gateway case 14: keep only the client-side X-Mq-Cache reject; the cache HIT/no-store/bypass branch and the origin's /__count counter never ran against the Rust server. - e2e_mitm_h2: run cases g and h by default; they were opt-in only because the C binary failed them. Delete the C/Rust interop wrapper. - Stop passing the ignored --cache-max-bytes to the test servers. - generate-mitm.sh: regenerate a CA key that is not PKCS#8 instead of keeping a stale one the Rust loader rejects. --- tests/certs/generate-mitm.sh | 2 +- tests/integration/e2e_gateway.sh | 198 +------------------ tests/integration/e2e_mitm_h2.sh | 12 +- tests/integration/mqproxy-interop-wrapper.sh | 3 - 4 files changed, 15 insertions(+), 200 deletions(-) delete mode 100755 tests/integration/mqproxy-interop-wrapper.sh diff --git a/tests/certs/generate-mitm.sh b/tests/certs/generate-mitm.sh index 8e214472..3fddb587 100644 --- a/tests/certs/generate-mitm.sh +++ b/tests/certs/generate-mitm.sh @@ -2,7 +2,7 @@ # Disposable test CA; never install it into a machine's trust store. set -euo pipefail cd "$(dirname "$0")" -if [[ -s mitm-ca.crt && -s mitm-ca.key ]]; then exit 0; fi +if [[ -s mitm-ca.crt ]] && grep -q "BEGIN PRIVATE KEY" mitm-ca.key 2>/dev/null; then exit 0; fi umask 077 openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out mitm-ca.key openssl req -x509 -new -key mitm-ca.key -out mitm-ca.crt -days 3650 \ diff --git a/tests/integration/e2e_gateway.sh b/tests/integration/e2e_gateway.sh index 0b8c5f57..14c6c9f3 100755 --- a/tests/integration/e2e_gateway.sh +++ b/tests/integration/e2e_gateway.sh @@ -139,7 +139,7 @@ trap cleanup EXIT INT TERM # interpolated into the python source. write_origin_py() { cat >"${WORK}/origin.py" <<'PY' -import gzip, http.server, os, ssl, urllib.parse +import gzip, http.server, os, ssl ROOT = os.environ["MQ_ORIGIN_ROOT"] SAVE = os.environ["MQ_ORIGIN_SAVE"] @@ -151,12 +151,6 @@ KEY = os.environ["MQ_ORIGIN_KEY"] class H(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" - # Per-path GET counter (cache e2e case 14). Counts FILE fetches only — the - # /__count introspection endpoint never bumps it, so /__count?p= reads - # back exactly how many times the origin actually served . The cache - # HIT proof is "fetch twice, /__count must read 1" (the HIT never reached us). - COUNTS = {} - def log_message(self, *a): pass @@ -184,22 +178,6 @@ class H(http.server.BaseHTTPRequestHandler): self.end_headers() self.wfile.write(raw) return - if self.path.split("?", 1)[0] == "/__count": - # Introspection: ?p= → body is how many times the origin - # served that FILE path (this endpoint itself is NOT counted, and is - # fetched with NO X-Mq-Cache so it is never itself cached). Used by - # the cache HIT proof (case 14): a HIT must keep the count at 1. - qs = urllib.parse.urlparse(self.path).query - p = urllib.parse.parse_qs(qs).get("p", [""])[0] - body = str(H.COUNTS.get(p, 0)).encode() - self.send_response(200) - self.send_header("Content-Type", "text/plain") - self.send_header("Content-Length", str(len(body))) - self.end_headers() - self.wfile.write(body) - return - # FILE path: count this origin hit (NOT /__count, handled above). - H.COUNTS[self.path] = H.COUNTS.get(self.path, 0) + 1 path = os.path.join(ROOT, os.path.basename(self.path)) if not os.path.isfile(path): self.send_response(404) @@ -211,10 +189,6 @@ class H(http.server.BaseHTTPRequestHandler): self.send_response(200) self.send_header("Content-Type", "application/octet-stream") self.send_header("Content-Length", str(len(data))) - # no-store mode: a basename starting "nostore-" is served with - # Cache-Control: no-store so mqproxy must NOT cache it (case 14). - if os.path.basename(self.path).startswith("nostore-"): - self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(data) @@ -307,7 +281,6 @@ start_server() { --cert "${MQPROXY_CERT}" --key "${MQPROXY_KEY}" \ --origin-ca "${ORIGIN_CA}" \ --request-metrics \ - --cache-max-bytes 67108864 \ >"${WORK}/server.log" 2>&1 & SERVER_PID=$! } @@ -514,8 +487,8 @@ wait_gateway_ready || exit 1 # the same origin authority (https://127.0.0.1:${ORIGIN_PORT}). # RESERVED sizes — the mq.req anchors below grep server.log by resp_bytes, so each # reuse-case body size MUST be unique across the whole script: 3MiB/1MiB (case 9), -# 2MiB/512KiB (case 10), 8MiB (big.bin, cases 1/2/8), 4MiB (case 13), -# 1.5MiB/640KiB (case 14: cache_hit.bin / nostore-x.bin). A new case reusing one of +# 2MiB/512KiB (case 10), 8MiB (big.bin, cases 1/2/8), 4MiB (case 13). +# A new case reusing one of # these sizes in the same server.log epoch could mis-anchor (grep|tail -1). A_SIZE=3145728 # 3 MiB B_SIZE=1048576 # 1 MiB @@ -664,169 +637,18 @@ if [ "${c13_found}" -ne 1 ]; then fi ok 13 "X-Mq-Origin-Protocol: invalid→400 (+x-mq-error); h1→honored (origin_protocol=h1, wiring smoke not switch-proof)" -# ── case 14: X-Mq-Cache opt-in response cache (no root required) ────────────── -# Proves the minimal opt-in server-side cache end-to-end with a FALSIFIABLE -# origin-once assertion: -# HIT — fetch a cacheable file twice with X-Mq-Cache: 60 → miss then hit, -# and the origin served it EXACTLY ONCE (/__count == 1, the HIT never -# reached the origin); both downloaded bodies byte-identical. -# no-store — an origin Cache-Control: no-store response is NEVER cached: two -# X-Mq-Cache fetches both miss and the origin is hit twice (== 2). -# bypass — a normal fetch with NO X-Mq-Cache reports cache=bypass. -# invalid — X-Mq-Cache: bogus is rejected client-side with 400 + x-mq-error. -# -# Restart server+client clean so the in-memory cache + server.log start EMPTY -# (cache state does not survive a server restart; the HIT proof needs the SAME -# server instance to see both fetches — the whole case runs against this one). -stop_client -kill "${SERVER_PID}" 2>/dev/null; wait "${SERVER_PID}" 2>/dev/null; SERVER_PID="" -start_server "" || exit 1 -start_client || exit 1 -wait_gateway_ready || exit 1 - -# Reserved unique sizes (< 4 MiB per-object cap): cache_hit.bin = 1.5 MiB, -# nostore-x.bin = 640 KiB. Distinct from every other case's resp_bytes anchor. -C14_HIT=1572864 # 1.5 MiB -C14_NS=655360 # 640 KiB -head -c "${C14_HIT}" /dev/urandom >"${WORK}/cache_hit.bin" -head -c "${C14_NS}" /dev/urandom >"${WORK}/nostore-x.bin" - -# Spec §10.4: the Rust server has no response cache (it warns "origin response -# cache was removed" for --cache-max-bytes), so a-c are skipped against it. -# (d) is a client-side reject and always runs. -C14_RAN="14" -if grep -q "origin response cache was removed" "${WORK}/server.log"; then - note "case 14 (a-c) skipped: server has no response cache (origin response cache was removed); (d) still runs." - C14_RAN="14d (14a-c skipped: no server cache)" -else - # ── (a) HIT: fetch the cacheable file twice → miss, then hit ────────────────── - RESP14A1="${WORK}/c14a1_resp.bin" - code14a1="$(curl -s -o "${RESP14A1}" -w '%{http_code}' --max-time 30 \ - -X POST "http://${GW}/_mqproxy/fetch" \ - -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/cache_hit.bin" \ - -H "X-Mq-Cache: 60")" - [ "${code14a1}" = "200" ] || fail 14 "(a) first cacheable fetch HTTP code = ${code14a1} (want 200)" - - # First fetch must be a miss (anchored to resp_bytes=1572864 cache=miss; field - # order is resp_bytes=… … cache=… so anchor left-to-right). - c14_miss_found=0 - for _ in $(seq 1 25); do - if grep -Eq 'mq\.req .* resp_bytes=1572864 .* cache=miss' "${WORK}/server.log"; then - c14_miss_found=1; break - fi - sleep 0.2 - done - if [ "${c14_miss_found}" -ne 1 ]; then - note "case 14 FAIL: no mq.req with resp_bytes=1572864 cache=miss in server.log" - grep -E 'mq\.req ' "${WORK}/server.log" >&2 2>/dev/null || true - exit 1 - fi - - RESP14A2="${WORK}/c14a2_resp.bin" - code14a2="$(curl -s -o "${RESP14A2}" -w '%{http_code}' --max-time 30 \ - -X POST "http://${GW}/_mqproxy/fetch" \ - -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/cache_hit.bin" \ - -H "X-Mq-Cache: 60")" - [ "${code14a2}" = "200" ] || fail 14 "(a) second cacheable fetch HTTP code = ${code14a2} (want 200)" - - # Second fetch must be a HIT served from cache (anchored resp_bytes=1572864 cache=hit). - c14_hit_found=0 - for _ in $(seq 1 25); do - if grep -Eq 'mq\.req .* resp_bytes=1572864 .* cache=hit' "${WORK}/server.log"; then - c14_hit_found=1; break - fi - sleep 0.2 - done - if [ "${c14_hit_found}" -ne 1 ]; then - note "case 14 FAIL: no mq.req with resp_bytes=1572864 cache=hit in server.log (HIT never served)" - grep -E 'mq\.req .* cache=' "${WORK}/server.log" >&2 2>/dev/null || true - exit 1 - fi - - # The HIT replay must be byte-identical to the origin's body (and to the miss). - cmp -s "${WORK}/cache_hit.bin" "${RESP14A1}" || fail 14 "(a) miss body differs from origin" - cmp -s "${RESP14A1}" "${RESP14A2}" || fail 14 "(a) HIT body differs from the cached miss body" - - # FALSIFIABLE origin-once proof: the origin counts each FILE GET it serves. After - # two gateway fetches (1 miss + 1 cache-served hit) the origin must have served - # /cache_hit.bin EXACTLY ONCE. Fetch /__count WITHOUT X-Mq-Cache so the count - # read itself is never cached (it returns cache=bypass). - count14="$(curl -s --max-time 20 -X POST "http://${GW}/_mqproxy/fetch" -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/__count?p=/cache_hit.bin")" - [ "${count14}" = "1" ] || fail 14 "(a) origin hit count for /cache_hit.bin = '${count14}' (want 1 — the HIT must NOT reach origin)" - ok 14 "X-Mq-Cache HIT: miss→hit, body byte-identical, origin served exactly once" - - # ── (b) no-store: an origin no-store response is never cached ───────────────── - code14b1="$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 \ - -X POST "http://${GW}/_mqproxy/fetch" \ - -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/nostore-x.bin" \ - -H "X-Mq-Cache: 60")" - [ "${code14b1}" = "200" ] || fail 14 "(b) first no-store fetch HTTP code = ${code14b1} (want 200)" - code14b2="$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 \ - -X POST "http://${GW}/_mqproxy/fetch" \ - -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/nostore-x.bin" \ - -H "X-Mq-Cache: 60")" - [ "${code14b2}" = "200" ] || fail 14 "(b) second no-store fetch HTTP code = ${code14b2} (want 200)" - - # BOTH fetches must be misses (Cache-Control: no-store ⇒ never stored). Anchor to - # the unique resp_bytes=655360 and require at least two cache=miss lines for it. - c14_ns_misses=0 - for _ in $(seq 1 25); do - c14_ns_misses="$(grep -Ec 'mq\.req .* resp_bytes=655360 .* cache=miss' "${WORK}/server.log")" - [ "${c14_ns_misses}" -ge 2 ] && break - sleep 0.2 - done - if [ "${c14_ns_misses}" -lt 2 ]; then - note "case 14 FAIL: expected 2 cache=miss for resp_bytes=655360 (no-store), got ${c14_ns_misses}" - grep -E 'mq\.req .* resp_bytes=655360 ' "${WORK}/server.log" >&2 2>/dev/null || true - exit 1 - fi - # And the origin must have been hit BOTH times (== 2, not deduplicated by cache). - count14ns="$(curl -s --max-time 20 -X POST "http://${GW}/_mqproxy/fetch" -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/__count?p=/nostore-x.bin")" - [ "${count14ns}" = "2" ] || fail 14 "(b) origin hit count for /nostore-x.bin = '${count14ns}' (want 2 — no-store must not cache)" - ok 14 "X-Mq-Cache no-store: both fetches miss, origin hit twice (not cached)" - - # ── (c) bypass: a normal fetch with NO X-Mq-Cache reports cache=bypass ──────── - # Reuse cache_hit.bin (already cached) WITHOUT opting in: a no-opt-in request must - # bypass the cache entirely (cache=bypass), proving opt-in gating. The origin hit - # count for /cache_hit.bin will rise to 2 here, which is fine — the HIT proof in - # (a) already locked in the count==1 assertion before this fetch. - code14c="$(curl -s -o /dev/null -w '%{http_code}' --max-time 30 \ - -X POST "http://${GW}/_mqproxy/fetch" \ - -H "${AUTH}" \ - -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/cache_hit.bin")" - [ "${code14c}" = "200" ] || fail 14 "(c) bypass fetch HTTP code = ${code14c} (want 200)" - c14_bypass_found=0 - for _ in $(seq 1 25); do - if grep -Eq 'mq\.req .* resp_bytes=1572864 .* cache=bypass' "${WORK}/server.log"; then - c14_bypass_found=1; break - fi - sleep 0.2 - done - if [ "${c14_bypass_found}" -ne 1 ]; then - note "case 14 FAIL: no mq.req with resp_bytes=1572864 cache=bypass (no-opt-in fetch)" - grep -E 'mq\.req .* cache=' "${WORK}/server.log" >&2 2>/dev/null || true - exit 1 - fi - ok 14 "X-Mq-Cache bypass: no opt-in ⇒ cache=bypass" -fi - -# ── (d) invalid TTL → 400 + x-mq-error: bad-cache-ttl (client-side reject) ──── +# ── case 14: invalid X-Mq-Cache → 400 + x-mq-error: bad-cache-ttl ─────────── +# The response cache was removed, but the client still validates the header. # Mirror case 13a: a code-only check would pass on ANY 400, so assert BOTH the # 400 code AND the specific x-mq-error reason emitted by the client. H14D="${WORK}/c14d_headers.txt" code14d="$(curl -s -o /dev/null -D "${H14D}" -w '%{http_code}' --max-time 20 \ -X POST "http://${GW}/_mqproxy/fetch" \ - -H "${AUTH}" -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/cache_hit.bin" \ + -H "${AUTH}" -H "X-Mq-Target: https://127.0.0.1:${ORIGIN_PORT}/big.bin" \ -H "X-Mq-Cache: bogus")" -[ "${code14d}" = "400" ] || fail 14 "(d) invalid X-Mq-Cache: code=${code14d} (want 400)" +[ "${code14d}" = "400" ] || fail 14 "invalid X-Mq-Cache: code=${code14d} (want 400)" grep -qi '^x-mq-error:[[:space:]]*bad-cache-ttl' "${H14D}" || \ - fail 14 "(d) invalid X-Mq-Cache: x-mq-error missing; got: $(grep -i x-mq-error "${H14D}" || echo '')" + fail 14 "invalid X-Mq-Cache: x-mq-error missing; got: $(grep -i x-mq-error "${H14D}" || echo '')" ok 14 "X-Mq-Cache invalid TTL → 400 + x-mq-error: bad-cache-ttl" # ── case 8: 2-path aggregation smoke (NET_ADMIN-gated) ─────────────────────── @@ -848,7 +670,7 @@ if [ "${can_tc}" -ne 1 ]; then exit 1 fi note "case 8 skipped (no NET_ADMIN): 2-path aggregation smoke needs tc on lo." - note "RESULT = PASS (cases 1-7 + cases 9 + 11 + 12 + 13 + ${C14_RAN}; case 8 skipped)." + note "RESULT = PASS (cases 1-7 + cases 9 + 11 + 12 + 13 + 14; case 8 skipped)." exit 0 fi @@ -1075,5 +897,5 @@ else TC_ON=0 fi -note "RESULT = PASS (cases 1-9 + 11 + 12 + 13 + ${C14_RAN} + L2 case 10 ran under NET_ADMIN)." +note "RESULT = PASS (cases 1-9 + 11 + 12 + 13 + 14 + L2 case 10 ran under NET_ADMIN)." exit 0 diff --git a/tests/integration/e2e_mitm_h2.sh b/tests/integration/e2e_mitm_h2.sh index 24cad82a..9317c8c7 100755 --- a/tests/integration/e2e_mitm_h2.sh +++ b/tests/integration/e2e_mitm_h2.sh @@ -61,8 +61,7 @@ # offer h2 takes the OPAQUE path (origin cert verifies; the MITM CA is rejected). # (h) a 6 KiB Cookie and a 3 KiB URL succeed. # -# CASE SELECTION: MITM_CASES (default "a b c d e f"); set -# MITM_CASES="a b c d e f g h" to run all. +# CASE SELECTION: MITM_CASES (default: all, "a b c d e f g h"). # # HOW HOSTNAMES RESOLVE (both client AND server sides): # The H2 adapter forwards the browser's :authority verbatim; the gateway server @@ -111,14 +110,12 @@ # (NOTE the curl-CLI gotcha: libcurl-dev != the curl binary — install `curl`.) # # ENV (overridable): -# MQPROXY_BIN the `mqproxy` binary (MUST be MITM-capable; the Rust -# binary or, for interop, tests/integration/ -# mqproxy-interop-wrapper.sh). +# MQPROXY_BIN the `mqproxy` binary. # MQPROXY_CERT/KEY tunnel TLS cert/key (CN=mqproxy-test). # MQ_MITM_CA_CRT/KEY the MITM signing CA (configure-time fixtures) — # consumed by --ca-cert/--ca-key; curl trusts the crt. # The key must be PKCS#8 (the Rust loader's only format). -# MITM_CASES cases to run (default "a b c d e f"). +# MITM_CASES cases to run (default "a b c d e f g h"). # set -u @@ -133,7 +130,7 @@ MQPROXY_KEY="${MQPROXY_KEY:-${REPO_ROOT}/tests/certs/test.key}" MITM_CA_CRT="${MQ_MITM_CA_CRT:-${REPO_ROOT}/tests/certs/mitm-ca.crt}" MITM_CA_KEY="${MQ_MITM_CA_KEY:-${REPO_ROOT}/tests/certs/mitm-ca.key}" -MITM_CASES="${MITM_CASES:-a b c d e f}" +MITM_CASES="${MITM_CASES:-a b c d e f g h}" want() { case " ${MITM_CASES} " in *" $1 "*) return 0 ;; esac; return 1; } TOKEN="mitm-h2-e2e-token" @@ -489,7 +486,6 @@ start_server() { --cert "${MQPROXY_CERT}" --key "${MQPROXY_KEY}" \ --origin-ca "${ORIGIN_CA}" \ --request-metrics \ - --cache-max-bytes 67108864 \ >"${WORK}/server.log" 2>&1 & SERVER_PID=$! } diff --git a/tests/integration/mqproxy-interop-wrapper.sh b/tests/integration/mqproxy-interop-wrapper.sh deleted file mode 100755 index 6b13ea85..00000000 --- a/tests/integration/mqproxy-interop-wrapper.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -set -eu -case "$1" in client) exec "$MQPROXY_CLIENT_BIN" "$@";; server) exec "$MQPROXY_SERVER_BIN" "$@";; *) exec "$MQPROXY_SERVER_BIN" "$@";; esac