Skip to content

Commit 7b9c4fe

Browse files
authored
Add Config::tls_server_name and validate when using rustls (#1104)
* Bump hyper-rustls Signed-off-by: clux <[email protected]> * Support tls-server-name with rustls Signed-off-by: clux <[email protected]> * change default incluster for rustls to use env with tls_server_name Signed-off-by: clux <[email protected]> * fix build Signed-off-by: clux <[email protected]> * better docs Signed-off-by: clux <[email protected]> * simplify feature selection and use current stack precedence Signed-off-by: clux <[email protected]> * allow rustls_https_connector to be created from a connector Signed-off-by: clux <[email protected]> Signed-off-by: clux <[email protected]>
1 parent d28a715 commit 7b9c4fe

File tree

5 files changed

+73
-25
lines changed

5 files changed

+73
-25
lines changed

kube-client/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ kube-core = { path = "../kube-core", version = "=0.76.0" }
5757
jsonpath_lib = { version = "0.3.0", optional = true }
5858
tokio-util = { version = "0.7.0", optional = true, features = ["io", "codec"] }
5959
hyper = { version = "0.14.13", optional = true, features = ["client", "http1", "stream", "tcp"] }
60-
hyper-rustls = { version = "0.23.0", optional = true }
60+
hyper-rustls = { version = "0.23.2", optional = true }
6161
tokio-tungstenite = { version = "0.18.0", optional = true }
6262
tower = { version = "0.4.6", optional = true, features = ["buffer", "filter", "util"] }
6363
tower-http = { version = "0.3.2", optional = true, features = ["auth", "map-response-body", "trace"] }

kube-client/src/client/builder.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ impl TryFrom<Config> for ClientBuilder<BoxService<Request<hyper::Body>, Response
8585
#[cfg(feature = "openssl-tls")]
8686
let connector = config.openssl_https_connector_with_connector(connector)?;
8787
#[cfg(all(not(feature = "openssl-tls"), feature = "rustls-tls"))]
88-
let connector = hyper_rustls::HttpsConnector::from((
89-
connector,
90-
std::sync::Arc::new(config.rustls_client_config()?),
91-
));
88+
let connector = config.rustls_https_connector_with_connector(connector)?;
9289

9390
let mut connector = TimeoutConnector::new(connector);
9491

kube-client/src/client/config_ext.rs

+42-4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,29 @@ pub trait ConfigExt: private::Sealed {
4343
#[cfg(feature = "rustls-tls")]
4444
fn rustls_https_connector(&self) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;
4545

46+
/// Create [`hyper_rustls::HttpsConnector`] based on config and `connector`.
47+
///
48+
/// # Example
49+
///
50+
/// ```rust
51+
/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
52+
/// # use kube::{client::ConfigExt, Config};
53+
/// # use hyper::client::HttpConnector;
54+
/// let config = Config::infer().await?;
55+
/// let mut connector = HttpConnector::new();
56+
/// connector.enforce_http(false);
57+
/// let https = config.rustls_https_connector_with_connector(connector)?;
58+
/// let hyper_client: hyper::Client<_, hyper::Body> = hyper::Client::builder().build(https);
59+
/// # Ok(())
60+
/// # }
61+
/// ```
62+
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
63+
#[cfg(feature = "rustls-tls")]
64+
fn rustls_https_connector_with_connector(
65+
&self,
66+
connector: hyper::client::HttpConnector,
67+
) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;
68+
4669
/// Create [`rustls::ClientConfig`] based on config.
4770
/// # Example
4871
///
@@ -186,15 +209,30 @@ impl ConfigExt for Config {
186209

187210
#[cfg(feature = "rustls-tls")]
188211
fn rustls_https_connector(&self) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>> {
189-
let rustls_config = std::sync::Arc::new(self.rustls_client_config()?);
190-
let mut http = hyper::client::HttpConnector::new();
191-
http.enforce_http(false);
192-
Ok(hyper_rustls::HttpsConnector::from((http, rustls_config)))
212+
let mut connector = hyper::client::HttpConnector::new();
213+
connector.enforce_http(false);
214+
self.rustls_https_connector_with_connector(connector)
215+
}
216+
217+
#[cfg(feature = "rustls-tls")]
218+
fn rustls_https_connector_with_connector(
219+
&self,
220+
connector: hyper::client::HttpConnector,
221+
) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>> {
222+
let rustls_config = self.rustls_client_config()?;
223+
let mut builder = hyper_rustls::HttpsConnectorBuilder::new()
224+
.with_tls_config(rustls_config)
225+
.https_or_http();
226+
if let Some(tsn) = self.tls_server_name.as_ref() {
227+
builder = builder.with_server_name(tsn.clone());
228+
}
229+
Ok(builder.enable_http1().wrap_connector(connector))
193230
}
194231

195232
#[cfg(feature = "openssl-tls")]
196233
fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder> {
197234
let identity = self.exec_identity_pem().or_else(|| self.identity_pem());
235+
// TODO: pass self.tls_server_name for openssl
198236
tls::openssl_tls::ssl_connector_builder(identity.as_ref(), self.root_cert.as_ref())
199237
.map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateSslConnector(e)))
200238
}

kube-client/src/config/file_config.rs

+6
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ pub struct Cluster {
105105
#[serde(rename = "proxy-url")]
106106
#[serde(skip_serializing_if = "Option::is_none")]
107107
pub proxy_url: Option<String>,
108+
/// Name used to check server certificate.
109+
///
110+
/// If `tls_server_name` is `None`, the hostname used to contact the server is used.
111+
#[serde(rename = "tls-server-name")]
112+
#[serde(skip_serializing_if = "Option::is_none")]
113+
pub tls_server_name: Option<String>,
108114
/// Additional information for extenders so that reads and writes don't clobber unknown fields
109115
#[serde(skip_serializing_if = "Option::is_none")]
110116
pub extensions: Option<Vec<NamedExtension>>,

kube-client/src/config/mod.rs

+23-16
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ pub struct Config {
159159
// TODO Actually support proxy or create an example with custom client
160160
/// Optional proxy URL.
161161
pub proxy_url: Option<http::Uri>,
162+
/// If set, apiserver certificate will be validated to contain this string
163+
///
164+
/// If not set, the `cluster_url` is used instead
165+
pub tls_server_name: Option<String>,
162166
}
163167

164168
impl Config {
@@ -180,6 +184,7 @@ impl Config {
180184
accept_invalid_certs: false,
181185
auth_info: AuthInfo::default(),
182186
proxy_url: None,
187+
tls_server_name: None,
183188
}
184189
}
185190

@@ -213,20 +218,20 @@ impl Config {
213218

214219
/// Load an in-cluster Kubernetes client configuration using
215220
/// [`Config::incluster_env`].
216-
#[cfg(not(feature = "rustls-tls"))]
217-
pub fn incluster() -> Result<Self, InClusterError> {
218-
Self::incluster_env()
219-
}
220-
221-
/// Load an in-cluster Kubernetes client configuration using
222-
/// [`Config::incluster_dns`].
223221
///
224-
/// The `rustls-tls` feature is currently incompatible with
225-
/// [`Config::incluster_env`]. See
226-
/// <https://github.com/kube-rs/kube/issues/1003>.
227-
#[cfg(feature = "rustls-tls")]
222+
/// # Rustls-specific behavior
223+
/// Rustls does not support validating IP addresses (see
224+
/// <https://github.com/kube-rs/kube/issues/1003>).
225+
/// To work around this, when rustls is configured, this function automatically appends
226+
/// `tls-server-name = "kubernetes.default.svc"` to the resulting configuration.
227+
/// Overriding or unsetting `Config::tls_server_name` will avoid this behaviour.
228228
pub fn incluster() -> Result<Self, InClusterError> {
229-
Self::incluster_dns()
229+
let mut cfg = Self::incluster_env()?;
230+
if cfg!(all(not(feature = "openssl-tls"), feature = "rustls-tls")) {
231+
// openssl takes precedence when both features present, so only do it when only rustls is there
232+
cfg.tls_server_name = Some("kubernetes.default.svc".to_string());
233+
}
234+
Ok(cfg)
230235
}
231236

232237
/// Load an in-cluster config using the `KUBERNETES_SERVICE_HOST` and
@@ -236,9 +241,7 @@ impl Config {
236241
/// `/var/run/secrets/kubernetes.io/serviceaccount/`.
237242
///
238243
/// This method matches the behavior of the official Kubernetes client
239-
/// libraries, but it is not compatible with the `rustls-tls` feature . When
240-
/// this feature is enabled, [`Config::incluster_dns`] should be used
241-
/// instead. See <https://github.com/kube-rs/kube/issues/1003>.
244+
/// libraries and is the default for both TLS stacks.
242245
pub fn incluster_env() -> Result<Self, InClusterError> {
243246
let uri = incluster_config::try_kube_from_env()?;
244247
Self::incluster_with_uri(uri)
@@ -251,7 +254,9 @@ impl Config {
251254
/// `/var/run/secrets/kubernetes.io/serviceaccount/`.
252255
///
253256
/// This behavior does not match that of the official Kubernetes clients,
254-
/// but this approach is compatible with the `rustls-tls` feature.
257+
/// but this approach is compatible with the `rustls-tls` feature
258+
/// without setting `tls_server_name`.
259+
/// See <https://github.com/kube-rs/kube/issues/1003>.
255260
pub fn incluster_dns() -> Result<Self, InClusterError> {
256261
Self::incluster_with_uri(incluster_config::kube_dns())
257262
}
@@ -275,6 +280,7 @@ impl Config {
275280
..Default::default()
276281
},
277282
proxy_url: None,
283+
tls_server_name: None,
278284
})
279285
}
280286

@@ -333,6 +339,7 @@ impl Config {
333339
accept_invalid_certs,
334340
proxy_url: loader.proxy_url()?,
335341
auth_info: loader.user,
342+
tls_server_name: loader.cluster.tls_server_name,
336343
})
337344
}
338345

0 commit comments

Comments
 (0)