Skip to content

Commit

Permalink
Implement support for Rustls - v2 (redis-rs#725)
Browse files Browse the repository at this point in the history
* Rustls features: 
    * Rustls support is added under the tls-rustls feature flag.
    * Insecure TLS connections are permitted with Rustls using the tls-rustls-insecure 
      feature flag.
    * Rustls can be instructed to use Mozilla's root certificates using the tls-rustls-webpki-roots 
      feature flag.
* Rustls support for async I/O:
    * Tokio Rustls support is added under the tokio-rustls-comp feature flag.
    * async-std Rustls support is added under the async-std-rustls-comp feature flag.
* Renames for native-tls (deprecation of older features):
    * The native-tls feature flag has been renamed from tls to tls-native-tls 
       (for lack of a better naming scheme that's consistent with Rustls).
    * The async-std + native-tls feature flag has been renamed from 
       async-std-tls-comp to async-std-native-tls-comp (for consistency).
    * Both of the older flags are retained for backwards-compatibility, with deprecation 
      notices in the README and in Cargo.toml
  • Loading branch information
rharish101 authored Mar 31, 2023
1 parent 6a0c4c3 commit 7eab4cf
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 54 deletions.
16 changes: 13 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ test:
@REDISRS_SERVER_TYPE=tcp cargo test -p redis --all-features -- --nocapture --test-threads=1 --skip test_module

@echo "===================================================================="
@echo "Testing Connection Type TCP with all features and TLS support"
@echo "Testing Connection Type TCP with all features and Rustls support"
@echo "===================================================================="
@REDISRS_SERVER_TYPE=tcp+tls cargo test -p redis --all-features -- --nocapture --test-threads=1 --skip test_module

@echo "===================================================================="
@echo "Testing Connection Type TCP with all features and native-TLS support"
@echo "===================================================================="
@REDISRS_SERVER_TYPE=tcp+tls cargo test -p redis --features=json,tokio-native-tls-comp,connection-manager,cluster-async -- --nocapture --test-threads=1 --skip test_module

@echo "===================================================================="
@echo "Testing Connection Type UNIX"
@echo "===================================================================="
Expand All @@ -29,9 +34,14 @@ test:
@REDISRS_SERVER_TYPE=unix cargo test -p redis --all-features -- --skip test_cluster --skip test_async_cluster --skip test_module

@echo "===================================================================="
@echo "Testing async-std"
@echo "Testing async-std with Rustls"
@echo "===================================================================="
@REDISRS_SERVER_TYPE=tcp cargo test -p redis --features=async-std-rustls-comp,cluster-async -- --nocapture --test-threads=1

@echo "===================================================================="
@echo "Testing async-std with native-TLS"
@echo "===================================================================="
@REDISRS_SERVER_TYPE=tcp cargo test -p redis --features=async-std-tls-comp,cluster-async -- --nocapture --test-threads=1
@REDISRS_SERVER_TYPE=tcp cargo test -p redis --features=async-std-native-tls-comp,cluster-async -- --nocapture --test-threads=1

@echo "===================================================================="
@echo "Testing redis-test"
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,44 @@ redis = { version = "0.23.0", features = ["async-std-comp"] }
## TLS Support

To enable TLS support, you need to use the relevant feature entry in your Cargo.toml.
Currently, `native-tls` and `rustls` are supported.

To use `native-tls`:

```
redis = { version = "0.23.0", features = ["tls"] }
redis = { version = "0.23.0", features = ["tls-native-tls"] }
# if you use tokio
redis = { version = "0.23.0", features = ["tokio-native-tls-comp"] }
# if you use async-std
redis = { version = "0.23.0", features = ["async-std-tls-comp"] }
redis = { version = "0.23.0", features = ["async-std-native-tls-comp"] }
```

To use `rustls`:

```
redis = { version = "0.23.0", features = ["tls-rustls"] }
# if you use tokio
redis = { version = "0.23.0", features = ["tokio-rustls-comp"] }
# if you use async-std
redis = { version = "0.23.0", features = ["async-std-rustls-comp"] }
```

With `rustls`, you can add the following feature flags on top of other feature flags to enable additional features:
- `tls-rustls-insecure`: Allow insecure TLS connections
- `tls-rustls-webpki-roots`: Use `webpki-roots` (Mozilla's root certificates) instead of native root certificates

then you should be able to connect to a redis instance using the `rediss://` URL scheme:

```rust
let client = redis::Client::open("rediss://127.0.0.1/")?;
```

**Deprecation Notice:** If you were using the `tls` or `async-std-tls-comp` features, please use the `tls-native-tls` or `async-std-native-tls-comp` features respectively.

## Cluster Support

Support for Redis Cluster can be enabled by enabling the `cluster` feature in your Cargo.toml:
Expand Down
25 changes: 21 additions & 4 deletions redis/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,19 @@ rand = { version = "0.8", optional = true }
async-std = { version = "1.8.0", optional = true}
async-trait = { version = "0.1.24", optional = true }

# Only needed for TLS
# Only needed for native tls
native-tls = { version = "0.2", optional = true }
tokio-native-tls = { version = "0.3", optional = true }
async-native-tls = { version = "0.4", optional = true }

# Only needed for rustls
rustls = { version = "0.20.4", optional = true }
webpki = { version = "0.22.0", optional = true }
webpki-roots = { version = "0.22.3", optional = true }
rustls-native-certs = { version = "0.6.2", optional = true }
tokio-rustls = { version = "0.23.3", optional = true }
futures-rustls = { version = "0.22.2", optional = true }

# Only needed for RedisJSON Support
serde = { version = "1.0.82", optional = true }
serde_json = { version = "1.0.82", optional = true }
Expand All @@ -76,15 +84,24 @@ geospatial = []
json = ["serde", "serde/derive", "serde_json"]
cluster = ["crc16", "rand"]
script = ["sha1_smol"]
tls = ["native-tls"]
tls-native-tls = ["native-tls"]
tls-rustls = ["rustls", "rustls-native-certs", "webpki"]
tls-rustls-insecure = ["tls-rustls", "rustls/dangerous_configuration"]
tls-rustls-webpki-roots = ["tls-rustls", "webpki-roots"]
async-std-comp = ["aio", "async-std"]
async-std-tls-comp = ["async-std-comp", "async-native-tls", "tls"]
async-std-native-tls-comp = ["async-std-comp", "async-native-tls", "tls-native-tls"]
async-std-rustls-comp = ["async-std-comp", "futures-rustls", "tls-rustls"]
tokio-comp = ["aio", "tokio", "tokio/net"]
tokio-native-tls-comp = ["tls", "tokio-native-tls"]
tokio-native-tls-comp = ["tokio-comp", "tls-native-tls", "tokio-native-tls"]
tokio-rustls-comp = ["tokio-comp", "tls-rustls", "tokio-rustls"]
connection-manager = ["arc-swap", "futures", "aio"]
streams = []
cluster-async = ["cluster", "futures", "futures-util", "log"]

# Deprecated features
tls = ["tls-native-tls"] # use "tls-native-tls" instead
async-std-tls-comp = ["async-std-native-tls-comp"] # use "async-std-native-tls-comp" instead

[dev-dependencies]
rand = "0.8"
socket2 = "0.4"
Expand Down
6 changes: 3 additions & 3 deletions redis/src/aio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub(crate) trait RedisRuntime: AsyncStream + Send + Sync + Sized + 'static {
async fn connect_tcp(socket_addr: SocketAddr) -> RedisResult<Self>;

// Performs a TCP TLS connection
#[cfg(feature = "tls")]
#[cfg(any(feature = "tls-native-tls", feature = "tls-rustls"))]
async fn connect_tcp_tls(
hostname: &str,
socket_addr: SocketAddr,
Expand Down Expand Up @@ -459,7 +459,7 @@ pub(crate) async fn connect_simple<T: RedisRuntime>(
<T>::connect_tcp(socket_addr).await?
}

#[cfg(feature = "tls")]
#[cfg(any(feature = "tls-native-tls", feature = "tls-rustls"))]
ConnectionAddr::TcpTls {
ref host,
port,
Expand All @@ -469,7 +469,7 @@ pub(crate) async fn connect_simple<T: RedisRuntime>(
<T>::connect_tcp_tls(host, socket_addr, insecure).await?
}

#[cfg(not(feature = "tls"))]
#[cfg(not(any(feature = "tls-native-tls", feature = "tls-rustls")))]
ConnectionAddr::TcpTls { .. } => {
fail!((
ErrorKind::InvalidClientConfig,
Expand Down
60 changes: 52 additions & 8 deletions redis/src/aio/async_std.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#[cfg(unix)]
use std::path::Path;
#[cfg(feature = "tls-rustls")]
use std::sync::Arc;
use std::{
future::Future,
io,
Expand All @@ -10,8 +12,15 @@ use std::{

use crate::aio::{AsyncStream, RedisRuntime};
use crate::types::RedisResult;
#[cfg(feature = "tls")]

#[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
use async_native_tls::{TlsConnector, TlsStream};

#[cfg(feature = "tls-rustls")]
use crate::connection::create_rustls_config;
#[cfg(feature = "tls-rustls")]
use futures_rustls::{client::TlsStream, TlsConnector};

use async_std::net::TcpStream;
#[cfg(unix)]
use async_std::os::unix::net::UnixStream;
Expand Down Expand Up @@ -82,7 +91,10 @@ pub enum AsyncStd {
/// Represents an Async_std TCP connection.
Tcp(AsyncStdWrapped<TcpStream>),
/// Represents an Async_std TLS encrypted TCP connection.
#[cfg(feature = "async-std-tls-comp")]
#[cfg(any(
feature = "async-std-native-tls-comp",
feature = "async-std-rustls-comp"
))]
TcpTls(AsyncStdWrapped<Box<TlsStream<TcpStream>>>),
/// Represents an Async_std Unix connection.
#[cfg(unix)]
Expand All @@ -97,7 +109,10 @@ impl AsyncWrite for AsyncStd {
) -> Poll<io::Result<usize>> {
match &mut *self {
AsyncStd::Tcp(r) => Pin::new(r).poll_write(cx, buf),
#[cfg(feature = "async-std-tls-comp")]
#[cfg(any(
feature = "async-std-native-tls-comp",
feature = "async-std-rustls-comp"
))]
AsyncStd::TcpTls(r) => Pin::new(r).poll_write(cx, buf),
#[cfg(unix)]
AsyncStd::Unix(r) => Pin::new(r).poll_write(cx, buf),
Expand All @@ -107,7 +122,10 @@ impl AsyncWrite for AsyncStd {
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<io::Result<()>> {
match &mut *self {
AsyncStd::Tcp(r) => Pin::new(r).poll_flush(cx),
#[cfg(feature = "async-std-tls-comp")]
#[cfg(any(
feature = "async-std-native-tls-comp",
feature = "async-std-rustls-comp"
))]
AsyncStd::TcpTls(r) => Pin::new(r).poll_flush(cx),
#[cfg(unix)]
AsyncStd::Unix(r) => Pin::new(r).poll_flush(cx),
Expand All @@ -117,7 +135,10 @@ impl AsyncWrite for AsyncStd {
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<io::Result<()>> {
match &mut *self {
AsyncStd::Tcp(r) => Pin::new(r).poll_shutdown(cx),
#[cfg(feature = "async-std-tls-comp")]
#[cfg(any(
feature = "async-std-native-tls-comp",
feature = "async-std-rustls-comp"
))]
AsyncStd::TcpTls(r) => Pin::new(r).poll_shutdown(cx),
#[cfg(unix)]
AsyncStd::Unix(r) => Pin::new(r).poll_shutdown(cx),
Expand All @@ -133,7 +154,10 @@ impl AsyncRead for AsyncStd {
) -> Poll<io::Result<()>> {
match &mut *self {
AsyncStd::Tcp(r) => Pin::new(r).poll_read(cx, buf),
#[cfg(feature = "async-std-tls-comp")]
#[cfg(any(
feature = "async-std-native-tls-comp",
feature = "async-std-rustls-comp"
))]
AsyncStd::TcpTls(r) => Pin::new(r).poll_read(cx, buf),
#[cfg(unix)]
AsyncStd::Unix(r) => Pin::new(r).poll_read(cx, buf),
Expand All @@ -149,7 +173,7 @@ impl RedisRuntime for AsyncStd {
.map(|con| Self::Tcp(AsyncStdWrapped::new(con)))?)
}

#[cfg(feature = "tls")]
#[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
async fn connect_tcp_tls(
hostname: &str,
socket_addr: SocketAddr,
Expand All @@ -170,6 +194,23 @@ impl RedisRuntime for AsyncStd {
.map(|con| Self::TcpTls(AsyncStdWrapped::new(Box::new(con))))?)
}

#[cfg(feature = "tls-rustls")]
async fn connect_tcp_tls(
hostname: &str,
socket_addr: SocketAddr,
insecure: bool,
) -> RedisResult<Self> {
let tcp_stream = TcpStream::connect(&socket_addr).await?;

let config = create_rustls_config(insecure)?;
let tls_connector = TlsConnector::from(Arc::new(config));

Ok(tls_connector
.connect(hostname.try_into()?, tcp_stream)
.await
.map(|con| Self::TcpTls(AsyncStdWrapped::new(Box::new(con))))?)
}

#[cfg(unix)]
async fn connect_unix(path: &Path) -> RedisResult<Self> {
Ok(UnixStream::connect(path)
Expand All @@ -184,7 +225,10 @@ impl RedisRuntime for AsyncStd {
fn boxed(self) -> Pin<Box<dyn AsyncStream + Send + Sync>> {
match self {
AsyncStd::Tcp(x) => Box::pin(x),
#[cfg(feature = "async-std-tls-comp")]
#[cfg(any(
feature = "async-std-native-tls-comp",
feature = "async-std-rustls-comp"
))]
AsyncStd::TcpTls(x) => Box::pin(x),
#[cfg(unix)]
AsyncStd::Unix(x) => Box::pin(x),
Expand Down
43 changes: 34 additions & 9 deletions redis/src/aio/tokio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,17 @@ use tokio::{
net::TcpStream as TcpStreamTokio,
};

#[cfg(feature = "tls")]
#[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
use native_tls::TlsConnector;

#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(feature = "tls-rustls")]
use crate::connection::create_rustls_config;
#[cfg(feature = "tls-rustls")]
use std::{convert::TryInto, sync::Arc};
#[cfg(feature = "tls-rustls")]
use tokio_rustls::{client::TlsStream, TlsConnector};

#[cfg(all(feature = "tokio-native-tls-comp", not(feature = "tokio-rustls-comp")))]
use tokio_native_tls::TlsStream;

#[cfg(unix)]
Expand All @@ -28,7 +35,7 @@ pub(crate) enum Tokio {
/// Represents a Tokio TCP connection.
Tcp(TcpStreamTokio),
/// Represents a Tokio TLS encrypted TCP connection
#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(any(feature = "tokio-native-tls-comp", feature = "tokio-rustls-comp"))]
TcpTls(Box<TlsStream<TcpStreamTokio>>),
/// Represents a Tokio Unix connection.
#[cfg(unix)]
Expand All @@ -43,7 +50,7 @@ impl AsyncWrite for Tokio {
) -> Poll<io::Result<usize>> {
match &mut *self {
Tokio::Tcp(r) => Pin::new(r).poll_write(cx, buf),
#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(any(feature = "tokio-native-tls-comp", feature = "tokio-rustls-comp"))]
Tokio::TcpTls(r) => Pin::new(r).poll_write(cx, buf),
#[cfg(unix)]
Tokio::Unix(r) => Pin::new(r).poll_write(cx, buf),
Expand All @@ -53,7 +60,7 @@ impl AsyncWrite for Tokio {
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<io::Result<()>> {
match &mut *self {
Tokio::Tcp(r) => Pin::new(r).poll_flush(cx),
#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(any(feature = "tokio-native-tls-comp", feature = "tokio-rustls-comp"))]
Tokio::TcpTls(r) => Pin::new(r).poll_flush(cx),
#[cfg(unix)]
Tokio::Unix(r) => Pin::new(r).poll_flush(cx),
Expand All @@ -63,7 +70,7 @@ impl AsyncWrite for Tokio {
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<io::Result<()>> {
match &mut *self {
Tokio::Tcp(r) => Pin::new(r).poll_shutdown(cx),
#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(any(feature = "tokio-native-tls-comp", feature = "tokio-rustls-comp"))]
Tokio::TcpTls(r) => Pin::new(r).poll_shutdown(cx),
#[cfg(unix)]
Tokio::Unix(r) => Pin::new(r).poll_shutdown(cx),
Expand All @@ -79,7 +86,7 @@ impl AsyncRead for Tokio {
) -> Poll<io::Result<()>> {
match &mut *self {
Tokio::Tcp(r) => Pin::new(r).poll_read(cx, buf),
#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(any(feature = "tokio-native-tls-comp", feature = "tokio-rustls-comp"))]
Tokio::TcpTls(r) => Pin::new(r).poll_read(cx, buf),
#[cfg(unix)]
Tokio::Unix(r) => Pin::new(r).poll_read(cx, buf),
Expand All @@ -95,7 +102,7 @@ impl RedisRuntime for Tokio {
.map(Tokio::Tcp)?)
}

#[cfg(feature = "tls")]
#[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
async fn connect_tcp_tls(
hostname: &str,
socket_addr: SocketAddr,
Expand All @@ -117,6 +124,24 @@ impl RedisRuntime for Tokio {
.map(|con| Tokio::TcpTls(Box::new(con)))?)
}

#[cfg(feature = "tls-rustls")]
async fn connect_tcp_tls(
hostname: &str,
socket_addr: SocketAddr,
insecure: bool,
) -> RedisResult<Self> {
let config = create_rustls_config(insecure)?;
let tls_connector = TlsConnector::from(Arc::new(config));

Ok(tls_connector
.connect(
hostname.try_into()?,
TcpStreamTokio::connect(&socket_addr).await?,
)
.await
.map(|con| Tokio::TcpTls(Box::new(con)))?)
}

#[cfg(unix)]
async fn connect_unix(path: &Path) -> RedisResult<Self> {
Ok(UnixStreamTokio::connect(path).await.map(Tokio::Unix)?)
Expand All @@ -135,7 +160,7 @@ impl RedisRuntime for Tokio {
fn boxed(self) -> Pin<Box<dyn AsyncStream + Send + Sync>> {
match self {
Tokio::Tcp(x) => Box::pin(x),
#[cfg(feature = "tokio-native-tls-comp")]
#[cfg(any(feature = "tokio-native-tls-comp", feature = "tokio-rustls-comp"))]
Tokio::TcpTls(x) => Box::pin(x),
#[cfg(unix)]
Tokio::Unix(x) => Box::pin(x),
Expand Down
Loading

0 comments on commit 7eab4cf

Please sign in to comment.