use crate::error::*;
use crate::{parse_host_url, DockerEngineClient, UnixConnector};
use hyper::client::connect::Connect;
use hyper::client::HttpConnector;
use hyper::Client;
use hyper_tls::HttpsConnector;
use native_tls::{Certificate, Identity};
use openssl::pkcs12::Pkcs12;
use openssl::pkey::PKey;
use openssl::x509::X509;
use snafu::ResultExt;
use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;
use std::{env, fs};
use tokio_tls::TlsConnector;
pub type DockerEngineClientOption<C> = Box<dyn Fn(&mut DockerEngineClient<C>) -> Result<(), Error>>;
pub fn from_env_remote_tls(
client: &mut DockerEngineClient<HttpsConnector<HttpConnector>>,
) -> Result<(), Error> {
if let Ok(docker_cert_path) = env::var("DOCKER_CERT_PATH") {
let insecure_skip_verify = if let Ok(tls_verify) = env::var("DOCKER_TLS_VERIFY") {
tls_verify.is_empty()
} else {
false
};
let docker_cert_path = Path::new(&docker_cert_path);
let ca_certificate_pem =
fs::read_to_string(docker_cert_path.join("ca.pem")).context(IoError {})?;
let client_certificate_pem =
fs::read_to_string(docker_cert_path.join("cert.pem")).context(IoError {})?;
let client_key_pem =
fs::read_to_string(docker_cert_path.join("key.pem")).context(IoError {})?;
let pcks12_encoded_client_key = pem_to_pkcs12(&client_certificate_pem, &client_key_pem)?;
let ca_certificate = Certificate::from_pem(ca_certificate_pem.as_bytes())
.context(CertificateParseError {})?;
let client_identity = Identity::from_pkcs12(&pcks12_encoded_client_key, "")
.context(CertificateParseError {})?;
let mut http_connector = HttpConnector::new();
http_connector.enforce_http(false);
let tls_connector = TlsConnector::from(
native_tls::TlsConnector::builder()
.identity(client_identity)
.add_root_certificate(ca_certificate)
.danger_accept_invalid_hostnames(insecure_skip_verify)
.build()
.unwrap(),
);
let https_connector = HttpsConnector::from((http_connector, tls_connector));
client.client = Some(Client::builder().build(https_connector));
}
if let Ok(host) = env::var("DOCKER_HOST") {
with_host(host)(client)?;
}
if let Ok(version) = env::var("DOCKER_API_VERSION") {
with_version(version)(client)?;
}
Ok(())
}
pub fn from_env_remote(client: &mut DockerEngineClient<HttpConnector>) -> Result<(), Error> {
let mut http_connector = HttpConnector::new();
http_connector.enforce_http(false);
client.client = Some(Client::builder().build(http_connector));
if let Ok(host) = env::var("DOCKER_HOST") {
with_host(host)(client)?;
}
if let Ok(version) = env::var("DOCKER_API_VERSION") {
with_version(version)(client)?;
}
Ok(())
}
pub fn from_env(client: &mut DockerEngineClient<UnixConnector>) -> Result<(), Error> {
let unix_connector = UnixConnector;
client.client = Some(Client::builder().build(unix_connector));
if let Ok(host) = env::var("DOCKER_HOST") {
with_host(host)(client)?;
}
if let Ok(version) = env::var("DOCKER_API_VERSION") {
with_version(version)(client)?;
}
Ok(())
}
pub fn with_host<C: Connect + Clone + Send + Sync + 'static>(
host: String,
) -> DockerEngineClientOption<C> {
Box::new(
move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
let host_url = parse_host_url(&host)?;
client.host = host_url.host().map(String::from);
client.proto = host_url.scheme_str().map(String::from);
client.addr = host_url.host().map(String::from);
let path = host_url.path();
client.base_path = if !path.is_empty() {
Some(path.into())
} else {
None
};
Ok(())
},
)
}
pub fn with_timeout<C: Connect + Clone + Send + Sync + 'static>(
timeout: Duration,
) -> DockerEngineClientOption<C> {
Box::new(
move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
client.timeout = timeout;
Ok(())
},
)
}
#[allow(clippy::implicit_hasher)]
pub fn with_http_headers<C: Connect + Clone + Send + Sync + 'static>(
headers: HashMap<String, String>,
) -> DockerEngineClientOption<C> {
Box::new(
move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
if !headers.is_empty() {
client.custom_http_headers = Some(headers.clone());
}
Ok(())
},
)
}
pub fn with_scheme<C: Connect + Clone + Send + Sync + 'static>(
scheme: String,
) -> DockerEngineClientOption<C> {
Box::new(
move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
client.scheme = Some(scheme.clone());
Ok(())
},
)
}
pub fn with_tls_client_config(
cacert_path: String,
cert_path: String,
key_path: String,
) -> DockerEngineClientOption<HttpsConnector<HttpConnector>> {
Box::new(
move |client: &mut DockerEngineClient<HttpsConnector<HttpConnector>>| -> Result<(), Error> {
let ca_certificate_pem = fs::read_to_string(&cacert_path).context(IoError {})?;
let client_certificate_pem = fs::read_to_string(&cert_path).context(IoError {})?;
let client_key_pem = fs::read_to_string(&key_path).context(IoError {})?;
let pcks12_encoded_client_key =
pem_to_pkcs12(&client_certificate_pem, &client_key_pem)?;
let ca_certificate = Certificate::from_pem(ca_certificate_pem.as_bytes())
.context(CertificateParseError {})?;
let client_identity = Identity::from_pkcs12(&pcks12_encoded_client_key, "")
.context(CertificateParseError {})?;
let mut http_connector = HttpConnector::new();
http_connector.enforce_http(false);
let tls_connector = TlsConnector::from(
native_tls::TlsConnector::builder()
.identity(client_identity)
.add_root_certificate(ca_certificate)
.build()
.unwrap(),
);
let https_connector = HttpsConnector::from((http_connector, tls_connector));
client.client = Some(Client::builder().build(https_connector));
Ok(())
},
)
}
pub fn with_version<C: Connect + Clone + Send + Sync + 'static>(
version: String,
) -> DockerEngineClientOption<C> {
Box::new(
move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
if !version.is_empty() {
client.version = version.clone();
client.manual_override = true;
}
Ok(())
},
)
}
pub fn with_api_version_negotiation<C: Connect + Clone + Send + Sync + 'static>(
) -> DockerEngineClientOption<C> {
Box::new(
move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
client.negotiate_version = true;
Ok(())
},
)
}
fn pem_to_pkcs12(client_certificate_pem: &str, client_key_pem: &str) -> Result<Vec<u8>, Error> {
let client_certificate =
X509::from_pem(client_certificate_pem.as_bytes()).context(CryptoError {})?;
let client_private_key =
PKey::private_key_from_pem(client_key_pem.as_bytes()).context(CryptoError {})?;
Pkcs12::builder()
.build("", "", &client_private_key, &client_certificate)
.context(CryptoError {})?
.to_der()
.context(CryptoError {})
}