docker_client_async/
opts.rs

1/*
2 * Copyright 2020 Damian Peckett <damian@pecke.tt>.
3 * Copyright 2013-2018 Docker, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18//! Docker client configuration and options.
19
20use crate::error::*;
21use crate::{parse_host_url, DockerEngineClient, UnixConnector};
22use hyper::client::connect::Connect;
23use hyper::client::HttpConnector;
24use hyper::Client;
25use hyper_tls::HttpsConnector;
26use native_tls::{Certificate, Identity};
27use openssl::pkcs12::Pkcs12;
28use openssl::pkey::PKey;
29use openssl::x509::X509;
30use snafu::ResultExt;
31use std::collections::HashMap;
32use std::path::Path;
33use std::time::Duration;
34use std::{env, fs};
35use tokio_tls::TlsConnector;
36
37/// DockerEngineClientOption is a configuration option to initialize a client.
38pub type DockerEngineClientOption<C> = Box<dyn Fn(&mut DockerEngineClient<C>) -> Result<(), Error>>;
39
40/// from_env_remote_tls configures a remote Docker client with values from environment variables.
41/// Supported environment variables:
42/// DOCKER_HOST to set the url to the docker server.
43/// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
44/// DOCKER_CERT_PATH to load the TLS certificates from.
45/// DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
46pub fn from_env_remote_tls(
47    client: &mut DockerEngineClient<HttpsConnector<HttpConnector>>,
48) -> Result<(), Error> {
49    if let Ok(docker_cert_path) = env::var("DOCKER_CERT_PATH") {
50        let insecure_skip_verify = if let Ok(tls_verify) = env::var("DOCKER_TLS_VERIFY") {
51            tls_verify.is_empty()
52        } else {
53            false
54        };
55
56        let docker_cert_path = Path::new(&docker_cert_path);
57        let ca_certificate_pem =
58            fs::read_to_string(docker_cert_path.join("ca.pem")).context(IoError {})?;
59        let client_certificate_pem =
60            fs::read_to_string(docker_cert_path.join("cert.pem")).context(IoError {})?;
61        let client_key_pem =
62            fs::read_to_string(docker_cert_path.join("key.pem")).context(IoError {})?;
63
64        let pcks12_encoded_client_key = pem_to_pkcs12(&client_certificate_pem, &client_key_pem)?;
65
66        let ca_certificate = Certificate::from_pem(ca_certificate_pem.as_bytes())
67            .context(CertificateParseError {})?;
68        let client_identity = Identity::from_pkcs12(&pcks12_encoded_client_key, "")
69            .context(CertificateParseError {})?;
70
71        let mut http_connector = HttpConnector::new();
72        http_connector.enforce_http(false);
73        let tls_connector = TlsConnector::from(
74            native_tls::TlsConnector::builder()
75                .identity(client_identity)
76                .add_root_certificate(ca_certificate)
77                .danger_accept_invalid_hostnames(insecure_skip_verify)
78                .build()
79                .unwrap(),
80        );
81        let https_connector = HttpsConnector::from((http_connector, tls_connector));
82        client.client = Some(Client::builder().build(https_connector));
83    }
84    if let Ok(host) = env::var("DOCKER_HOST") {
85        with_host(host)(client)?;
86    }
87    if let Ok(version) = env::var("DOCKER_API_VERSION") {
88        with_version(version)(client)?;
89    }
90    Ok(())
91}
92
93/// from_env_remote configures a remote Docker client with values from environment variables.
94/// Supported environment variables:
95/// DOCKER_HOST to set the url to the docker server.
96/// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
97pub fn from_env_remote(client: &mut DockerEngineClient<HttpConnector>) -> Result<(), Error> {
98    let mut http_connector = HttpConnector::new();
99    http_connector.enforce_http(false);
100    client.client = Some(Client::builder().build(http_connector));
101
102    if let Ok(host) = env::var("DOCKER_HOST") {
103        with_host(host)(client)?;
104    }
105    if let Ok(version) = env::var("DOCKER_API_VERSION") {
106        with_version(version)(client)?;
107    }
108    Ok(())
109}
110
111/// from_env configures a local client with values from environment variables.
112/// Supported environment variables:
113/// DOCKER_HOST to set the url to the docker server.
114/// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
115pub fn from_env(client: &mut DockerEngineClient<UnixConnector>) -> Result<(), Error> {
116    let unix_connector = UnixConnector;
117    client.client = Some(Client::builder().build(unix_connector));
118
119    if let Ok(host) = env::var("DOCKER_HOST") {
120        with_host(host)(client)?;
121    }
122    if let Ok(version) = env::var("DOCKER_API_VERSION") {
123        with_version(version)(client)?;
124    }
125    Ok(())
126}
127
128/// with_host overrides the client host with the specified one.
129pub fn with_host<C: Connect + Clone + Send + Sync + 'static>(
130    host: String,
131) -> DockerEngineClientOption<C> {
132    Box::new(
133        move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
134            let host_url = parse_host_url(&host)?;
135            client.host = host_url.host().map(String::from);
136            client.proto = host_url.scheme_str().map(String::from);
137            client.addr = host_url.host().map(String::from);
138
139            let path = host_url.path();
140            client.base_path = if !path.is_empty() {
141                Some(path.into())
142            } else {
143                None
144            };
145            Ok(())
146        },
147    )
148}
149
150/// with_timeout configures the time limit for requests made by the HTTP client.
151pub fn with_timeout<C: Connect + Clone + Send + Sync + 'static>(
152    timeout: Duration,
153) -> DockerEngineClientOption<C> {
154    Box::new(
155        move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
156            client.timeout = timeout;
157            Ok(())
158        },
159    )
160}
161
162/// with_http_headers overrides the client default http headers.
163#[allow(clippy::implicit_hasher)]
164pub fn with_http_headers<C: Connect + Clone + Send + Sync + 'static>(
165    headers: HashMap<String, String>,
166) -> DockerEngineClientOption<C> {
167    Box::new(
168        move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
169            if !headers.is_empty() {
170                client.custom_http_headers = Some(headers.clone());
171            }
172            Ok(())
173        },
174    )
175}
176
177/// with_scheme overrides the client scheme with the specified one.
178pub fn with_scheme<C: Connect + Clone + Send + Sync + 'static>(
179    scheme: String,
180) -> DockerEngineClientOption<C> {
181    Box::new(
182        move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
183            client.scheme = Some(scheme.clone());
184            Ok(())
185        },
186    )
187}
188
189/// with_tls_client_config applies a tls config to the client transport.
190pub fn with_tls_client_config(
191    cacert_path: String,
192    cert_path: String,
193    key_path: String,
194) -> DockerEngineClientOption<HttpsConnector<HttpConnector>> {
195    Box::new(
196        move |client: &mut DockerEngineClient<HttpsConnector<HttpConnector>>| -> Result<(), Error> {
197            let ca_certificate_pem = fs::read_to_string(&cacert_path).context(IoError {})?;
198            let client_certificate_pem = fs::read_to_string(&cert_path).context(IoError {})?;
199            let client_key_pem = fs::read_to_string(&key_path).context(IoError {})?;
200
201            let pcks12_encoded_client_key =
202                pem_to_pkcs12(&client_certificate_pem, &client_key_pem)?;
203
204            let ca_certificate = Certificate::from_pem(ca_certificate_pem.as_bytes())
205                .context(CertificateParseError {})?;
206            let client_identity = Identity::from_pkcs12(&pcks12_encoded_client_key, "")
207                .context(CertificateParseError {})?;
208
209            let mut http_connector = HttpConnector::new();
210            http_connector.enforce_http(false);
211            let tls_connector = TlsConnector::from(
212                native_tls::TlsConnector::builder()
213                    .identity(client_identity)
214                    .add_root_certificate(ca_certificate)
215                    .build()
216                    .unwrap(),
217            );
218            let https_connector = HttpsConnector::from((http_connector, tls_connector));
219            client.client = Some(Client::builder().build(https_connector));
220            Ok(())
221        },
222    )
223}
224
225/// with_version overrides the client version with the specified one. If an empty
226/// version is specified, the value will be ignored to allow version negotiation.
227pub fn with_version<C: Connect + Clone + Send + Sync + 'static>(
228    version: String,
229) -> DockerEngineClientOption<C> {
230    Box::new(
231        move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
232            if !version.is_empty() {
233                client.version = version.clone();
234                client.manual_override = true;
235            }
236            Ok(())
237        },
238    )
239}
240
241/// with_api_version_negotiation enables automatic API version negotiation for the client.
242/// With this option enabled, the client automatically negotiates the API version
243/// to use when making requests. API version negotiation is performed on the first
244/// request; subsequent requests will not re-negotiate.
245pub fn with_api_version_negotiation<C: Connect + Clone + Send + Sync + 'static>(
246) -> DockerEngineClientOption<C> {
247    Box::new(
248        move |client: &mut DockerEngineClient<C>| -> Result<(), Error> {
249            client.negotiate_version = true;
250            Ok(())
251        },
252    )
253}
254
255/// rust-native-tls currently does not support PEM based mutual authentication out of the box.
256/// to support this, this function converts a PEM key pair into a pkcs#12 archive.
257fn pem_to_pkcs12(client_certificate_pem: &str, client_key_pem: &str) -> Result<Vec<u8>, Error> {
258    let client_certificate =
259        X509::from_pem(client_certificate_pem.as_bytes()).context(CryptoError {})?;
260    let client_private_key =
261        PKey::private_key_from_pem(client_key_pem.as_bytes()).context(CryptoError {})?;
262    Pkcs12::builder()
263        .build("", "", &client_private_key, &client_certificate)
264        .context(CryptoError {})?
265        .to_der()
266        .context(CryptoError {})
267}