From b0f59d1b22b39287f419674be1a255aaa80f7581 Mon Sep 17 00:00:00 2001 From: Lucas Kent Date: Thu, 30 Mar 2023 10:34:29 +1100 Subject: [PATCH] Initial rustls --- Cargo.lock | 55 +++ .../tests/cassandra_int_tests/cluster/mod.rs | 2 +- shotover/Cargo.toml | 4 + shotover/src/lib.rs | 1 + shotover/src/server.rs | 2 +- shotover/src/sources/cassandra.rs | 2 +- shotover/src/sources/kafka.rs | 2 +- shotover/src/sources/redis.rs | 2 +- shotover/src/tlsls.rs | 319 ++++++++++++++++++ .../src/transforms/cassandra/connection.rs | 2 +- .../transforms/cassandra/sink_cluster/mod.rs | 2 +- .../transforms/cassandra/sink_cluster/node.rs | 6 +- .../src/transforms/cassandra/sink_single.rs | 2 +- shotover/src/transforms/redis/sink_cluster.rs | 2 +- shotover/src/transforms/redis/sink_single.rs | 2 +- .../util/cluster_connection_pool.rs | 2 +- .../src/connection/redis_connection.rs | 2 + 17 files changed, 396 insertions(+), 13 deletions(-) create mode 100644 shotover/src/tlsls.rs diff --git a/Cargo.lock b/Cargo.lock index 0d749e9f2..ccdc4cfd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2859,6 +2859,37 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "rustls" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07180898a28ed6a7f7ba2311594308f595e3dd2e3c3812fa0a80a47b45f17e5d" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -2901,6 +2932,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "scylla" version = "0.7.0" @@ -3154,6 +3195,9 @@ dependencies = [ "redis-protocol", "rusoto_kms", "rusoto_signature", + "rustls", + "rustls-pemfile", + "rustls-webpki", "serde", "serde_json", "serde_yaml", @@ -3161,6 +3205,7 @@ dependencies = [ "thiserror", "tokio", "tokio-openssl", + "tokio-rustls", "tokio-stream", "tokio-util", "tracing", @@ -3589,6 +3634,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.12" diff --git a/shotover-proxy/tests/cassandra_int_tests/cluster/mod.rs b/shotover-proxy/tests/cassandra_int_tests/cluster/mod.rs index 246d6bb3a..803a41649 100644 --- a/shotover-proxy/tests/cassandra_int_tests/cluster/mod.rs +++ b/shotover-proxy/tests/cassandra_int_tests/cluster/mod.rs @@ -2,7 +2,7 @@ use cassandra_protocol::frame::message_startup::BodyReqStartup; use cassandra_protocol::frame::Version; use shotover::frame::{cassandra::Tracing, CassandraFrame, CassandraOperation, Frame}; use shotover::message::Message; -use shotover::tls::{TlsConnector, TlsConnectorConfig}; +use shotover::tlsls::{TlsConnector, TlsConnectorConfig}; use shotover::transforms::cassandra::sink_cluster::{ node::{CassandraNode, ConnectionFactory}, topology::{create_topology_task, TaskConnectionInfo}, diff --git a/shotover/Cargo.toml b/shotover/Cargo.toml index e97fd2d04..8de71cbb5 100644 --- a/shotover/Cargo.toml +++ b/shotover/Cargo.toml @@ -78,3 +78,7 @@ generic-array = { version = "0.14", features = ["serde"] } dyn-clone = "1.0.10" kafka-protocol = "0.6.0" typetag = "0.2.5" +rustls = { version = "0.21.0", features = ["dangerous_configuration"] } +tokio-rustls = "0.24" +rustls-pemfile = "1.0.2" +rustls-webpki = "0.100.1" diff --git a/shotover/src/lib.rs b/shotover/src/lib.rs index 2b568f039..888cdcca1 100644 --- a/shotover/src/lib.rs +++ b/shotover/src/lib.rs @@ -33,5 +33,6 @@ mod server; pub mod sources; pub mod tcp; pub mod tls; +pub mod tlsls; pub mod tracing_panic_handler; pub mod transforms; diff --git a/shotover/src/server.rs b/shotover/src/server.rs index 29f280a70..2a7d26d1f 100644 --- a/shotover/src/server.rs +++ b/shotover/src/server.rs @@ -1,6 +1,6 @@ use crate::codec::{CodecBuilder, CodecReadError}; use crate::message::Messages; -use crate::tls::{AcceptError, TlsAcceptor}; +use crate::tlsls::{AcceptError, TlsAcceptor}; use crate::transforms::chain::{TransformChain, TransformChainBuilder}; use crate::transforms::Wrapper; use anyhow::{anyhow, Context, Result}; diff --git a/shotover/src/sources/cassandra.rs b/shotover/src/sources/cassandra.rs index 074c548bc..ad82296cc 100644 --- a/shotover/src/sources/cassandra.rs +++ b/shotover/src/sources/cassandra.rs @@ -2,7 +2,7 @@ use crate::codec::Direction; use crate::codec::{cassandra::CassandraCodecBuilder, CodecBuilder}; use crate::server::TcpCodecListener; use crate::sources::Sources; -use crate::tls::{TlsAcceptor, TlsAcceptorConfig}; +use crate::tlsls::{TlsAcceptor, TlsAcceptorConfig}; use crate::transforms::chain::TransformChainBuilder; use anyhow::Result; use serde::Deserialize; diff --git a/shotover/src/sources/kafka.rs b/shotover/src/sources/kafka.rs index 7bc9117d6..398754219 100644 --- a/shotover/src/sources/kafka.rs +++ b/shotover/src/sources/kafka.rs @@ -1,7 +1,7 @@ use crate::codec::{kafka::KafkaCodecBuilder, CodecBuilder, Direction}; use crate::server::TcpCodecListener; use crate::sources::Sources; -use crate::tls::{TlsAcceptor, TlsAcceptorConfig}; +use crate::tlsls::{TlsAcceptor, TlsAcceptorConfig}; use crate::transforms::chain::TransformChainBuilder; use anyhow::Result; use serde::Deserialize; diff --git a/shotover/src/sources/redis.rs b/shotover/src/sources/redis.rs index a2b3f29f9..386d7b91c 100644 --- a/shotover/src/sources/redis.rs +++ b/shotover/src/sources/redis.rs @@ -1,7 +1,7 @@ use crate::codec::{redis::RedisCodecBuilder, CodecBuilder, Direction}; use crate::server::TcpCodecListener; use crate::sources::Sources; -use crate::tls::{TlsAcceptor, TlsAcceptorConfig}; +use crate::tlsls::{TlsAcceptor, TlsAcceptorConfig}; use crate::transforms::chain::TransformChainBuilder; use anyhow::Result; use serde::Deserialize; diff --git a/shotover/src/tlsls.rs b/shotover/src/tlsls.rs new file mode 100644 index 000000000..01499e272 --- /dev/null +++ b/shotover/src/tlsls.rs @@ -0,0 +1,319 @@ +use crate::tcp; +use anyhow::{anyhow, bail, Context, Error, Result}; +use rustls::client::{InvalidDnsNameError, ServerCertVerified, ServerCertVerifier}; +use rustls::{Certificate, OwnedTrustAnchor, PrivateKey, RootCertStore, ServerName}; +use rustls_pemfile::{certs, Item}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{BufReader, ErrorKind}; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::sync::Arc; +use std::time::Duration; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::{TcpStream, ToSocketAddrs}; +use tokio_rustls::client::TlsStream as TlsStreamClient; +use tokio_rustls::server::TlsStream as TlsStreamServer; +use tokio_rustls::{TlsAcceptor as RustlsAcceptor, TlsConnector as RustlsConnector}; +use webpki::TrustAnchor; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TlsAcceptorConfig { + /// Path to the certificate authority in PEM format + pub certificate_authority_path: String, + /// Path to the certificate in PEM format + pub certificate_path: String, + /// Path to the private key in PEM format + pub private_key_path: String, +} + +#[derive(Clone)] +pub struct TlsAcceptor { + acceptor: RustlsAcceptor, +} + +pub enum AcceptError { + /// The client decided it didnt need the connection anymore and politely disconnected before the handshake completed. + /// This can occur during regular use and indicates the connection should be quietly discarded. + Disconnected, + Failure(Error), +} + +fn load_certs(path: &str) -> Result> { + certs(&mut BufReader::new(File::open(path)?)) + .context("Error while parsing PEM") + .map(|certs| certs.into_iter().map(Certificate).collect()) +} + +fn load_keys(path: &str) -> Result> { + rustls_pemfile::read_all(&mut BufReader::new(File::open(path)?)) + .context("Error while parsing PEM") + .map(|keys| { + keys.into_iter() + .filter_map(|item| match item { + Item::RSAKey(x) | Item::PKCS8Key(x) => Some(PrivateKey(x)), + _ => None, + }) + .collect() + }) +} + +impl TlsAcceptor { + pub fn new(tls_config: TlsAcceptorConfig) -> Result { + let mut keys = load_keys(&tls_config.private_key_path).map_err(|err| { + anyhow!(err).context(format!( + "Failed to read file {} configured at 'private_key_path", + tls_config.private_key_path, + )) + })?; + let certs = load_certs(&tls_config.certificate_path).map_err(|err| { + anyhow!(err).context(format!( + "Failed to read file {} configured at 'certificate_path'", + tls_config.private_key_path, + )) + })?; + + let config = rustls::ServerConfig::builder() + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(certs, keys.remove(0))?; + + Ok(TlsAcceptor { + acceptor: RustlsAcceptor::from(Arc::new(config)), + }) + } + + pub async fn accept( + &self, + tcp_stream: TcpStream, + ) -> Result, AcceptError> { + self.acceptor + .accept(tcp_stream) + .await + .map_err(|err| match err.kind() { + ErrorKind::UnexpectedEof => AcceptError::Disconnected, + _ => AcceptError::Failure(anyhow!(err).context("Failed to accept TLS connection")), + }) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TlsConnectorConfig { + /// Path to the certificate authority in PEM format + pub certificate_authority_path: String, + /// Path to the certificate in PEM format + pub certificate_path: Option, + /// Path to the private key in PEM format + pub private_key_path: Option, + /// enable/disable verifying the hostname of the destination's certificate. + pub verify_hostname: bool, +} + +#[derive(Clone)] +pub struct TlsConnector { + connector: RustlsConnector, +} + +fn load_ca(path: &str) -> Result { + let mut root_cert_store = RootCertStore::empty(); + + let mut pem = BufReader::new(File::open(path)?); + let certs = rustls_pemfile::certs(&mut pem).context("Error while parsing PEM")?; + let trust_anchors = certs.iter().map(|cert| { + let ta = TrustAnchor::try_from_cert_der(&cert[..]).unwrap(); + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + }); + root_cert_store.add_server_trust_anchors(trust_anchors); + Ok(root_cert_store) +} + +impl TlsConnector { + pub fn new(tls_config: TlsConnectorConfig) -> Result { + let root_cert_store = load_ca(&tls_config.certificate_authority_path).map_err(|err| { + anyhow!(err).context(format!( + "Failed to read file {} configured at 'certificate_authority_path'", + tls_config.certificate_authority_path, + )) + })?; + + let keys = tls_config + .private_key_path + .as_ref() + .map(|path| { + load_keys(path).map_err(|err| { + anyhow!(err).context(format!( + "Failed to read file {path} configured at 'private_key_path", + )) + }) + }) + .transpose()?; + let certs = tls_config + .certificate_path + .as_ref() + .map(|path| { + load_certs(path).map_err(|err| { + anyhow!(err).context(format!( + "Failed to read file {path} configured at 'certificate_path'", + )) + }) + }) + .transpose()?; + + let config_builder = rustls::ClientConfig::builder().with_safe_defaults(); + let config = match (keys, certs, tls_config.verify_hostname) { + (Some(mut keys), Some(certs), true) => config_builder + .with_root_certificates(root_cert_store) + .with_single_cert(certs, keys.remove(0))?, + (Some(mut keys), Some(certs), false) => config_builder + .with_custom_certificate_verifier(Arc::new(SkipVerifyHostName)) + .with_single_cert(certs, keys.remove(0))?, + (None, None, true) => config_builder + .with_root_certificates(root_cert_store) + .with_no_client_auth(), + (None, None, false) => config_builder + .with_custom_certificate_verifier(Arc::new(SkipVerifyHostName)) + .with_no_client_auth(), + + (Some(_), None, _) => { + bail!("private_key_path was specified but certificate_path was not") + } + (None, Some(_), _) => { + bail!("certificate_path was specified but private_key_path was not") + } + }; + + Ok(TlsConnector { + connector: RustlsConnector::from(Arc::new(config)), + }) + } + + pub async fn connect( + &self, + connect_timeout: Duration, + address: A, + ) -> Result> { + let servername = address.to_servername()?; + let tcp_stream = tcp::tcp_stream(connect_timeout, address).await?; + self.connector + .connect(servername, tcp_stream) + .await + .map_err(|e| anyhow!("{e:#?}")) + .context("Failed to establish TLS connection to destination") + } +} + +struct SkipVerifyHostName; +impl ServerCertVerifier for SkipVerifyHostName { + fn verify_server_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: std::time::SystemTime, + ) -> std::result::Result { + // TLS is added and removed here :) + Ok(ServerCertVerified::assertion()) + } +} + +/// A trait object can only consist of one trait + special language traits like Send/Sync etc +/// So we need to use this trait when creating trait objects that need both AsyncRead and AsyncWrite +pub trait AsyncStream: AsyncRead + AsyncWrite {} + +/// We need to tell rust that these types implement AsyncStream even though they already implement AsyncRead and AsyncWrite +impl AsyncStream for TlsStreamClient {} +impl AsyncStream for TlsStreamServer {} +impl AsyncStream for TcpStream {} + +/// Allows retrieving the hostname from any ToSocketAddrs type +pub trait ToHostname { + fn to_hostname(&self) -> String; + fn to_servername(&self) -> Result; +} + +/// Implement for all reference types +impl ToHostname for &T { + fn to_hostname(&self) -> String { + (**self).to_hostname() + } + fn to_servername(&self) -> Result { + (**self).to_servername() + } +} + +impl ToHostname for String { + fn to_hostname(&self) -> String { + self.split(':').next().unwrap_or("").to_owned() + } + fn to_servername(&self) -> Result { + ServerName::try_from(self.to_hostname().as_str()) + } +} + +impl ToHostname for &str { + fn to_hostname(&self) -> String { + self.split(':').next().unwrap_or("").to_owned() + } + fn to_servername(&self) -> Result { + ServerName::try_from(self.split(':').next().unwrap_or("")) + } +} + +impl ToHostname for (&str, u16) { + fn to_hostname(&self) -> String { + self.0.to_string() + } + fn to_servername(&self) -> Result { + ServerName::try_from(self.0) + } +} + +impl ToHostname for (String, u16) { + fn to_hostname(&self) -> String { + self.0.to_string() + } + fn to_servername(&self) -> Result { + ServerName::try_from(self.0.as_str()) + } +} + +impl ToHostname for (IpAddr, u16) { + fn to_hostname(&self) -> String { + self.0.to_string() + } + fn to_servername(&self) -> Result { + Ok(ServerName::IpAddress(self.0)) + } +} + +impl ToHostname for (Ipv4Addr, u16) { + fn to_hostname(&self) -> String { + self.0.to_string() + } + fn to_servername(&self) -> Result { + Ok(ServerName::IpAddress(IpAddr::V4(self.0))) + } +} + +impl ToHostname for (Ipv6Addr, u16) { + fn to_hostname(&self) -> String { + self.0.to_string() + } + fn to_servername(&self) -> Result { + Ok(ServerName::IpAddress(IpAddr::V6(self.0))) + } +} + +impl ToHostname for SocketAddr { + fn to_hostname(&self) -> String { + self.ip().to_string() + } + fn to_servername(&self) -> Result { + Ok(ServerName::IpAddress(self.ip())) + } +} diff --git a/shotover/src/transforms/cassandra/connection.rs b/shotover/src/transforms/cassandra/connection.rs index 03a12406e..8940841b0 100644 --- a/shotover/src/transforms/cassandra/connection.rs +++ b/shotover/src/transforms/cassandra/connection.rs @@ -4,7 +4,7 @@ use crate::frame::cassandra::CassandraMetadata; use crate::frame::{CassandraFrame, Frame}; use crate::message::{Message, Metadata}; use crate::tcp; -use crate::tls::{TlsConnector, ToHostname}; +use crate::tlsls::{TlsConnector, ToHostname}; use crate::transforms::Messages; use anyhow::{anyhow, Result}; use cassandra_protocol::frame::{Opcode, Version}; diff --git a/shotover/src/transforms/cassandra/sink_cluster/mod.rs b/shotover/src/transforms/cassandra/sink_cluster/mod.rs index 20e7c5031..f6fd4110c 100644 --- a/shotover/src/transforms/cassandra/sink_cluster/mod.rs +++ b/shotover/src/transforms/cassandra/sink_cluster/mod.rs @@ -4,7 +4,7 @@ use crate::error::ChainResponse; use crate::frame::cassandra::{CassandraMetadata, Tracing}; use crate::frame::{CassandraFrame, CassandraOperation, CassandraResult, Frame}; use crate::message::{Message, Messages, Metadata}; -use crate::tls::{TlsConnector, TlsConnectorConfig}; +use crate::tlsls::{TlsConnector, TlsConnectorConfig}; use crate::transforms::cassandra::connection::{CassandraConnection, Response, ResponseError}; use crate::transforms::{Transform, TransformBuilder, TransformConfig, Transforms, Wrapper}; use anyhow::{anyhow, Result}; diff --git a/shotover/src/transforms/cassandra/sink_cluster/node.rs b/shotover/src/transforms/cassandra/sink_cluster/node.rs index 31b2a044a..5e2379c22 100644 --- a/shotover/src/transforms/cassandra/sink_cluster/node.rs +++ b/shotover/src/transforms/cassandra/sink_cluster/node.rs @@ -2,7 +2,7 @@ use crate::codec::cassandra::CassandraCodecBuilder; use crate::codec::{CodecBuilder, Direction}; use crate::frame::Frame; use crate::message::{Message, Messages}; -use crate::tls::{TlsConnector, ToHostname}; +use crate::tlsls::{TlsConnector, ToHostname}; use crate::transforms::cassandra::connection::CassandraConnection; use anyhow::{anyhow, Result}; use cassandra_protocol::frame::Version; @@ -61,11 +61,13 @@ impl CassandraNode { } } -#[derive(Debug)] +#[derive(Derivative)] +#[derivative(Debug)] pub struct ConnectionFactory { connect_timeout: Duration, init_handshake: Vec, use_message: Option, + #[derivative(Debug = "ignore")] tls: Option, pushed_messages_tx: Option>, } diff --git a/shotover/src/transforms/cassandra/sink_single.rs b/shotover/src/transforms/cassandra/sink_single.rs index 4c78eac4f..9d028025f 100644 --- a/shotover/src/transforms/cassandra/sink_single.rs +++ b/shotover/src/transforms/cassandra/sink_single.rs @@ -3,7 +3,7 @@ use crate::codec::{cassandra::CassandraCodecBuilder, CodecBuilder, Direction}; use crate::error::ChainResponse; use crate::frame::cassandra::CassandraMetadata; use crate::message::{Messages, Metadata}; -use crate::tls::{TlsConnector, TlsConnectorConfig}; +use crate::tlsls::{TlsConnector, TlsConnectorConfig}; use crate::transforms::cassandra::connection::Response; use crate::transforms::{Transform, TransformBuilder, TransformConfig, Transforms, Wrapper}; use anyhow::{anyhow, Result}; diff --git a/shotover/src/transforms/redis/sink_cluster.rs b/shotover/src/transforms/redis/sink_cluster.rs index c795f0a35..4970750f6 100644 --- a/shotover/src/transforms/redis/sink_cluster.rs +++ b/shotover/src/transforms/redis/sink_cluster.rs @@ -3,7 +3,7 @@ use crate::codec::{CodecBuilder, Direction}; use crate::error::ChainResponse; use crate::frame::{Frame, RedisFrame}; use crate::message::Message; -use crate::tls::TlsConnectorConfig; +use crate::tlsls::TlsConnectorConfig; use crate::transforms::redis::RedisError; use crate::transforms::redis::TransformError; use crate::transforms::util::cluster_connection_pool::{Authenticator, ConnectionPool}; diff --git a/shotover/src/transforms/redis/sink_single.rs b/shotover/src/transforms/redis/sink_single.rs index a70d90563..9257b7420 100644 --- a/shotover/src/transforms/redis/sink_single.rs +++ b/shotover/src/transforms/redis/sink_single.rs @@ -5,7 +5,7 @@ use crate::codec::{ use crate::frame::{Frame, RedisFrame}; use crate::message::{Message, Messages}; use crate::tcp; -use crate::tls::{AsyncStream, TlsConnector, TlsConnectorConfig}; +use crate::tlsls::{AsyncStream, TlsConnector, TlsConnectorConfig}; use crate::transforms::{ ChainResponse, Transform, TransformBuilder, TransformConfig, Transforms, Wrapper, }; diff --git a/shotover/src/transforms/util/cluster_connection_pool.rs b/shotover/src/transforms/util/cluster_connection_pool.rs index 85a960986..bc315e27a 100644 --- a/shotover/src/transforms/util/cluster_connection_pool.rs +++ b/shotover/src/transforms/util/cluster_connection_pool.rs @@ -1,7 +1,7 @@ use super::Response; use crate::codec::{CodecBuilder, DecoderHalf, EncoderHalf}; use crate::tcp; -use crate::tls::{TlsConnector, TlsConnectorConfig}; +use crate::tlsls::{TlsConnector, TlsConnectorConfig}; use crate::transforms::util::{ConnectionError, Request}; use anyhow::{anyhow, Result}; use async_trait::async_trait; diff --git a/test-helpers/src/connection/redis_connection.rs b/test-helpers/src/connection/redis_connection.rs index b44644c1c..57e06ea0d 100644 --- a/test-helpers/src/connection/redis_connection.rs +++ b/test-helpers/src/connection/redis_connection.rs @@ -55,6 +55,8 @@ pub async fn new_async_tls(port: u16) -> redis::aio::Connection { .configure() .unwrap() .verify_hostname(false) + // really upstream should deal with this for us but for now we can easily just disable it ourselves https://github.com/sfackler/rust-openssl/issues/1860 + .use_server_name_indication(false) .into_ssl("127.0.0.1") .unwrap();