Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add openssl-tls feature #700

Merged
merged 2 commits into from
Nov 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ jobs:
- name: Test kube with features rustls-tls,ws,oauth
run: cargo test -p kube --lib --no-default-features --features=rustls-tls,ws,oauth
if: matrix.os == 'ubuntu-latest'
- name: Test kube with features openssl-tls,ws,oauth
run: cargo test -p kube --lib --no-default-features --features=openssl-tls,ws,oauth
if: matrix.os == 'ubuntu-latest'
# Feature tests in examples
- name: Test crd_derive_no_schema example
run: cargo test -p examples --example crd_derive_no_schema --no-default-features --features=native-tls,latest
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ UNRELEASED
- If you currently disable default `kube-derive` default features to avoid automatic schema generation, add `#[kube(schema = "disabled")]` to your spec struct instead
* BREAKING: Moved `CustomResource` derive crate overrides into subattribute `#[kube(crates(...))]` - #690
- Replace `#[kube(kube_core = .., k8s_openapi = .., schema = .., serde = .., serde_json = ..)]` with `#[kube(crates(kube_core = .., k8s_openapi = .., schema = .., serde = .., serde_json = ..))]`
* Added `openssl-tls` feature to use `openssl` for TLS on all platforms. Note that, even though `native-tls` uses a platform specific TLS, `kube` requires `openssl` on all platforms because `native-tls` only allows PKCS12 input to load certificates and private key at the moment, and creating PKCS12 requires `openssl`. - #700

### Refining Errors

We started working on improving error ergonomics (tracking issue: #688).
Expand Down
4 changes: 3 additions & 1 deletion kube-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ edition = "2021"
default = ["client", "native-tls"]
native-tls = ["openssl", "hyper-tls", "tokio-native-tls"]
rustls-tls = ["rustls", "rustls-pemfile", "hyper-rustls", "webpki"]
openssl-tls = ["openssl", "hyper-openssl"]
ws = ["client", "tokio-tungstenite", "rand", "kube-core/ws"]
oauth = ["client", "tame-oauth"]
gzip = ["client", "tower-http/decompression-gzip"]
Expand All @@ -32,7 +33,7 @@ deprecated-crd-v1beta1 = ["kube-core/deprecated-crd-v1beta1"]
__non_core = ["tracing", "serde_yaml", "base64"]

[package.metadata.docs.rs]
features = ["client", "native-tls", "rustls-tls", "ws", "oauth", "jsonpatch", "admission", "k8s-openapi/v1_22"]
features = ["client", "native-tls", "rustls-tls", "openssl-tls", "ws", "oauth", "jsonpatch", "admission", "k8s-openapi/v1_22"]
# Define the configuration attribute `docsrs`. Used to enable `doc_cfg` feature.
rustdoc-args = ["--cfg", "docsrs"]

Expand Down Expand Up @@ -70,6 +71,7 @@ tame-oauth = { version = "0.4.7", features = ["gcp"], optional = true }
pin-project = { version = "1.0.4", optional = true }
rand = { version = "0.8.3", optional = true }
tracing = { version = "0.1.29", features = ["log"], optional = true }
hyper-openssl = { version = "0.9.1", optional = true }

[dependencies.k8s-openapi]
version = "0.13.1"
Expand Down
29 changes: 22 additions & 7 deletions kube-client/src/client/auth/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub enum Error {
/// OAuth failed with unknown reason
#[error("unknown OAuth error: {0}")]
Unknown(String),

/// Failed to create OpenSSL HTTPS connector
#[cfg(feature = "openssl-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
#[error("failed to create OpenSSL HTTPS connector: {0}")]
CreateOpensslHttpsConnector(#[source] openssl::error::ErrorStack),
}

pub(crate) struct Gcp {
Expand Down Expand Up @@ -94,16 +100,25 @@ impl Gcp {
Ok(TokenOrRequest::Request {
request, scope_hash, ..
}) => {
#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
#[cfg(not(any(feature = "native-tls", feature = "rustls-tls", feature = "openssl-tls")))]
compile_error!(
"At least one of native-tls or rustls-tls feature must be enabled to use oauth feature"
"At least one of native-tls or rustls-tls or openssl-tls feature must be enabled to use oauth feature"
);
// If both are enabled, we use rustls unlike `Client` because there's no need to support ip v4/6 subject matching.
// TODO Allow users to choose when both are enabled.
#[cfg(feature = "rustls-tls")]
let https = hyper_rustls::HttpsConnector::with_native_roots();
#[cfg(all(not(feature = "rustls-tls"), feature = "native-tls"))]
// Current TLS feature precedence when more than one are set:
// 1. openssl-tls
// 2. native-tls
// 3. rustls-tls
#[cfg(feature = "openssl-tls")]
let https =
hyper_openssl::HttpsConnector::new().map_err(Error::CreateOpensslHttpsConnector)?;
#[cfg(all(not(feature = "openssl-tls"), feature = "native-tls"))]
let https = hyper_tls::HttpsConnector::new();
#[cfg(all(
not(any(feature = "openssl-tls", feature = "native-tls")),
feature = "rustls-tls"
))]
let https = hyper_rustls::HttpsConnector::with_native_roots();

let client = hyper::Client::builder().build::<_, hyper::Body>(https);

let res = client
Expand Down
90 changes: 89 additions & 1 deletion kube-client/src/client/config_ext.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use tower::util::Either;

#[cfg(any(feature = "native-tls", feature = "rustls-tls"))] use super::tls;
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "openssl-tls"))]
use super::tls;
use super::{
auth::Auth,
middleware::{AddAuthorizationLayer, AuthLayer, BaseUriLayer, RefreshTokenLayer},
Expand Down Expand Up @@ -96,6 +97,63 @@ pub trait ConfigExt: private::Sealed {
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
#[cfg(feature = "rustls-tls")]
fn rustls_client_config(&self) -> Result<rustls::ClientConfig>;

/// Create [`hyper_openssl::HttpsConnector`] based on config.
/// # Example
///
/// ```rust
/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
/// # use kube::{client::ConfigExt, Config};
/// let config = Config::infer().await?;
/// let https = config.openssl_https_connector()?;
/// # Ok(())
/// # }
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
#[cfg(feature = "openssl-tls")]
fn openssl_https_connector(&self) -> Result<hyper_openssl::HttpsConnector<hyper::client::HttpConnector>>;

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

/// Create [`openssl::ssl::SslConnectorBuilder`] based on config.
/// # Example
///
/// ```rust
/// # async fn doc() -> Result<(), Box<dyn std::error::Error>> {
/// # use hyper::client::HttpConnector;
/// # use kube::{client::ConfigExt, Client, Config};
/// let config = Config::infer().await?;
/// let https = {
/// let mut http = HttpConnector::new();
/// http.enforce_http(false);
/// let ssl = config.openssl_ssl_connector_builder()?;
/// hyper_openssl::HttpsConnector::with_connector(http, ssl)?
/// };
/// # Ok(())
/// # }
/// ```
#[cfg_attr(docsrs, doc(cfg(feature = "openssl-tls")))]
#[cfg(feature = "openssl-tls")]
fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder>;
}

mod private {
Expand Down Expand Up @@ -154,4 +212,34 @@ impl ConfigExt for Config {
http.enforce_http(false);
Ok(hyper_rustls::HttpsConnector::from((http, rustls_config)))
}

#[cfg(feature = "openssl-tls")]
fn openssl_ssl_connector_builder(&self) -> Result<openssl::ssl::SslConnectorBuilder> {
tls::openssl_tls::ssl_connector_builder(self.identity_pem.as_ref(), self.root_cert.as_ref())
.map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateSslConnector(e)))
}

#[cfg(feature = "openssl-tls")]
fn openssl_https_connector(&self) -> Result<hyper_openssl::HttpsConnector<hyper::client::HttpConnector>> {
let mut connector = hyper::client::HttpConnector::new();
connector.enforce_http(false);
self.openssl_https_connector_with_connector(connector)
}

#[cfg(feature = "openssl-tls")]
fn openssl_https_connector_with_connector(
&self,
connector: hyper::client::HttpConnector,
) -> Result<hyper_openssl::HttpsConnector<hyper::client::HttpConnector>> {
let mut https =
hyper_openssl::HttpsConnector::with_connector(connector, self.openssl_ssl_connector_builder()?)
.map_err(|e| Error::OpensslTls(tls::openssl_tls::Error::CreateHttpsConnector(e)))?;
if self.accept_invalid_certs {
https.set_callback(|ssl, _uri| {
ssl.set_verify(openssl::ssl::SslVerifyMode::NONE);
Ok(())
});
}
Ok(https)
}
}
23 changes: 17 additions & 6 deletions kube-client/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ mod config_ext;
pub use auth::Error as AuthError;
pub use config_ext::ConfigExt;
pub mod middleware;
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))] mod tls;
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "openssl-tls"))]
mod tls;
#[cfg(feature = "openssl-tls")]
pub use tls::openssl_tls::Error as OpensslTlsError;
#[cfg(feature = "ws")] mod upgrade;

#[cfg(feature = "oauth")]
Expand Down Expand Up @@ -460,15 +463,23 @@ impl TryFrom<Config> for Client {
let mut connector = HttpConnector::new();
connector.enforce_http(false);

// Note that if both `native_tls` and `rustls` is enabled, `native_tls` is used by default.
// To use `rustls`, disable `native_tls` or create custom client.
// If tls features are not enabled, http connector will be used.
#[cfg(feature = "native-tls")]
// Current TLS feature precedence when more than one are set:
// 1. openssl-tls
// 2. native-tls
// 3. rustls-tls
// Create a custom client to use something else.
// If TLS features are not enabled, http connector will be used.
#[cfg(feature = "openssl-tls")]
let connector = config.openssl_https_connector_with_connector(connector)?;
#[cfg(all(not(feature = "openssl-tls"), feature = "native-tls"))]
let connector = hyper_tls::HttpsConnector::from((
connector,
tokio_native_tls::TlsConnector::from(config.native_tls_connector()?),
));
#[cfg(all(not(feature = "native-tls"), feature = "rustls-tls"))]
#[cfg(all(
not(any(feature = "openssl-tls", feature = "native-tls")),
feature = "rustls-tls"
))]
let connector = hyper_rustls::HttpsConnector::from((
connector,
std::sync::Arc::new(config.rustls_client_config()?),
Expand Down
102 changes: 102 additions & 0 deletions kube-client/src/client/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,105 @@ pub mod rustls_tls {
}
}
}

#[cfg(feature = "openssl-tls")]
pub mod openssl_tls {
use openssl::{
pkey::PKey,
ssl::{SslConnector, SslConnectorBuilder, SslMethod},
x509::X509,
};
use thiserror::Error;

/// Errors from OpenSSL TLS
#[derive(Debug, Error)]
pub enum Error {
/// Failed to create OpenSSL HTTPS connector
#[error("failed to create OpenSSL HTTPS connector: {0}")]
CreateHttpsConnector(#[source] openssl::error::ErrorStack),

/// Failed to create OpenSSL SSL connector
#[error("failed to create OpenSSL SSL connector: {0}")]
CreateSslConnector(#[source] SslConnectorError),
}

/// Errors from creating a `SslConnectorBuilder`
#[derive(Debug, Error)]
pub enum SslConnectorError {
/// Failed to build SslConnectorBuilder
#[error("failed to build SslConnectorBuilder: {0}")]
CreateBuilder(#[source] openssl::error::ErrorStack),

/// Failed to deserialize PEM-encoded chain of certificates
#[error("failed to deserialize PEM-encoded chain of certificates: {0}")]
DeserializeCertificateChain(#[source] openssl::error::ErrorStack),

/// Failed to deserialize PEM-encoded private key
#[error("failed to deserialize PEM-encoded private key: {0}")]
DeserializePrivateKey(#[source] openssl::error::ErrorStack),

/// Failed to set private key
#[error("failed to set private key: {0}")]
SetPrivateKey(#[source] openssl::error::ErrorStack),

/// Failed to get a leaf certificate, the certificate chain is empty
#[error("failed to get a leaf certificate, the certificate chain is empty")]
GetLeafCertificate,

/// Failed to set the leaf certificate
#[error("failed to set the leaf certificate: {0}")]
SetLeafCertificate(#[source] openssl::error::ErrorStack),

/// Failed to append a certificate to the chain
#[error("failed to append a certificate to the chain: {0}")]
AppendCertificate(#[source] openssl::error::ErrorStack),

/// Failed to deserialize DER-encoded root certificate
#[error("failed to deserialize DER-encoded root certificate: {0}")]
DeserializeRootCertificate(#[source] openssl::error::ErrorStack),

/// Failed to add a root certificate
#[error("failed to add a root certificate: {0}")]
AddRootCertificate(#[source] openssl::error::ErrorStack),
}

/// Create `openssl::ssl::SslConnectorBuilder` required for `hyper_openssl::HttpsConnector`.
pub fn ssl_connector_builder(
identity_pem: Option<&Vec<u8>>,
root_certs: Option<&Vec<Vec<u8>>>,
) -> Result<SslConnectorBuilder, SslConnectorError> {
let mut builder =
SslConnector::builder(SslMethod::tls()).map_err(SslConnectorError::CreateBuilder)?;
if let Some(pem) = identity_pem {
let mut chain = X509::stack_from_pem(pem)
.map_err(SslConnectorError::DeserializeCertificateChain)?
.into_iter();
let leaf_cert = chain.next().ok_or(SslConnectorError::GetLeafCertificate)?;
builder
.set_certificate(&leaf_cert)
.map_err(SslConnectorError::SetLeafCertificate)?;
for cert in chain {
builder
.add_extra_chain_cert(cert)
.map_err(SslConnectorError::AppendCertificate)?;
}

let pkey = PKey::private_key_from_pem(pem).map_err(SslConnectorError::DeserializePrivateKey)?;
builder
.set_private_key(&pkey)
.map_err(SslConnectorError::SetPrivateKey)?;
}

if let Some(ders) = root_certs {
for der in ders {
let cert = X509::from_der(der).map_err(SslConnectorError::DeserializeRootCertificate)?;
builder
.cert_store_mut()
.add_cert(cert)
.map_err(SslConnectorError::AddRootCertificate)?;
}
}

Ok(builder)
}
}
6 changes: 6 additions & 0 deletions kube-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ pub enum Error {
#[error("OpensslError: {0}")]
OpensslError(#[source] openssl::error::ErrorStack),

/// Errors from OpenSSL TLS
#[cfg(feature = "openssl-tls")]
#[cfg_attr(docsrs, doc(feature = "openssl-tls"))]
#[error("openssl tls error: {0}")]
OpensslTls(#[source] crate::client::OpensslTlsError),

/// Failed to upgrade to a WebSocket connection
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
Expand Down
3 changes: 2 additions & 1 deletion kube/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ edition = "2021"
default = ["client", "native-tls"]
native-tls = ["kube-client/native-tls"]
rustls-tls = ["kube-client/rustls-tls"]
openssl-tls = ["kube-client/openssl-tls"]
ws = ["kube-client/ws", "kube-core/ws"]
oauth = ["kube-client/oauth"]
gzip = ["kube-client/gzip"]
Expand All @@ -31,7 +32,7 @@ runtime = ["kube-runtime"]
deprecated-crd-v1beta1 = ["kube-core/deprecated-crd-v1beta1"]

[package.metadata.docs.rs]
features = ["client", "native-tls", "rustls-tls", "derive", "ws", "oauth", "jsonpatch", "admission", "runtime", "k8s-openapi/v1_22"]
features = ["client", "native-tls", "rustls-tls", "openssl-tls", "derive", "ws", "oauth", "jsonpatch", "admission", "runtime", "k8s-openapi/v1_22"]
# Define the configuration attribute `docsrs`. Used to enable `doc_cfg` feature.
rustdoc-args = ["--cfg", "docsrs"]

Expand Down