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

Update rustls to 0.20.1 #704

Merged
merged 6 commits into from
Nov 23, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()
kazk marked this conversation as resolved.
Show resolved Hide resolved
};

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()
};
clux marked this conversation as resolved.
Show resolved Hide resolved

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