-
Notifications
You must be signed in to change notification settings - Fork 64
/
tls.rs
243 lines (218 loc) · 8.64 KB
/
tls.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
use crate::error::{Error, ErrorKind};
use std::{
fmt,
fmt::{Debug, Formatter},
net::IpAddr,
sync::Arc,
};
#[cfg(feature = "enable-native-tls")]
use std::convert::{TryFrom, TryInto};
#[cfg(feature = "enable-native-tls")]
use tokio_native_tls::native_tls::{
TlsConnector as NativeTlsConnector,
TlsConnectorBuilder as NativeTlsConnectorBuilder,
};
#[cfg(feature = "enable-native-tls")]
use tokio_native_tls::TlsConnector as TokioNativeTlsConnector;
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
use tokio_rustls::rustls::{ClientConfig as RustlsClientConfig, RootCertStore};
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
use tokio_rustls::TlsConnector as RustlsConnector;
/// A trait used for mapping IP addresses to hostnames when processing the `CLUSTER SLOTS` response.
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))))]
pub trait HostMapping: Send + Sync + Debug {
/// Map the provided IP address to a hostname that should be used during the TLS handshake.
///
/// The `default_host` argument represents the hostname of the node that returned the `CLUSTER SLOTS` response.
///
/// If `None` is returned the client will use the IP address as the server name during the TLS handshake.
fn map(&self, ip: &IpAddr, default_host: &str) -> Option<String>;
}
/// An optional enum used to describe how the client should modify or map IP addresses and hostnames in a clustered
/// deployment.
///
/// This is only necessary to use with a clustered deployment. Centralized or sentinel deployments should use `None`.
///
/// More information can be found [here](https://github.com/mna/redisc/issues/13) and [here](https://github.com/lettuce-io/lettuce-core/issues/1454#issuecomment-707537384).
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))))]
#[derive(Clone, Debug)]
pub enum TlsHostMapping {
/// Do not modify or replace hostnames or IP addresses in the `CLUSTER SLOTS` response.
///
/// Default
None,
/// Replace any IP addresses in the `CLUSTER SLOTS` response with the hostname of the node that returned
/// the `CLUSTER SLOTS` response.
///
/// If the `CLUSTER SLOTS` response contains hostnames alongside IP addresses (via the `metadata` block) then
/// those hostnames will be used instead. However, this is a relatively new Redis feature and it's likely some
/// configurations will not expose this information.
DefaultHost,
/// Provide a custom mapping from IP address to hostname to be used in a manner similar to a reverse DNS lookup.
Custom(Arc<dyn HostMapping>),
}
impl TlsHostMapping {
pub(crate) fn map(&self, value: &IpAddr, default_host: &str) -> Option<String> {
match self {
TlsHostMapping::None => None,
TlsHostMapping::DefaultHost => Some(default_host.to_owned()),
TlsHostMapping::Custom(ref inner) => inner.map(value, default_host),
}
}
}
impl PartialEq for TlsHostMapping {
fn eq(&self, other: &Self) -> bool {
match self {
TlsHostMapping::None => matches!(other, TlsHostMapping::None),
TlsHostMapping::DefaultHost => matches!(other, TlsHostMapping::DefaultHost),
TlsHostMapping::Custom(_) => matches!(other, TlsHostMapping::Custom(_)),
}
}
}
impl Eq for TlsHostMapping {}
/// TLS configuration for a client.
///
/// Note: the `hostnames` field is only necessary to use with certain clustered deployments.
///
/// ```rust no_run
/// # use fred::types::config::*;
/// let config = TlsConfig {
/// // or use `TlsConnector::default_rustls()`
/// connector: TlsConnector::default_native_tls().unwrap(),
/// hostnames: TlsHostMapping::None
/// };
///
/// // or use the shorthand
/// let config: TlsConfig = TlsConnector::default_native_tls()?.into();
/// let config: TlsConfig = TlsConnector::default_rustls()?.into();
/// ```
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TlsConfig {
/// The TLS connector from either `native-tls` or `rustls`.
pub connector: TlsConnector,
/// The hostname modification or mapping policy to use when discovering and connecting to cluster nodes.
pub hostnames: TlsHostMapping,
}
impl<C: Into<TlsConnector>> From<C> for TlsConfig {
fn from(connector: C) -> Self {
TlsConfig {
connector: connector.into(),
hostnames: TlsHostMapping::None,
}
}
}
/// An enum for interacting with various TLS libraries and interfaces.
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))))]
#[derive(Clone)]
pub enum TlsConnector {
#[cfg(feature = "enable-native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable-native-tls")))]
Native(TokioNativeTlsConnector),
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))))]
Rustls(RustlsConnector),
}
impl PartialEq for TlsConnector {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl Eq for TlsConnector {}
impl Debug for TlsConnector {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("TlsConnector")
.field("kind", match self {
#[cfg(feature = "enable-native-tls")]
TlsConnector::Native(_) => &"Native",
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
TlsConnector::Rustls(_) => &"Rustls",
})
.finish()
}
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-native-tls", feature = "enable-rustls"))))]
impl TlsConnector {
/// Create a default TLS connector from the `native-tls` module.
#[cfg(feature = "enable-native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable-native-tls")))]
pub fn default_native_tls() -> Result<Self, Error> {
NativeTlsConnector::builder().try_into()
}
/// Create a default TLS connector with the `rustls` module with safe defaults and system certs via [rustls-native-certs](https://github.com/rustls/rustls-native-certs).
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))))]
pub fn default_rustls() -> Result<Self, Error> {
let mut system_certs = rustls_native_certs::load_native_certs();
if !system_certs.errors.is_empty() {
return Err(Error::new(
ErrorKind::Tls,
format!("{:?}", system_certs.errors.pop().unwrap()),
));
}
let mut cert_store = RootCertStore::empty();
for system_cert in system_certs.certs.into_iter() {
cert_store.add(system_cert)?;
}
Ok(
RustlsClientConfig::builder()
.with_root_certificates(cert_store)
.with_no_client_auth()
.into(),
)
}
}
#[cfg(feature = "enable-native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable-native-tls")))]
impl TryFrom<NativeTlsConnectorBuilder> for TlsConnector {
type Error = Error;
fn try_from(builder: NativeTlsConnectorBuilder) -> Result<Self, Self::Error> {
let connector = builder
.build()
.map(TokioNativeTlsConnector::from)
.map_err(|e| Error::new(ErrorKind::Tls, format!("{:?}", e)))?;
Ok(TlsConnector::Native(connector))
}
}
#[cfg(feature = "enable-native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable-native-tls")))]
impl From<NativeTlsConnector> for TlsConnector {
fn from(connector: NativeTlsConnector) -> Self {
TlsConnector::Native(TokioNativeTlsConnector::from(connector))
}
}
#[cfg(feature = "enable-native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "enable-native-tls")))]
impl From<TokioNativeTlsConnector> for TlsConnector {
fn from(connector: TokioNativeTlsConnector) -> Self {
TlsConnector::Native(connector)
}
}
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))))]
impl From<RustlsClientConfig> for TlsConnector {
fn from(config: RustlsClientConfig) -> Self {
TlsConnector::Rustls(RustlsConnector::from(Arc::new(config)))
}
}
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))))]
impl From<RustlsConnector> for TlsConnector {
fn from(connector: RustlsConnector) -> Self {
TlsConnector::Rustls(connector)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(any(feature = "enable-rustls", feature = "enable-rustls-ring"))]
fn should_create_default_rustls() {
let _ = TlsConnector::default_rustls().unwrap();
}
#[test]
#[cfg(feature = "enable-native-tls")]
fn should_create_default_native_tls() {
let _ = TlsConnector::default_native_tls().unwrap();
}
}