Skip to content

Commit

Permalink
Merge pull request #704 from kazk/chore/rustls020
Browse files Browse the repository at this point in the history
  • Loading branch information
kazk committed Nov 23, 2021
2 parents f780319 + 988705f commit 6b23251
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 97 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ UNRELEASED
===================
* see https://github.com/kube-rs/kube-rs/compare/0.64.0...master

* BREAKING: Removed `kube::Error::OpenSslError`
* BREAKING: Added `kube::Error::NativeTls(kube::client::NativeTlsError)` for errors from native TLS
* BREAKING: Removed `kube::Error::OpenSslError` - #716
* BREAKING: Removed `kube::Error::SslError` - #704 and #716
* BREAKING: Added `kube::Error::NativeTls(kube::client::NativeTlsError)` for errors from Native TLS - #716
* BREAKING: Added `kube::Error::RustlsTls(kube::client::RustlsTlsError)` for errors from Rustls TLS - #704
* Updated `rustls` to 0.20.1 - #704

0.64.0 / 2021-11-16
===================
Expand Down
7 changes: 7 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,10 @@ allow-git = []

[bans]
multiple-versions = "deny"
skip = [
# warp uses an older version of rustls (warp is only used in the examples)
{ name = "rustls", version = "=0.19.1" },
{ name = "webpki", version = "=0.21.4" },
{ name = "tokio-rustls", version = "=0.22.0" },
{ name = "sct", version = "=0.6.1" },
]
4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ How deriving `CustomResource` works in practice, and how it interacts with the [
cargo run --example crd_api
cargo run --example crd_derive
cargo run --example crd_derive_schema
cargo run --example crd_derive_no_schema --no-default-features --features=native-tls
cargo run --example crd_derive_no_schema --no-default-features --features=native-tls,latest
```

The last one opts out from the default `schema` feature from `kube-derive` (and thus the need for you to derive/impl `JsonSchema`).
Expand Down Expand Up @@ -108,5 +108,5 @@ The `crd_reflector` will just await changes. You can run `kubectl apply -f crd-b
Disable default features and enable `rustls-tls`:

```sh
cargo run --example pod_watcher --no-default-features --features=rustls-tls
cargo run --example pod_watcher --no-default-features --features=rustls-tls,latest,runtime
```
7 changes: 3 additions & 4 deletions kube-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ edition = "2021"
[features]
default = ["client", "native-tls"]
native-tls = ["openssl", "hyper-tls", "tokio-native-tls"]
rustls-tls = ["rustls", "rustls-pemfile", "hyper-rustls", "webpki"]
rustls-tls = ["rustls", "rustls-pemfile", "hyper-rustls"]
openssl-tls = ["openssl", "hyper-openssl"]
ws = ["client", "tokio-tungstenite", "rand", "kube-core/ws"]
oauth = ["client", "tame-oauth"]
Expand Down Expand Up @@ -52,17 +52,16 @@ futures = { version = "0.3.17", optional = true }
pem = { version = "1.0.1", optional = true }
openssl = { version = "0.10.36", optional = true }
tokio-native-tls = { version = "0.3.0", optional = true }
rustls = { version = "0.19.1", features = ["dangerous_configuration"], optional = true }
rustls = { version = "0.20.1", features = ["dangerous_configuration"], optional = true }
rustls-pemfile = { version = "0.2.1", optional = true }
webpki = { version = "0.21.4", optional = true }
bytes = { version = "1.1.0", optional = true }
tokio = { version = "1.14.0", features = ["time", "signal", "sync"], optional = true }
kube-core = { path = "../kube-core", version = "^0.64.0"}
jsonpath_lib = { version = "0.3.0", optional = true }
tokio-util = { version = "0.6.8", optional = true, features = ["io", "codec"] }
hyper = { version = "0.14.13", optional = true, features = ["client", "http1", "stream", "tcp"] }
hyper-tls = { version = "0.5.0", optional = true }
hyper-rustls = { version = "0.22.1", optional = true }
hyper-rustls = { version = "0.23.0", optional = true }
tokio-tungstenite = { version = "0.15.0", optional = true }
tower = { version = "0.4.6", optional = true, features = ["buffer", "util"] }
tower-http = { version = "0.1.1", optional = true, features = ["auth", "map-response-body", "trace"] }
Expand Down
6 changes: 5 additions & 1 deletion kube-client/src/client/auth/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ impl Gcp {
not(any(feature = "openssl-tls", feature = "native-tls")),
feature = "rustls-tls"
))]
let https = hyper_rustls::HttpsConnector::with_native_roots();
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_only()
.enable_http1()
.build();

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

Expand Down
5 changes: 3 additions & 2 deletions kube-client/src/client/config_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,11 @@ impl ConfigExt for Config {
#[cfg(feature = "rustls-tls")]
fn rustls_client_config(&self) -> Result<rustls::ClientConfig> {
tls::rustls_tls::rustls_client_config(
self.identity_pem.as_ref(),
self.root_cert.as_ref(),
self.identity_pem.as_deref(),
self.root_cert.as_deref(),
self.accept_invalid_certs,
)
.map_err(Error::RustlsTls)
}

#[cfg(feature = "rustls-tls")]
Expand Down
1 change: 1 addition & 0 deletions kube-client/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod tls;
#[cfg(feature = "native-tls")] pub use tls::native_tls::Error as NativeTlsError;
#[cfg(feature = "openssl-tls")]
pub use tls::openssl_tls::Error as OpensslTlsError;
#[cfg(feature = "rustls-tls")] pub use tls::rustls_tls::Error as RustlsTlsError;
#[cfg(feature = "ws")] mod upgrade;

#[cfg(feature = "oauth")]
Expand Down
171 changes: 89 additions & 82 deletions kube-client/src/client/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,109 +80,116 @@ pub mod native_tls {

#[cfg(feature = "rustls-tls")]
pub mod rustls_tls {
use std::sync::Arc;
use hyper_rustls::ConfigBuilderExt;
use rustls::{
self,
client::{ServerCertVerified, ServerCertVerifier},
Certificate, ClientConfig, PrivateKey,
};
use thiserror::Error;

use rustls::{self, Certificate, ClientConfig, ServerCertVerified, ServerCertVerifier};
use webpki::DNSNameRef;
/// Errors from Rustls
#[derive(Debug, Error)]
pub enum Error {
/// Identity PEM is invalid
#[error("identity PEM is invalid: {0}")]
InvalidIdentityPem(#[source] std::io::Error),

use crate::{Error, Result};
/// Identity PEM is missing a private key: the key must be PKCS8 or RSA/PKCS1
#[error("identity PEM is missing a private key: the key must be PKCS8 or RSA/PKCS1")]
MissingPrivateKey,

/// Identity PEM is missing certificate
#[error("identity PEM is missing certificate")]
MissingCertificate,

/// Invalid private key
#[error("invalid private key: {0}")]
InvalidPrivateKey(#[source] rustls::Error),

// Using type-erased error to avoid depending on webpki
/// Failed to add a root certificate
#[error("failed to add a root certificate: {0}")]
AddRootCertificate(#[source] Box<dyn std::error::Error + Send + Sync>),
}

/// Create `rustls::ClientConfig`.
pub fn rustls_client_config(
identity_pem: Option<&Vec<u8>>,
root_cert: Option<&Vec<Vec<u8>>>,
identity_pem: Option<&[u8]>,
root_certs: Option<&[Vec<u8>]>,
accept_invalid: bool,
) -> Result<ClientConfig> {
use std::io::Cursor;

// Based on code from `reqwest`
let mut client_config = ClientConfig::new();
if let Some(buf) = identity_pem {
let (key, certs) = {
let mut pem = Cursor::new(buf);
let certs = rustls_pemfile::certs(&mut pem)
.and_then(|certs| {
if certs.is_empty() {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No X.509 Certificates Found",
))
} else {
Ok(certs.into_iter().map(rustls::Certificate).collect::<Vec<_>>())
}
})
.map_err(|_| Error::SslError("No valid certificate was found".into()))?;
pem.set_position(0);

// TODO Support EC Private Key to support k3d. Need to convert to PKCS#8 or RSA (PKCS#1).
// `openssl pkcs8 -topk8 -nocrypt -in ec.pem -out pkcs8.pem`
// https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations#EC_Private_Key_File_Formats
let mut sk = rustls_pemfile::pkcs8_private_keys(&mut pem)
.and_then(|keys| {
if keys.is_empty() {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No PKCS8 Key Found",
))
} else {
Ok(keys.into_iter().map(rustls::PrivateKey).collect::<Vec<_>>())
}
})
.or_else(|_| {
pem.set_position(0);
rustls_pemfile::rsa_private_keys(&mut pem).and_then(|keys| {
if keys.is_empty() {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No RSA Key Found",
))
} else {
Ok(keys.into_iter().map(rustls::PrivateKey).collect::<Vec<_>>())
}
})
})
.map_err(|_| Error::SslError("No valid private key was found".into()))?;

if let (Some(sk), false) = (sk.pop(), certs.is_empty()) {
(sk, certs)
} else {
return Err(Error::SslError("private key or certificate not found".into()));
}
};
) -> Result<ClientConfig, Error> {
let config_builder = if let Some(certs) = root_certs {
ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_store(certs)?)
} else {
ClientConfig::builder().with_safe_defaults().with_native_roots()
};

let mut client_config = if let Some((chain, pkey)) = identity_pem.map(client_auth).transpose()? {
config_builder
.with_single_cert(chain, pkey)
.map_err(Error::InvalidPrivateKey)?
} else {
config_builder.with_no_client_auth()
};

if accept_invalid {
client_config
.set_single_client_cert(certs, key)
.map_err(|e| Error::SslError(format!("{}", e)))?;
.dangerous()
.set_certificate_verifier(std::sync::Arc::new(NoCertificateVerification {}));
}
Ok(client_config)
}

if let Some(ders) = root_cert {
for der in ders {
client_config
.root_store
.add(&Certificate(der.to_owned()))
.map_err(|e| Error::SslError(format!("{}", e)))?;
}
fn root_store(root_certs: &[Vec<u8>]) -> Result<rustls::RootCertStore, Error> {
let mut root_store = rustls::RootCertStore::empty();
for der in root_certs {
root_store
.add(&Certificate(der.clone()))
.map_err(|e| Error::AddRootCertificate(Box::new(e)))?;
}
Ok(root_store)
}

if accept_invalid {
client_config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
// TODO Support EC Private Key to support k3d. Need to convert to PKCS#8 or RSA (PKCS#1).
// `openssl pkcs8 -topk8 -nocrypt -in ec.pem -out pkcs8.pem`
// https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations#EC_Private_Key_File_Formats
fn client_auth(data: &[u8]) -> Result<(Vec<Certificate>, PrivateKey), Error> {
use rustls_pemfile::Item;

let mut cert_chain = Vec::new();
let mut pkcs8_key = None;
let mut rsa_key = None;
let mut reader = std::io::Cursor::new(data);
for item in rustls_pemfile::read_all(&mut reader).map_err(Error::InvalidIdentityPem)? {
match item {
Item::X509Certificate(cert) => cert_chain.push(Certificate(cert)),
Item::PKCS8Key(key) => pkcs8_key = Some(PrivateKey(key)),
Item::RSAKey(key) => rsa_key = Some(PrivateKey(key)),
}
}

Ok(client_config)
let private_key = pkcs8_key.or(rsa_key).ok_or(Error::MissingPrivateKey)?;
if cert_chain.is_empty() {
return Err(Error::MissingCertificate);
}
Ok((cert_chain, private_key))
}

struct NoCertificateVerification {}

impl ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: DNSNameRef<'_>,
_ocsp: &[u8],
) -> Result<ServerCertVerified, rustls::TLSError> {
_end_entity: &Certificate,
_intermediates: &[Certificate],
_server_name: &rustls::client::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: std::time::SystemTime,
) -> Result<ServerCertVerified, rustls::Error> {
Ok(ServerCertVerified::assertion())
}
}
Expand Down
10 changes: 6 additions & 4 deletions kube-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ pub enum Error {
#[error("Error from discovery: {0}")]
Discovery(#[source] DiscoveryError),

/// An error with configuring SSL occured
#[error("SslError: {0}")]
SslError(String),

/// Errors from Native TLS
#[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
Expand All @@ -75,6 +71,12 @@ pub enum Error {
#[error("openssl tls error: {0}")]
OpensslTls(#[source] crate::client::OpensslTlsError),

/// Errors from Rustls TLS
#[cfg(feature = "rustls-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
#[error("rustls tls error: {0}")]
RustlsTls(#[source] crate::client::RustlsTlsError),

/// Failed to upgrade to a WebSocket connection
#[cfg(feature = "ws")]
#[cfg_attr(docsrs, doc(cfg(feature = "ws")))]
Expand Down

0 comments on commit 6b23251

Please sign in to comment.