launchbadge / sqlx

🧰 The Rust SQL Toolkit. An async, pure Rust SQL crate featuring compile-time checked queries without a DSL. Supports PostgreSQL, MySQL, and SQLite.
Apache License 2.0
12.36k stars 1.18k forks source link

Support SSL in postgres without an upgrade request #3264

Closed cjfuller closed 3 weeks ago

cjfuller commented 3 weeks ago

Is your feature request related to a problem? Please describe.

I'm trying to connect with SSL to a postgres instance sitting behind a proxy (on Google Cloud SQL). In this configuration, the server refuses to talk to any clients not over SSL, so the connection fails in connection::tls::maybe_upgrade when the server sends back an unexpected response. In a local fork, the connection works if I just remove the call to connection::tls::maybe_upgrade at tls.rs::43.

Describe the solution you'd like I'd like to be able to connect to a proxied DB that does not respond to the upgrade request.

I'm happy to contribute the fix, but since I'm not that familiar with the sqlx codebase, I wanted to ask for advice on which approach (below) would be preferable.

Describe alternatives you've considered

There are two implementation options I've considered:

  1. Add an extra field / methods for a boolean flag on PgConnectOptions called skip_ssl_upgrade_request or something that when set causes us to skip the upgrade request in tls.rs.

  2. Allow supplying the socket for communication from outside the library, so that users can configure the socket however they like, and then sqlx just speaks the normal non-ssl-mode postgres protocol over the preconfigured socket. (This is the approach I've seen used in a number of postgres libraries in other languages, like pg8000 in python for example: https://github.com/tlocke/pg8000?tab=readme-ov-file#connect-to-postgresql-over-ssl.)

I'll send a PR for the first one since I've got it working locally already, but happy to contribute the second one (or a totally different approach if desired).

abonander commented 3 weeks ago

Libpq only just gained an option to skip the plaintext handshake in the latest beta (sslnegotiation): https://www.postgresql.org/about/news/postgresql-17-beta-1-released-2865/

While I'd accept a PR that improves feature parity with libpq, the fact that it's not even in general release yet tells me that maybe you've missed a step in trying to connect to your Cloud SQL Postgres instance.

cjfuller commented 3 weeks ago

Their official client (for other languages, there is no official rust client, hence why I'm doing this) does do this nonstandard sort of setup, which is why I'm pretty convinced it's necessary? You can see here for instance with the python client that they're pre-preparing a tcp socket with the relevant ssl info and then passing that into the postgres client without actually telling postgres that we're in SSL mode: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector/blob/main/google/cloud/sql/connector/pg8000.py#L51.

I think the way the infra is set up is that their proxy is actually terminating the SSL connection, and then the proxy is speaking to postgres over a non-SSL connection; the upgrade has to be skipped entirely because the postgres instance itself is not doing anything with SSL.

I realize this is kind of nonstandard, which means perhaps option 2 (allow passing in a tcp socket) would maybe be better? Rather than directly supporting a nonstandard setup, create an escape hatch for users to do whatever nonstandard things they need to?

abonander commented 3 weeks ago

Just run the proxy and then connect to it with ?sslmode=never to skip the TLS upgrade.

cjfuller commented 3 weeks ago

Sorry, to be clear the proxy is not something we control; this is not the client proxy they distribute but an SSL-terminating proxy internal to Google's infrastructure that sits in front of the postgres instance, to which we do not have direct access. I'd like to do what you suggested, but to do that would require being able to tunnel the connection over an existing tcp socket that we have already configured for TLS because the proxy rejects all non-TLS connections?

Regardless, I started implementing that last night (giving sqlx the ability to have the user provide the socket for the connection), and it ended up being quite disruptive in a way that I think probably doesn't make sense for sqlx given that it's a nonstandard configuration?

I think I can probably work around this in my application by writing an in-process proxy (like your suggestion) that listens for a local non-SSL postgres connection, establishes a secure connection to the Google proxy, and then tunnels the postgres bytes over that connection, so I'll just do that instead. Thanks, and sorry for the noise!