fix(security): mutual replication auth + replica refuses unauthenticated primaries - #104
fix(security): mutual replication auth + replica refuses unauthenticated primaries#104HelgeSverre wants to merge 1 commit into
Conversation
…ted primaries A replica applies everything its primary streams to it, but it never verified the primary's identity: auth_connect only proves the replica to the primary. Any host accepting TCP connections on the primary's address could impersonate it (the transport is plaintext unless cluster TLS is configured) and feed the replica arbitrary snapshot/write frames -- fabricated tables, tampered rows, corrupted catalog state -- which the replica then served to clients. Demonstrated end-to-end with a fake primary against a stock replica. - Add auth_verify_peer/auth_respond_to_peer: after the existing replica->primary proof, the primary must prove knowledge of ELYRASQL_CLUSTER_SECRET back to the replica before any data flows. Handshake is bounded by a 5s timeout so a stalling peer cannot hang the reconnect loop. - run_replica fails closed when no cluster secret is configured unless ELYRASQL_ALLOW_OPEN_AUTH=1 explicitly opts in. Primary and replica must be upgraded together (handshake changed). Adds handshake unit tests plus an end-to-end primary/replica test.
Three independent findings on the replication surface, sequenced and with their mutual conflicts resolved. Supersedes #103, #104 and #105. The replication endpoint handed a full copy of the database to any peer that connected, with no handshake -- reproduced against 1.9.8 on a loopback bind, where zero bytes sent returned the canary row. The old guard only covered non-loopback binds. A replica never verified its primary's identity, so anything answering on the primary's address could inject fabricated rows, even with ELYRASQL_CLUSTER_SECRET set: authentication ran in one direction only. Replication auth is now mutual, and auth_accept runs before auth_respond_to_peer so the responder cannot be used as an oracle by a peer that does not already know the secret. `elyrasql replica` had no auth flags at all and always started its MySQL listener with open authentication over replicated production data. It now takes --user/--password/--auth like `serve` and refuses to start credential-less. Verified: the endpoint refuses to start without a secret and its port does not listen; with a secret, an unauthenticated peer receives the 16-byte challenge and no data. 448/448 tests. Co-authored-by: Helge Sverre <helge.sverre@gmail.com>
|
Merged as part of #107 ( Consolidated rather than merged one by one for two reasons. Actions never ran on any of the three. They conflicted with each other, not just with One genuine interaction needed a commit of its own: I reproduced the #103 finding against 1.9.8 before touching anything: zero bytes sent to the replication port, 560 bytes back with the canary row in them. Verified closed both ways afterwards — without a secret the endpoint refuses to start and the port does not listen; with one, an unauthenticated peer gets the 16-byte challenge and nothing else. Thanks — this was a real hole in a shipped release. |
Summary
A replica applies everything its primary streams to it, but it never verified the primary's identity:
auth_connectonly proves the replica to the primary. Any host accepting TCP connections on the primary's address could impersonate it (the transport is plaintext unless cluster TLS is configured) and feed the replica arbitrary snapshot/write frames — fabricated tables, tampered rows, corrupted catalog state — which the replica then served to clients.Demonstrated end-to-end against
main(dd0ed82) with a ~60-line Python fake primary:repl_t(id, secret)=(1, 'REPLICATION_CANARY_SECRET'); snapshot captured from its replication port.Note this works even when
ELYRASQL_CLUSTER_SECRETis set on the primary: the fake primary reads the replica's proof, ignores it, and streams data anyway — nothing ever checks the other direction.Fix
auth_verify_peer(replica challenges, verifies constant-time) andauth_respond_to_peer(primary answers). Order:auth_connect→auth_verify_peer→Hello→ data. Bounded by a 5 s timeout so a stalling peer cannot hang the reconnect loop.run_replicafails closed when no cluster secret is configured unlessELYRASQL_ALLOW_OPEN_AUTH=1.Breaking change
Primary and replica must be upgraded together (the handshake changed). Mixed versions fail closed at connect rather than silently degrading. Replicas now require
ELYRASQL_CLUSTER_SECRET.Verification
Re-ran the fake-primary attack against this branch:
cargo test -p elyra-server— 222 passed (4 new)cargo clippy -p elyra-server --all-targets -- -D warningscleancargo fmt --all -- --checkcleanDepends conceptually on #103 (same guard philosophy); conflicts are trivial if ordering matters.