scripts: require one certificate across the whole FIFA-facing TLS stack
Two live gates were lost to a second variable I had been asked to eliminate. The Rust redirector was pointed at the repo's fifa17-recon/tools/redir_cert.pem (fingerprint F9:16:1A...), while the running container serves a different cert baked into its image (E7:F9:46...) which the Python redirector, roster and the rest of the stack all share. So the A/B compared TLS implementation AND certificate identity at once. FIFA 17's ProtoSSL caches the server certificate for a backend. The redirector is the first TLS connection of a session, so its cert becomes the one the client expects; the next service presenting a different cert fails its handshake. That is why the redirect itself always succeeded and the failure surfaced later, on the roster fetch -- "An error occurred downloading the FUT Squad Update". It stayed invisible because Python's socketserver swallows it: a handshake failure at accept() raises ssl.SSLError, which subclasses OSError and is discarded by _handle_request_noblock. No request log, no stderr. Every server looked healthy while the client could not talk to any of them. Confirmed on the wire: tls-observe in front of the roster server captured four ClientHellos from the client, correct SNI and the same 8 static-RSA suites it offers the redirector, none of which produced a request. The check is mutation-tested against the real bug: with a redirector started on the stale repo cert it exits 1 and names the mismatch.
This commit is contained in:
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Every FIFA-facing TLS service must present the SAME certificate.
|
||||||
|
#
|
||||||
|
# check-tls-parity.sh <host:port> <host:port> [<host:port> ...]
|
||||||
|
#
|
||||||
|
# WHY
|
||||||
|
#
|
||||||
|
# FIFA 17's ProtoSSL caches the server certificate it saw for a backend. The
|
||||||
|
# redirector is the first TLS connection of a session, so whatever cert it
|
||||||
|
# presents becomes the one the client expects from that backend. If a later
|
||||||
|
# service — roster, UTAS — presents a different cert, that handshake fails.
|
||||||
|
#
|
||||||
|
# Python's socketserver makes this invisible: a handshake failure at accept()
|
||||||
|
# raises ssl.SSLError, which subclasses OSError and is silently swallowed by
|
||||||
|
# _handle_request_noblock. No request log, no stderr, nothing. The observable
|
||||||
|
# symptom is a client that logs in fine and then cannot download the FUT Squad
|
||||||
|
# Update, with every server looking healthy.
|
||||||
|
#
|
||||||
|
# This cost two live gates. The Rust redirector was pointed at the repo's
|
||||||
|
# fifa17-recon/tools/redir_cert.pem while the running container serves a
|
||||||
|
# different cert baked into its image — so the A/B had two variables, TLS
|
||||||
|
# implementation AND certificate identity, when it was meant to have one.
|
||||||
|
#
|
||||||
|
# Run this before arming any gate that puts a new TLS implementation in front
|
||||||
|
# of the client.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
[[ $# -ge 2 ]] || { echo "usage: check-tls-parity.sh <host:port> <host:port> [...]" >&2; exit 2; }
|
||||||
|
|
||||||
|
fingerprint() {
|
||||||
|
timeout 8 openssl s_client -connect "$1" </dev/null 2>/dev/null \
|
||||||
|
| openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2
|
||||||
|
}
|
||||||
|
|
||||||
|
ref=""; ref_target=""; rc=0
|
||||||
|
for target in "$@"; do
|
||||||
|
fp="$(fingerprint "$target")"
|
||||||
|
if [[ -z "$fp" ]]; then
|
||||||
|
printf " %-24s %s\n" "$target" "NO TLS / unreachable"
|
||||||
|
rc=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
printf " %-24s %s\n" "$target" "$fp"
|
||||||
|
if [[ -z "$ref" ]]; then
|
||||||
|
ref="$fp"; ref_target="$target"
|
||||||
|
elif [[ "$fp" != "$ref" ]]; then
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $rc -eq 0 ]]; then
|
||||||
|
echo "OK: all $# services present the same certificate"
|
||||||
|
else
|
||||||
|
echo "REFUSING: certificate mismatch across the FIFA-facing stack (reference: $ref_target)" >&2
|
||||||
|
echo " A client that caches the first cert it sees will fail on the others." >&2
|
||||||
|
fi
|
||||||
|
exit $rc
|
||||||
Reference in New Issue
Block a user