Skip to content

Commit

Permalink
Add Config::tls_server_name and validate when using rustls (#1104)
Browse files Browse the repository at this point in the history
* Bump hyper-rustls

Signed-off-by: clux <sszynrae@gmail.com>

* Support tls-server-name with rustls

Signed-off-by: clux <sszynrae@gmail.com>

* change default incluster for rustls to use env with tls_server_name

Signed-off-by: clux <sszynrae@gmail.com>

* fix build

Signed-off-by: clux <sszynrae@gmail.com>

* better docs

Signed-off-by: clux <sszynrae@gmail.com>

* simplify feature selection and use current stack precedence

Signed-off-by: clux <sszynrae@gmail.com>

* allow rustls_https_connector to be created from a connector

Signed-off-by: clux <sszynrae@gmail.com>

Signed-off-by: clux <sszynrae@gmail.com>
  • Loading branch information
clux committed Dec 15, 2022
1 parent d28a715 commit 7b9c4fe
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 25 deletions.
2 changes: 1 addition & 1 deletion kube-client/Cargo.toml
Expand Up @@ -57,7 +57,7 @@ kube-core = { path = "../kube-core", version = "=0.76.0" }
jsonpath_lib = { version = "0.3.0", optional = true }
tokio-util = { version = "0.7.0", optional = true, features = ["io", "codec"] }
hyper = { version = "0.14.13", optional = true, features = ["client", "http1", "stream", "tcp"] }
hyper-rustls = { version = "0.23.0", optional = true }
hyper-rustls = { version = "0.23.2", optional = true }
tokio-tungstenite = { version = "0.18.0", optional = true }
tower = { version = "0.4.6", optional = true, features = ["buffer", "filter", "util"] }
tower-http = { version = "0.3.2", optional = true, features = ["auth", "map-response-body", "trace"] }
Expand Down
5 changes: 1 addition & 4 deletions kube-client/src/client/builder.rs
Expand Up @@ -85,10 +85,7 @@ impl TryFrom<Config> for ClientBuilder<BoxService<Request<hyper::Body>, Response
#[cfg(feature = "openssl-tls")]
let connector = config.openssl_https_connector_with_connector(connector)?;
#[cfg(all(not(feature = "openssl-tls"), feature = "rustls-tls"))]
let connector = hyper_rustls::HttpsConnector::from((
connector,
std::sync::Arc::new(config.rustls_client_config()?),
));
let connector = config.rustls_https_connector_with_connector(connector)?;

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

Expand Down
46 changes: 42 additions & 4 deletions kube-client/src/client/config_ext.rs
Expand Up @@ -43,6 +43,29 @@ pub trait ConfigExt: private::Sealed {
#[cfg(feature = "rustls-tls")]
fn rustls_https_connector(&self) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;

/// Create [`hyper_rustls::HttpsConnector`] based on config and `connector`.
///
/// # Example
///
/// ```rust
/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
/// # use kube::{client::ConfigExt, Config};
/// # use hyper::client::HttpConnector;
/// let config = Config::infer().await?;
/// let mut connector = HttpConnector::new();
/// connector.enforce_http(false);
/// let https = config.rustls_https_connector_with_connector(connector)?;
/// let hyper_client: hyper::Client<_, hyper::Body> = hyper::Client::builder().build(https);
/// # Ok(())
/// # }
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
#[cfg(feature = "rustls-tls")]
fn rustls_https_connector_with_connector(
&self,
connector: hyper::client::HttpConnector,
) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>>;

/// Create [`rustls::ClientConfig`] based on config.
/// # Example
///
Expand Down Expand Up @@ -186,15 +209,30 @@ impl ConfigExt for Config {

#[cfg(feature = "rustls-tls")]
fn rustls_https_connector(&self) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>> {
let rustls_config = std::sync::Arc::new(self.rustls_client_config()?);
let mut http = hyper::client::HttpConnector::new();
http.enforce_http(false);
Ok(hyper_rustls::HttpsConnector::from((http, rustls_config)))
let mut connector = hyper::client::HttpConnector::new();
connector.enforce_http(false);
self.rustls_https_connector_with_connector(connector)
}

#[cfg(feature = "rustls-tls")]
fn rustls_https_connector_with_connector(
&self,
connector: hyper::client::HttpConnector,
) -> Result<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>> {
let rustls_config = self.rustls_client_config()?;
let mut builder = hyper_rustls::HttpsConnectorBuilder::new()
.with_tls_config(rustls_config)
.https_or_http();
if let Some(tsn) = self.tls_server_name.as_ref() {
builder = builder.with_server_name(tsn.clone());
}
Ok(builder.enable_http1().wrap_connector(connector))
}

#[cfg(feature = "openssl-tls")]
fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder> {
let identity = self.exec_identity_pem().or_else(|| self.identity_pem());
// TODO: pass self.tls_server_name for openssl
tls::openssl_tls::ssl_connector_builder(identity.as_ref(), self.root_cert.as_ref())
.map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateSslConnector(e)))
}
Expand Down
6 changes: 6 additions & 0 deletions kube-client/src/config/file_config.rs
Expand Up @@ -105,6 +105,12 @@ pub struct Cluster {
#[serde(rename = "proxy-url")]
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy_url: Option<String>,
/// Name used to check server certificate.
///
/// If `tls_server_name` is `None`, the hostname used to contact the server is used.
#[serde(rename = "tls-server-name")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tls_server_name: Option<String>,
/// Additional information for extenders so that reads and writes don't clobber unknown fields
#[serde(skip_serializing_if = "Option::is_none")]
pub extensions: Option<Vec<NamedExtension>>,
Expand Down
39 changes: 23 additions & 16 deletions kube-client/src/config/mod.rs
Expand Up @@ -159,6 +159,10 @@ pub struct Config {
// TODO Actually support proxy or create an example with custom client
/// Optional proxy URL.
pub proxy_url: Option<http::Uri>,
/// If set, apiserver certificate will be validated to contain this string
///
/// If not set, the `cluster_url` is used instead
pub tls_server_name: Option<String>,
}

impl Config {
Expand All @@ -180,6 +184,7 @@ impl Config {
accept_invalid_certs: false,
auth_info: AuthInfo::default(),
proxy_url: None,
tls_server_name: None,
}
}

Expand Down Expand Up @@ -213,20 +218,20 @@ impl Config {

/// Load an in-cluster Kubernetes client configuration using
/// [`Config::incluster_env`].
#[cfg(not(feature = "rustls-tls"))]
pub fn incluster() -> Result<Self, InClusterError> {
Self::incluster_env()
}

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

/// Load an in-cluster config using the `KUBERNETES_SERVICE_HOST` and
Expand All @@ -236,9 +241,7 @@ impl Config {
/// `/var/run/secrets/kubernetes.io/serviceaccount/`.
///
/// This method matches the behavior of the official Kubernetes client
/// libraries, but it is not compatible with the `rustls-tls` feature . When
/// this feature is enabled, [`Config::incluster_dns`] should be used
/// instead. See <https://github.com/kube-rs/kube/issues/1003>.
/// libraries and is the default for both TLS stacks.
pub fn incluster_env() -> Result<Self, InClusterError> {
let uri = incluster_config::try_kube_from_env()?;
Self::incluster_with_uri(uri)
Expand All @@ -251,7 +254,9 @@ impl Config {
/// `/var/run/secrets/kubernetes.io/serviceaccount/`.
///
/// This behavior does not match that of the official Kubernetes clients,
/// but this approach is compatible with the `rustls-tls` feature.
/// but this approach is compatible with the `rustls-tls` feature
/// without setting `tls_server_name`.
/// See <https://github.com/kube-rs/kube/issues/1003>.
pub fn incluster_dns() -> Result<Self, InClusterError> {
Self::incluster_with_uri(incluster_config::kube_dns())
}
Expand All @@ -275,6 +280,7 @@ impl Config {
..Default::default()
},
proxy_url: None,
tls_server_name: None,
})
}

Expand Down Expand Up @@ -333,6 +339,7 @@ impl Config {
accept_invalid_certs,
proxy_url: loader.proxy_url()?,
auth_info: loader.user,
tls_server_name: loader.cluster.tls_server_name,
})
}

Expand Down

0 comments on commit 7b9c4fe

Please sign in to comment.