Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add client SSL authentication using key-file for Postgres, MySQL and MariaDB #1850

Merged
merged 22 commits into from
Feb 18, 2023

Conversation

ThibsG
Copy link
Contributor

@ThibsG ThibsG commented May 2, 2022

Revival of #1166
Fixes #1162

Tests are separated whether client SSL or password is used to authenticate the user.
Database's configuration is changing (to configure database and not provide any password, and thus be more confident in the test),
but if you believe this is too heavy (too long, too many images etc.) let me know, I'll mix the Docker images to use the same,
either we want to connect using SSL or with a password.

@RabidFire
Copy link

Thanks for this, @ThibsG! We use YugabyteDB (it's PostgreSQL wire-compatible like CockroachDB) and need to use client certificates for authentication. We've been waiting for this support in order to shift from Diesel to SQLx.

Tagging @abonander since he reviewed the previous PR (#1166).

I'll do some testing on my end and report back. Thanks again!

@ThibsG
Copy link
Contributor Author

ThibsG commented Jul 12, 2022

Hi @RabidFire , did you have some time to run some tests?

@EnigmaCurry
Copy link

I get Error: Configuration("no keys found pem file") when I use an elliptic curve key type (-----BEGIN EC PRIVATE KEY-----). But maybe this type of key is not supported by rustls? It is tested working with psql.

@abonander
Copy link
Collaborator

I'd like to see the workflows running, not sure why they're not.

@ThibsG
Copy link
Contributor Author

ThibsG commented Jul 19, 2022

@abonander it seems that if I push more than 1 time per day-ish, it won't run tests automatically (too heavy I suppose), so it needs approval from maintainer (the link I see is this one: https://docs.github.com/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks)

@abonander
Copy link
Collaborator

PRs from first-time contributors require approval every time a workflow wants to be run. It's a bit annoying but it seems to be a decent precaution as an attacker could open a PR and modify a workflow to submit some secret environment variables like auth tokens to a server they control.

@ThibsG ThibsG force-pushed the postgres-native-tls-fixes branch from 5caca9b to 7d07758 Compare July 22, 2022 09:30
@ThibsG
Copy link
Contributor Author

ThibsG commented Jul 22, 2022

Well thanks that makes perfect sense.

I just updated GA workflow to properly remove the previous container so the port is free to spawn the next container. Tests should run well now.

@ThibsG ThibsG force-pushed the postgres-native-tls-fixes branch 2 times, most recently from 90b3f76 to 6506063 Compare August 20, 2022 16:40
@ThibsG ThibsG requested a review from abonander October 4, 2022 07:40
@fiadliel
Copy link

I've been playing with this a bit, and one potential issue I have (for the use case I'm looking at) is that configuration of the client certs is dependent on accept_invalid_certs being false.

It seems to me that client certificate configuration should be independent of server certificate verification? WDYT?

@ThibsG
Copy link
Contributor Author

ThibsG commented Nov 28, 2022

Indeed I totally agree, good catch thanks! 😉

@srinivasmohan
Copy link

@ThibsG it would also be nice to support combined pem files (cert + key in same file) as that would be rather convenient (and potentially reduce IO/reads in "heavy" envs).

something like.

async fn load_client_certs(client_cert: &CertificateInput) -> Result<(Vec<Certificate>, PrivateKey), Error> {
    let mut cursor = Cursor::new(client_cert.data().await?);

    let mut certs: Vec<Certificate> = vec! [];
    let mut keys: Vec<PrivateKey> = vec! [];

    loop {
        match rustls_pemfile::read_one(&mut cursor).expect("Could not parse cert/private keys from client cert") {
            Some(rustls_pemfile::Item::RSAKey(key)) => keys.push(PrivateKey(key)),
            Some(rustls_pemfile::Item::PKCS8Key(key)) => keys.push(PrivateKey(key)),
            Some(rustls_pemfile::Item::ECKey(key)) => keys.push(PrivateKey(key)),
            Some(rustls_pemfile::Item::X509Certificate(cert)) => certs.push(Certificate(cert)),
            None => break,
            _ => {}
        }
    }

    if keys.len() == 1 {
        Ok((certs, keys.into_iter().next().unwrap()))
    } else {
        Err(Error::Tls(format!("No private key in client cert").into()))
    }
}

@abonander
Copy link
Collaborator

@ThibsG current development is on the 0.7-dev branch which just had some major refactors merged. Do you mind rebasing?

@ThibsG
Copy link
Contributor Author

ThibsG commented Feb 4, 2023

@abonander done with rebasing 😉

@EnigmaCurry
Copy link

   Compiling sqlx-postgres v0.6.2 (https://github.com/ThibsG/sqlx?branch=postgres-native-tls-fixes#7ffc1330)
error[E0063]: missing fields `client_cert_path` and `client_key_path` in initializer of `TlsConfig<'_>`
  --> /home/ryan/.cargo/git/checkouts/sqlx-1c3a6f3a9c600558/7ffc133/sqlx-postgres/src/connection/tls.rs:56:18
   |
56 |     let config = TlsConfig {
   |                  ^^^^^^^^^ missing `client_cert_path` and `client_key_path`

@EnigmaCurry
Copy link

EnigmaCurry commented Feb 5, 2023

I'm testing this with setting PGSSLCERT, PGSSLKEY, and PGSSLROOTCERT environment vars. I'm getting this panic:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Tls(NotPkcs8)', src/main.rs:11:10

Heres my code and the full backtrace and how I set my env vars and my throwaway cert+key. I had suspected that my use of Elliptic Curve keys was causing this, so I switched my root CA, server, and client certs all to use RSA instead. AFAIK it did output a valid pkcs8 key file. It connects with psql correctly. I am generating the keys with step-ca in my script here.

In general, this is how I bring up postgres

@ThibsG ThibsG changed the base branch from main to 0.7-dev February 6, 2023 10:07
@ThibsG
Copy link
Contributor Author

ThibsG commented Feb 6, 2023

The EC key variant was not handled, this has been fixed, thanks!

Also, I changed the target branch to merge this PR into 0.7-dev, it will be easier to review changes.

@EnigmaCurry
Copy link

FWIW, I'm still getting the same error Tls(NotPkcs8) with either EC or RSA keys. There must be something strange about my certs (maybe to do with step-ca vs openssl?), as I don't get this error if I use instead the ones provided in the tests folder.

@ThibsG
Copy link
Contributor Author

ThibsG commented Feb 6, 2023

I'm not a cert expert, but your client key seems to be in PKCS#1 format (the header is starting with BEGIN RSA PRIVATE KEY, not BEGIN PRIVATE KEY, in PKCS#8 the type is encoded within the key).

Looking at source code, it seems also that rust-native-tls does not support PKCS#1 keys (TL;DR: no need, the conversion to PKCS#8 is not that hard).

Try to convert using: openssl pkcs8 -topk8 -nocrypt -in cc-client-key -out cc-client-key.pk8.
I will test this with your setup tomorrow.

@EnigmaCurry
Copy link

Try to convert using: openssl pkcs8 -topk8 -nocrypt -in cc-client-key -out cc-client-key.pk8. I will test this with your setup tomorrow.

That fixed it! 🎉

I can confirm this is working now. End-to-End mutual TLS ::) Thank you!

@ThibsG
Copy link
Contributor Author

ThibsG commented Feb 7, 2023

@EnigmaCurry , you may also want to try runtime-tokio-rustls feature instead of runtime-tokio-native-tls, as Rustls supports PKCS#1 format. It avoids the conversion if you didn't want to use openssl at all.

@EnigmaCurry
Copy link

Rustls support PKCS#1 format.

Confirmed! I fixed my script so it outputs keyfiles in both formats, but now its nice to be able to use either one. Thanks again.

@ThibsG ThibsG force-pushed the postgres-native-tls-fixes branch 2 times, most recently from 961b566 to 6b8446f Compare February 15, 2023 07:36
@abonander
Copy link
Collaborator

Looks like it's green, glad to finally land this!

@abonander abonander merged commit 5a6ef6b into launchbadge:0.7-dev Feb 18, 2023
abonander pushed a commit that referenced this pull request Feb 18, 2023
…MariaDB (#1850)

* use native-tls API

* Add client cert and key to MySQL connector

* Add client ssl tests for PostgreSQL

* Add client ssl tests for MariaDB and MySQL

* Adapt GA tests

* Fix RUSTFLAGS to run all tests

* Remove containers to free the DB port before running SSL auth tests

* Fix CI bad naming

* Use docker-compose down to remove also the network

* Fix main rebase

* Stop trying to stop service using docker-compose, simply use docker cmd

* Fix RUSTFLAGS for Postgres

* Name the Docker images for MariaDB and MySQL so we can stop them using their name

* Add the exception for mysql 5.7 not supporting compatible TLS version with RusTLS

* Rebase fixes

* Set correctly tls struct (fix merge)

* Handle Elliptic Curve variant for private key

* Fix tests suite

* Fix features in CI

* Add tests for Postgres 15 + rebase

* Python tests: fix exception for MySQL 5.7 + remove unneeded for loops

* CI: run SSL tests only when building with TLS support

---------

Co-authored-by: Barry Simons <[email protected]>
@ThibsG ThibsG deleted the postgres-native-tls-fixes branch February 18, 2023 06:09
abonander pushed a commit that referenced this pull request Feb 21, 2023
…MariaDB (#1850)

* use native-tls API

* Add client cert and key to MySQL connector

* Add client ssl tests for PostgreSQL

* Add client ssl tests for MariaDB and MySQL

* Adapt GA tests

* Fix RUSTFLAGS to run all tests

* Remove containers to free the DB port before running SSL auth tests

* Fix CI bad naming

* Use docker-compose down to remove also the network

* Fix main rebase

* Stop trying to stop service using docker-compose, simply use docker cmd

* Fix RUSTFLAGS for Postgres

* Name the Docker images for MariaDB and MySQL so we can stop them using their name

* Add the exception for mysql 5.7 not supporting compatible TLS version with RusTLS

* Rebase fixes

* Set correctly tls struct (fix merge)

* Handle Elliptic Curve variant for private key

* Fix tests suite

* Fix features in CI

* Add tests for Postgres 15 + rebase

* Python tests: fix exception for MySQL 5.7 + remove unneeded for loops

* CI: run SSL tests only when building with TLS support

---------

Co-authored-by: Barry Simons <[email protected]>
Aandreba pushed a commit to Aandreba/sqlx that referenced this pull request Mar 31, 2023
…MariaDB (launchbadge#1850)

* use native-tls API

* Add client cert and key to MySQL connector

* Add client ssl tests for PostgreSQL

* Add client ssl tests for MariaDB and MySQL

* Adapt GA tests

* Fix RUSTFLAGS to run all tests

* Remove containers to free the DB port before running SSL auth tests

* Fix CI bad naming

* Use docker-compose down to remove also the network

* Fix main rebase

* Stop trying to stop service using docker-compose, simply use docker cmd

* Fix RUSTFLAGS for Postgres

* Name the Docker images for MariaDB and MySQL so we can stop them using their name

* Add the exception for mysql 5.7 not supporting compatible TLS version with RusTLS

* Rebase fixes

* Set correctly tls struct (fix merge)

* Handle Elliptic Curve variant for private key

* Fix tests suite

* Fix features in CI

* Add tests for Postgres 15 + rebase

* Python tests: fix exception for MySQL 5.7 + remove unneeded for loops

* CI: run SSL tests only when building with TLS support

---------

Co-authored-by: Barry Simons <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Postgres native tls does not support client certificates.
7 participants