From 15b3c80c8d56d227fc35cc2599989c348b624089 Mon Sep 17 00:00:00 2001 From: Nimi Wariboko Jr Date: Thu, 11 Jan 2024 22:02:02 -0800 Subject: [PATCH] Add rustls example; Incorporate rustls into github action workflows --- .github/workflows/tls.yml | 11 +- examples/Cargo.toml | 18 +++- examples/rustls.rs | 163 +++++++++++++++++++++++++++++ scylla/Cargo.toml | 4 +- scylla/src/lib.rs | 3 - scylla/src/transport/connection.rs | 1 + test/tls/ca.crt | 43 ++++---- 7 files changed, 212 insertions(+), 31 deletions(-) create mode 100644 examples/rustls.rs diff --git a/.github/workflows/tls.yml b/.github/workflows/tls.yml index 94d3b4926c..a860a81aa3 100644 --- a/.github/workflows/tls.yml +++ b/.github/workflows/tls.yml @@ -32,8 +32,13 @@ jobs: working-directory: ./scylla steps: - uses: actions/checkout@v3 - - name: Check + - name: Check OpenSSL run: cargo check --verbose --features "ssl" working-directory: ${{env.working-directory}} - - name: Run tls example - run: cargo run --example tls + - name: Check OpenSSL + run: cargo check --verbose --features "rustls" + working-directory: ${{env.working-directory}} + - name: Run OpenSSL example + run: cargo run --features "ssl" --example tls + - name: Run rustls example + run: cargo run --features "rustls" --example rustls diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 88a7dbda62..6e3ad3eae6 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -4,14 +4,19 @@ name = "examples" publish = false version = "0.0.0" +[features] +rustls = ["scylla/rustls"] +cloud = ["scylla/cloud"] +ssl = ["scylla/ssl"] + [dev-dependencies] anyhow = "1.0.33" futures = "0.3.6" openssl = "0.10.32" rustyline = "9" rustyline-derive = "0.6" -scylla = {path = "../scylla", features = ["ssl", "cloud", "chrono", "time"]} -tokio = {version = "1.1.0", features = ["full"]} +scylla = { path = "../scylla", features = ["chrono", "time"] } +tokio = { version = "1.1.0", features = ["full"] } tracing = "0.1.25" tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } chrono = { version = "0.4", default-features = false } @@ -20,6 +25,8 @@ uuid = "1.0" tower = "0.4" stats_alloc = "0.1" clap = { version = "3.2.4", features = ["derive"] } +tokio-rustls = "0.25" +rustls-pemfile = "2" [[example]] name = "auth" @@ -36,6 +43,12 @@ path = "logging.rs" [[example]] name = "tls" path = "tls.rs" +required-features = ["ssl"] + +[[example]] +name = "rustls" +path = "rustls.rs" +required-features = ["rustls"] [[example]] name = "cqlsh-rs" @@ -108,6 +121,7 @@ path = "query_history.rs" [[example]] name = "cloud" path = "cloud.rs" +required-features = ["cloud"] [[example]] diff --git a/examples/rustls.rs b/examples/rustls.rs new file mode 100644 index 0000000000..5f3af4bd76 --- /dev/null +++ b/examples/rustls.rs @@ -0,0 +1,163 @@ +use anyhow::Result; +use scylla::transport::session::{IntoTypedRows, Session}; +use scylla::SessionBuilder; +use std::env; +use std::sync::Arc; + +use tokio_rustls::rustls::{ + self, + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + pki_types::{CertificateDer, ServerName, UnixTime}, + ClientConfig, DigitallySignedStruct, RootCertStore, +}; + +// How to run scylla instance with TLS: +// +// Edit your scylla.yaml file and add paths to certificates +// ex: +// client_encryption_options: +// enabled: true +// certificate: /etc/scylla/db.crt +// keyfile: /etc/scylla/db.key +// +// If using docker mount your scylla.yaml file and your cert files with option +// --volume $(pwd)/tls.yaml:/etc/scylla/scylla.yaml +// +// If python returns permission error 13 use "Z" flag +// --volume $(pwd)/tls.yaml:/etc/scylla/scylla.yaml:Z +// +// In your Rust program connect to port 9142 if it wasn't changed +// Create new a ClientConfig with your certificate added to it's +// root store. +// +// If your server is using a certifcate that does not have it's IP address +// as a Common Name or Subject Alternate Name you will need to skip +// name verification as part of rustls's configuration. +// +// Build it and add to scylla-rust-driver's SessionBuilder + +#[tokio::main] +async fn main() -> Result<()> { + // Create connection + let uri = env::var("SCYLLA_URI").unwrap_or_else(|_| "127.0.0.1:9142".to_string()); + + println!("Connecting to {} ...", uri); + let mut root_cert_store = RootCertStore::empty(); + let rustls_pemfile::Item::X509Certificate(cert) = rustls_pemfile::read_one_from_slice( + &tokio::fs::read("./test/tls/ca.crt") + .await + .expect("Failed to load cert"), + ) + .expect("Failed to parse pem") + .expect("No certificates in file") + .0 + else { + panic!("not a certificate") + }; + root_cert_store + .add(cert) + .expect("Failed to add cert to root cert"); + + let mut config = ClientConfig::builder() + .with_root_certificates(root_cert_store) + .with_no_client_auth(); + + config + .dangerous() + .set_certificate_verifier(Arc::new(NoCertificateVerification::default())); + + let session: Session = SessionBuilder::new() + .known_node(uri) + .rustls_config(Some(Arc::new(config))) + .build() + .await?; + + session.query("CREATE KEYSPACE IF NOT EXISTS ks WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'replication_factor' : 1}", &[]).await?; + + session + .query( + "CREATE TABLE IF NOT EXISTS ks.t (a int, b int, c text, primary key (a, b))", + &[], + ) + .await?; + + session + .query("INSERT INTO ks.t (a, b, c) VALUES (?, ?, ?)", (3, 4, "def")) + .await?; + + session + .query("INSERT INTO ks.t (a, b, c) VALUES (1, 2, 'abc')", &[]) + .await?; + + let prepared = session + .prepare("INSERT INTO ks.t (a, b, c) VALUES (?, 7, ?)") + .await?; + session + .execute(&prepared, (42_i32, "I'm prepared!")) + .await?; + session + .execute(&prepared, (43_i32, "I'm prepared 2!")) + .await?; + session + .execute(&prepared, (44_i32, "I'm prepared 3!")) + .await?; + + // Rows can be parsed as tuples + if let Some(rows) = session.query("SELECT a, b, c FROM ks.t", &[]).await?.rows { + for row in rows.into_typed::<(i32, i32, String)>() { + let (a, b, c) = row?; + println!("a, b, c: {}, {}, {}", a, b, c); + } + } + println!("Ok."); + + Ok(()) +} + +#[derive(Debug)] +struct NoCertificateVerification { + supported: rustls::crypto::WebPkiSupportedAlgorithms, +} + +impl Default for NoCertificateVerification { + fn default() -> Self { + Self { + supported: rustls::crypto::ring::default_provider().signature_verification_algorithms, + } + } +} + +impl ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + return Ok(ServerCertVerified::assertion()); + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + return Ok(HandshakeSignatureValid::assertion()); + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &CertificateDer<'_>, + _dss: &DigitallySignedStruct, + ) -> Result { + return Ok(HandshakeSignatureValid::assertion()); + } + + fn supported_verify_schemes(&self) -> Vec { + self.supported.supported_schemes() + } +} diff --git a/scylla/Cargo.toml b/scylla/Cargo.toml index 0cabdf2860..68303a8bf2 100644 --- a/scylla/Cargo.toml +++ b/scylla/Cargo.toml @@ -14,10 +14,10 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["rustls"] +default = [] ssl = ["dep:tokio-openssl", "dep:openssl"] rustls = ["dep:tokio-rustls"] -cloud = ["scylla-cql/serde", "dep:serde_yaml", "dep:serde", "dep:url", "dep:base64"] +cloud = ["ssl", "scylla-cql/serde", "dep:serde_yaml", "dep:serde", "dep:url", "dep:base64"] secret = ["scylla-cql/secret"] chrono = ["scylla-cql/chrono"] time = ["scylla-cql/time"] diff --git a/scylla/src/lib.rs b/scylla/src/lib.rs index 8671d8c76e..37edb08d65 100644 --- a/scylla/src/lib.rs +++ b/scylla/src/lib.rs @@ -145,6 +145,3 @@ pub use transport::metrics::Metrics; #[cfg(all(feature = "ssl", feature = "rustls"))] compile_error!("both rustls and ssl should not be enabled together."); - -#[cfg(all(feature = "cloud", not(any(feature = "ssl", feature = "rustls"))))] -compile_error!("cloud feature requires either the rustls or ssl feature."); diff --git a/scylla/src/transport/connection.rs b/scylla/src/transport/connection.rs index dd55309aa3..587865e764 100644 --- a/scylla/src/transport/connection.rs +++ b/scylla/src/transport/connection.rs @@ -365,6 +365,7 @@ mod ssl_config { sni: Option>, } + #[cfg(feature = "rustls")] impl SslConfig { // Used in case when the user provided their own ClientConfig to be used in all connections. pub fn new_with_global_config(config: &Arc) -> Self { diff --git a/test/tls/ca.crt b/test/tls/ca.crt index 83304e18b9..d552e65ddd 100644 --- a/test/tls/ca.crt +++ b/test/tls/ca.crt @@ -1,23 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID5TCCAs2gAwIBAgIUIz5sdjquoebzlA6s3gQNRsYV5jUwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlBMMQowCAYDVQQIDAFNMQ8wDQYDVQQHDAZXYXJzYXcx -DzANBgNVBAoMBlNjeWxsYTEVMBMGA1UECwwMdGVzdGluZ19yb290MRUwEwYDVQQD -DAx0ZXN0aW5nX3Jvb3QxFjAUBgkqhkiG9w0BCQEWB2Zvb0BiYXIwHhcNMjEwMzMw -MTI1ODUwWhcNNDEwMzI1MTI1ODUwWjCBgTELMAkGA1UEBhMCUEwxCjAIBgNVBAgM -AU0xDzANBgNVBAcMBldhcnNhdzEPMA0GA1UECgwGU2N5bGxhMRUwEwYDVQQLDAx0 -ZXN0aW5nX3Jvb3QxFTATBgNVBAMMDHRlc3Rpbmdfcm9vdDEWMBQGCSqGSIb3DQEJ -ARYHZm9vQGJhcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANlzsXih -nryaSUSf/sXD6oDeU2m0/Fn/HvKL97BwdDKBz3kbs7fyztDCJyvRa24f679cQ1Av -5RqmemTU7m0KaTmJy9cXewjD7P1gDF8K4GumiekBgIsPNyBwVk5TQJBkvPPwjreP -JSj2wlXA7FXc9uUuNVx1ku6ElpK0pWt18uU8+nVRAeZTVZ7ppgmh/aRmMOFNPs6z -Lb9liNJAKPCR7iIALLnpiQOXHMnj+6+o8wMOMD4ehFY8XHYd0TYj5w+OD4tNOjfb -6m21gBaqoMjVVvBq+pmuHDT+oiLyBXpIQ9LNFBXnd/LyqTFiFjoT4yEPgWtpgScQ -CVqi+EJBryRXmUMCAwEAAaNTMFEwHQYDVR0OBBYEFL5PBn2IWZvz5Ce259Z7vym1 -tNKDMB8GA1UdIwQYMBaAFL5PBn2IWZvz5Ce259Z7vym1tNKDMA8GA1UdEwEB/wQF -MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAojwm/3pqAYSBKdq2dqRYvMrVF2a1yE -d5R8AnR/r0vfJEvw9pa+Y8kC/XRezODSiA2HSTTRDI3HxEyixMwLewyS+VcDHYvp -pLZQuCD5zzuq6hWj22o0XklX441TZbkfimzAVhCxlSoufj0l9AG8Ae/xHrOy/Dcd -uzPmnrw9XDb9PkoJHjji3Apb2HjSjO3b17+Pb9TA1YZNiil+jOjwJ8L5UdLw/dYQ -gu5MSxQOerq3wMTY8CUIDMrJMdKenQSGoCEcgLpF27utuUbZGJhFnRe9j1H4der1 -RSFMXoVZLI/69iQHmNC3+3keHK1W+CgMliWQ5cF2cly2otAxATriOxM= +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE-----