Repository navigation
Verify MySQL's real name through SSH tunnels with a TLS relay - #64
Merged
Merged
Conversation
Verbatim move of PhpseclibTunnelDriver's child-process launch, READY handshake, PHP binary lookup and autoloader path, so the TLS relay can start the same way. PhpseclibTunnel and its driver's behaviour are unchanged.
Packet framing and parsing across split reads, clearing CLIENT_SSL in the greeting, the SSLRequest built from the client's response, the response renumbered after it, the end of the connection phase, and an ERR packet the client reports. Pure functions, tested without a network.
pdo_mysql checks a certificate against the host it dials, and mysqlnd has no peer-name option, so through a tunnel it can only compare 127.0.0.1. The relay, a PHP worker process, takes the client's place in MySQL's in-protocol TLS upgrade: it hides TLS from the client's greeting, sends the SSLRequest itself, starts TLS with peer_name set to the real host and the configured CA (and client certificate), renumbers the connection-phase packets across the extra SSLRequest, then passes bytes through. A failed verification reaches the client as a MySQL ERR naming why. End to end over Unix sockets against a fake TLS MySQL server, in every pest job that can listen: a login with a full-auth round trip, a command, and a certificate for another name refused.
When a MySQL-family server requires verified TLS and is reached across the network from the SSH host, TunnelManager starts the relay on a socket in the tunnel's private directory, and ServerRegistry points the admin connection at it with the client's own TLS settings removed (the PDO attributes and the keys db-tools reads). Postgres keeps hostaddr; a socket or loopback target, unverified TLS, and TLS off need no relay. A socket local end is now accepted for a relayed server, since the relay does TLS itself; it stays refused where the client would start TLS on it. The launcher is a contract so tests open relayed tunnels without a process; a relay that cannot start closes its tunnel and fails it.
mytls, a MySQL 8.4 with require_secure_transport and a certificate for its own name from the job's CA, also answering to mytls-alias. Through both drivers: the session is TLS and required_ok, logins take caching_sha2's full and fast paths across the relay, a second session shares it, the alias is refused with the reason, and mysqldump backs up through it.
…s-through-tunnels
… 8.5
PHP starts TLS only on tcp:// streams; on a unix:// one
stream_socket_enable_crypto() fails with "This stream does not support
SSL/crypto", which is how the end-to-end test failed in CI. The relay now
reaches the tunnel over its loopback port only, the fake server listens on
TCP, and a relayed server with ssh.local=socket is refused with that reason
instead of failing at its first login. The client still talks to the relay
over a private socket.
On PHP 8.5 chr() takes int<0, 255>, which phpstan cannot prove for the
packet length's top byte; the 3-byte length is now taken from pack('V').
…s-through-tunnels # Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last open limitation of the SSH tunnel work: MySQL and MariaDB can now verify the server's real name through a tunnel.
Why it needed more than a config change
Through a TCP tunnel the client dials 127.0.0.1, and checking a certificate against that name fails. PostgreSQL avoids this with libpq's
hostaddr: dial one address, verify another name (#47, proven live in #58). MySQL and SQL Server have no equivalent:peer_name, so PHP checks the certificate against the host string it dialled. No PDO attribute changes that.HostNameInCertificatekey (connection options). Its TLS handshake also runs inside TDS PRELOGIN packets, which a relay can't take over with PHP streams.The fix: a verifying TLS relay for MySQL
MySQL starts TLS inside its own protocol, so the TLS step can be done for the client.
Tunnels\Relay\MysqlTlsRelayis a PHP worker process, started like the phpseclib pump, that sits in front of the tunnel:CLIENT_SSLcleared, so PDO never asks the relay for TLS.peer_nameset to the real host, the configuredtls.ca, andtls.cert/tls.keyfor mutual TLS.PDO talks to the relay in plaintext over a
0600socket inside the tunnel's0700directory. Everything past the relay is TLS, anddoctormeasures the session as encrypted andrequired_ok. A certificate that doesn't match fails the login with a MySQL ERR naming why, e.g.TLS to db.example.com failed: … did not match.When it applies: a MySQL-family server,
tls.enabledandtls.verifyon, and a target across the network from the SSH host. PostgreSQL keepshostaddr. A socket or loopback target, unverified TLS, and TLS off need no relay.Refactor
Tunnels\Support\WorkerProcessnow holds the start-and-wait logic the phpseclib driver had: settings on stdin, aREADYline, and exit on stdin EOF. It's a verbatim move, in its own commit.TlsRelayLauncheris a contract, so the registry tests open relayed tunnels without spawning a process.Tests
mytls. One case logs in with a full-auth round trip and runs a command; the other has a certificate for another name refused, with the reason reaching the client.live-sshjob:mytls, a MySQL 8.4 withrequire_secure_transport=ONand a certificate for its own name, which also answers tomytls-alias. With both drivers: the session is TLS andrequired_ok, a second session shares the relay, the alias is refused with the reason, and mysqldump backs up through it.Locally: pest 854 passed (92 live or listening tests skipped), with
laranail-pint --test, phpstan and rector clean. CHANGELOG[0.1.4]and older sections are byte-identical to the v0.1.4 tag.SQL Server stays documented:
tls.verify=falsethrough a tunnel, with the reason above.