fix(security): require authentication on the replication endpoint for every bind - #103
fix(security): require authentication on the replication endpoint for every bind#103HelgeSverre wants to merge 1 commit into
Conversation
… every bind The replication endpoint hands a full copy of the database to every connecting peer. It previously refused only non-loopback binds without ELYRASQL_CLUSTER_SECRET, so a listener on 127.0.0.1 without a secret let any local process (or an SSRF payload able to open a TCP connection) exfiltrate the entire data set with a bare connection and zero bytes sent. Require ELYRASQL_CLUSTER_SECRET on every bind address; keep ELYRASQL_ALLOW_OPEN_AUTH=1 as the explicit opt-out. Add guard unit tests and update security/configuration/limitations docs.
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 security release.** Three independent holes in the replication surface (#107, superseding #103–#105), all reachable in 1.9.8. The first needs no credentials and no handshake: connecting to the replication port returned a full copy of the database. Reproduced against 1.9.8 — zero bytes sent, 560 bytes back with the canary row in them. The guard only covered non-loopback binds, so `--replication-listen 127.0.0.1:...` without a cluster secret was readable by any local process. ## The upgrade note is a `danger` block, not a `warning` Because the fixes are deliberately breaking and the exposure is real: - a primary with `--replication-listen` and a replica **both** now require `ELYRASQL_CLUSTER_SECRET` - **primary and replica must be upgraded together** — the handshake gained a step, so a 1.9.9 replica will not accept a 1.9.8 primary - a replica now needs accounts (`--user`/`--password`/`--auth`) - a refused endpoint **keeps the server running**, so `replication endpoint stopped` in the log is the only signal that replication is not happening — the note says to watch for it rather than assume - anyone who ran an exposed endpoint should rotate what the data would have revealed: the port left no access log `ELYRASQL_ALLOW_OPEN_AUTH=1` opts out of all four, described as the honest way to say "this port is on a network I control" rather than as a convenience. ## The three things the release workflow does not validate Per `CONTRIBUTING.md`: `SERVER_VERSION` → `8.0.12-ElyraSQL-1.9.9` (confirmed with `elyrasql version`), `testbench/sql-dump/Cargo.lock` refreshed with `cargo metadata --locked` green in both workspaces, and the version strings in README, installation, deployment and mysql-compatibility. The `Upgrading to 1.9.8` block keeps its title, and the 1.9.8 changelog section is byte-identical (verified by digest). ## Testing - [x] `cargo fmt --all --check` - [x] `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` - [x] `cargo nextest run --workspace --locked` — 448/448 - [x] `cargo metadata --locked` in both workspaces - [x] Verified against a MySQL client The fix itself was verified both ways before merging: without a secret the endpoint refuses to start and its port does not listen; with one, an unauthenticated peer receives the 16-byte challenge and no data. ## Checklist - [x] Docs updated under `docs/` - [x] No internal engine/dependency names leak into SQL, errors, CLI, or the wire handshake - [x] Limitations documented honestly - [x] Scoped to a single logical change
Summary
The replication endpoint hands a full copy of the database to every connecting peer. It previously refused only non-loopback binds without
ELYRASQL_CLUSTER_SECRET— so a listener on127.0.0.1without a secret let any local process (or an SSRF payload able to open a TCP connection) exfiltrate the entire data set.Demonstrated live against
main(dd0ed82) on macOS/Linux-equivalent loopback:No handshake is required: on EOF-without-Hello the server treats the peer as a fresh replica and streams the whole snapshot.
Fix
ELYRASQL_CLUSTER_SECRETon every bind address, loopback included.ELYRASQL_ALLOW_OPEN_AUTH=1as the explicit opt-out (consistent with the existing guards).docs/security.md,docs/limitations.md,docs/configuration.md, CHANGELOG.Breaking change
Deployments that relied on an unauthenticated loopback replication endpoint must now set
ELYRASQL_CLUSTER_SECRET(recommended) or opt out explicitly. Primary and replicas should be upgraded together; mixed versions fail closed at the handshake rather than silently degrading.Verification
cargo test -p elyra-server— 221 passed (3 new)cargo clippy -p elyra-server --all-targets -- -D warningscleancargo fmt --all -- --checkclean