diff --git a/.circleci/config.yml b/.circleci/config.yml index 6ecb94e2..16e8f144 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -63,15 +63,23 @@ jobs: RUST_BACKTRACE=1 cargo build --verbose --features ssl RUST_BACKTRACE=1 cargo build --examples -j 8 - run: - name: Unit testing + name: Unit testing with ssl feature command: | RUST_BACKTRACE=1 cargo test -j 8 --verbose --features ssl + - run: + name: Unit testing with ssl-rustls feature + command: | + RUST_BACKTRACE=1 cargo test -j 8 --verbose --features ssl-rustls - run: name: More unit testing command: | docker load -i test-iostream docker load -i test-signal RUST_BACKTRACE=1 cargo test --verbose --features ssl -- --ignored + - run: + name: More unit testing with ssl-rustls + command: | + RUST_BACKTRACE=1 cargo test --verbose --features ssl-rustls -- --ignored - save_cache: key: cache-cargo-target-{{ .Environment.CIRCLE_JOB }}-{{ .Environment.CIRCLECI_CACHE_VERSION }}-{{ checksum "/tmp/build-dep" }} paths: diff --git a/Cargo.lock b/Cargo.lock index e6f6303c..73182c05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,6 +208,7 @@ dependencies = [ "futures", "http", "hyper", + "hyper-rustls", "hyper-tls", "hyperlocal", "log", @@ -216,6 +217,8 @@ dependencies = [ "nix", "openssl", "rand", + "rustls", + "rustls-pemfile", "serde", "serde_json", "tar", @@ -398,6 +401,31 @@ dependencies = [ "wasi", ] +[[package]] +name = "h2" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -463,6 +491,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -476,6 +505,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +dependencies = [ + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -536,6 +580,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.12" @@ -881,6 +935,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustix" version = "0.37.15" @@ -895,6 +964,49 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.13" @@ -916,6 +1028,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.8.2" @@ -989,6 +1111,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1123,6 +1251,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -1145,6 +1283,7 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", + "tracing", ] [[package]] @@ -1205,6 +1344,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -1292,6 +1437,16 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 42d11526..827a3253 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ experimental = [] # Enable OpenSSL both directly and for Hyper. ssl = ["openssl", "native-tls", "hyper-tls"] +ssl-rustls = ["rustls", "hyper-rustls", "rustls-pemfile"] [dependencies] async-trait = "0.1" @@ -34,7 +35,10 @@ futures = "0.3" http = "0.2" hyper = { version = "0.14", features = ["client", "http1", "stream", "tcp"] } openssl = { version = "0.10", optional = true } +rustls = { version = "0.21", optional = true } +rustls-pemfile = { version = "1.0.0", optional = true } hyper-tls = { version = "0.5", optional = true } +hyper-rustls = { version = "0.24", optional = true, features = ["http2"] } serde = { version = "1", features = ["derive"] } serde_json = "1" url = "2" diff --git a/src/docker.rs b/src/docker.rs index 49ffc2ce..ad948db6 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -282,7 +282,7 @@ impl Docker { .into()) } - #[cfg(feature = "openssl")] + #[cfg(any(feature = "openssl", feature = "rustls"))] pub fn connect_with_ssl( addr: &str, key: &Path, @@ -298,7 +298,7 @@ impl Docker { Ok(Docker::new(client, Protocol::Tcp)) } - #[cfg(not(feature = "openssl"))] + #[cfg(not(any(feature = "openssl", feature = "rustls")))] pub fn connect_with_ssl( _addr: &str, _key: &Path, diff --git a/src/errors.rs b/src/errors.rs index 3d529650..baf88584 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -37,6 +37,9 @@ pub enum Error { #[cfg(feature = "openssl")] #[error("ssl error")] OpenSsl(#[from] openssl::error::ErrorStack), + #[cfg(feature = "rustls")] + #[error("ssl error")] + Rustls(#[from] rustls::Error), #[error("could not connect: {}", addr)] CouldNotConnect { addr: String, source: Box }, #[error("could not find DOCKER_CERT_PATH")] diff --git a/src/hyper_client.rs b/src/hyper_client.rs index 7b707261..bacccd90 100644 --- a/src/hyper_client.rs +++ b/src/hyper_client.rs @@ -11,6 +11,8 @@ enum Client { HttpClient(hyper::Client), #[cfg(feature = "openssl")] HttpsClient(hyper::Client>), + #[cfg(feature = "rustls")] + HttpsClient(hyper::Client>), #[cfg(unix)] UnixClient(hyper::Client), } @@ -21,6 +23,8 @@ impl Client { Client::HttpClient(http_client) => http_client.request(req), #[cfg(feature = "openssl")] Client::HttpsClient(https_client) => https_client.request(req), + #[cfg(feature = "rustls")] + Client::HttpsClient(https_client) => https_client.request(req), #[cfg(unix)] Client::UnixClient(unix_client) => unix_client.request(req), } @@ -174,6 +178,67 @@ impl HyperClient { Ok(Self::new(Client::HttpsClient(client), url)) } + #[cfg(feature = "rustls")] + pub fn connect_with_ssl( + addr: &str, + key: &Path, + cert: &Path, + ca: &Path, + ) -> Result { + use log::warn; + use rustls::{Certificate, PrivateKey}; + use rustls_pemfile::Item; + use std::fs::File; + use std::io::BufReader; + + let addr_https = addr.clone().replacen("tcp://", "https://", 1); + let url = Uri::from_str(&addr_https).map_err(|err| DwError::InvalidUri { + var: addr_https, + source: err, + })?; + + let mut key_buf = BufReader::new(File::open(key)?); + let mut cert_buf = BufReader::new(File::open(cert)?); + let mut ca_buf = BufReader::new(File::open(ca)?); + + let private_key = match rustls_pemfile::rsa_private_keys(&mut key_buf)? { + keys if keys.is_empty() => return Err(rustls::Error::NoCertificatesPresented.into()), + mut keys if keys.len() == 1 => PrivateKey(keys.remove(0)), + mut keys => { + // if keys.len() > 1 + warn!("Private key file contains multiple keys. Using only first one."); + PrivateKey(keys.remove(0)) + } + }; + let certs = rustls_pemfile::read_all(&mut cert_buf)? + .into_iter() + .filter_map(|item| match item { + Item::X509Certificate(c) => Some(Certificate(c)), + _ => None, + }) + .collect(); + let mut root_certs = rustls::RootCertStore::empty(); + for c in rustls_pemfile::certs(&mut ca_buf)? { + root_certs.add(&Certificate(c))?; + } + + let config = rustls::ClientConfig::builder() + .with_safe_default_cipher_suites() + .with_safe_default_kx_groups() + .with_safe_default_protocol_versions() + .unwrap() + .with_root_certificates(root_certs) + .with_single_cert(certs, private_key) + .expect("bad certificate/key"); + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_tls_config(config) + .https_or_http() + .enable_all_versions() + .build(); + let client = hyper::Client::builder().build::<_, hyper::Body>(https); + Ok(Self::new(Client::HttpsClient(client), url)) + } + pub fn connect_with_http(addr: &str) -> Result { // This ensures that using docker-machine-esque addresses work with Hyper. let addr_https = addr.to_string().replace("tcp://", "http://");