ginmi/client/
dangerous.rs

1//! Connect to a gNMI-capable Endpoint without verifying the TLS-Certificate
2//!
3//! Provides the means to connect to gNMI-capable Devices without verifying the
4//! used TLS-Certificates. This is accomplished by manually providing a [`ClientConfig`]
5//! to a hyper client and using it as a replacement for the default [Channel]. The
6//! [`ClientConfig`] is configured with a custom [`ServerCertVerifier`]
7//! that will always return a successful validation.
8//!
9//! [Channel]: tonic::transport::Channel
10//!
11//! # Safety
12//! You should never use the functionality provided by this module, except when you need to test
13//! something locally and do not care for the certificates. Using this in the wild is very dangerous because
14//! you are susceptible to Man-in-the-Middle attacks.
15//!
16//! # Examples
17//! Connecting to a SR-Linux device, ignoring any validation issues that happen with its certificate:
18//! ```rust
19//! # use ginmi::client::Client;
20//! # fn main() -> std::io::Result<()> {
21//! # tokio_test::block_on(async {
22//! # const CA_CERT: &str = "CA Certificate";
23//! let mut client = Client::builder("https://clab-srl01-srl:57400")
24//!     .credentials("admin", "password1")
25//!     .dangerous()
26//!     .disable_certificate_validation()
27//!     .build()
28//!     .await?;
29//! # })}
30use super::ClientBuilder;
31use crate::auth::AuthInterceptor;
32use crate::client::Client;
33use crate::error::GinmiError;
34use crate::gen::gnmi::g_nmi_client::GNmiClient;
35use http::Uri;
36use hyper::client::HttpConnector;
37use hyper_rustls::HttpsConnector;
38use std::convert::From;
39use std::str::FromStr;
40use std::sync::Arc;
41use std::time::SystemTime;
42use tokio_rustls::rustls::client::{ServerCertVerified, ServerCertVerifier};
43use tokio_rustls::rustls::{Certificate, ClientConfig, Error, RootCertStore, ServerName};
44use tonic::body::BoxBody;
45use tonic::codegen::InterceptedService;
46use tonic::metadata::AsciiMetadataValue;
47
48pub type DangerousConnection =
49    InterceptedService<hyper::Client<HttpsConnector<HttpConnector>, BoxBody>, AuthInterceptor>;
50
51/// Builder for [`Client`]s with extra options that are dangerous and require extra care.
52pub struct DangerousClientBuilder<'a> {
53    builder: ClientBuilder<'a>,
54    client_config: Option<ClientConfig>,
55}
56
57impl<'a> DangerousClientBuilder<'a> {
58    /// Disable the verification of TLS-certificates
59    ///
60    /// # Safety
61    /// Using this option completely disables certificate validation which on turn
62    /// makes you susceptible to Man-in-the-Middle attacks. This option can be useful for local
63    /// testing purposes, but should be avoided at all cost for any other use case.
64    pub fn disable_certificate_verification(mut self) -> Self {
65        let roots = RootCertStore::empty();
66
67        let mut tls = ClientConfig::builder()
68            .with_safe_defaults()
69            .with_root_certificates(roots)
70            .with_no_client_auth();
71
72        tls.dangerous()
73            .set_certificate_verifier(Arc::new(NoCertificateVerification {}));
74
75        self.client_config = Some(tls);
76        self
77    }
78
79    /// Consume the [`DangerousClientBuilder`] and return a [`Client`].
80    ///
81    /// # Errors
82    /// - Returns [`GinmiError::InvalidUriError`] if specified target is not a valid URI.
83    /// - Returns [`GinmiError::TransportError`] if the TLS-Settings are invalid.
84    /// - Returns [`GinmiError::TransportError`] if a connection to the target could not be
85    /// established.
86    pub async fn build(self) -> Result<Client<DangerousConnection>, GinmiError> {
87        // create a hyper HttpConnector
88        let mut http = HttpConnector::new();
89        http.enforce_http(false);
90
91        // specify tls configuration for the http connector to enable https
92        let connector = tower::ServiceBuilder::new()
93            .layer_fn(move |s| {
94                let tls = self.client_config.clone().unwrap();
95
96                hyper_rustls::HttpsConnectorBuilder::new()
97                    .with_tls_config(tls)
98                    .https_or_http()
99                    .enable_http2()
100                    .wrap_connector(s)
101            })
102            .service(http);
103
104        // create a hyper client from the connector
105        let http_client = hyper::Client::builder().build(connector);
106
107        let uri = match Uri::from_str(self.builder.target) {
108            Ok(u) => u,
109            Err(e) => return Err(GinmiError::InvalidUriError(e.to_string())),
110        };
111
112        let (username, password) = match self.builder.creds {
113            Some(c) => (
114                Some(AsciiMetadataValue::from_str(c.username)?),
115                Some(AsciiMetadataValue::from_str(c.password)?),
116            ),
117            None => (None, None),
118        };
119
120        // add the authentication interceptor to the service.
121        let svc = tower::ServiceBuilder::new()
122            .layer(tonic::service::interceptor(AuthInterceptor::new(
123                username, password,
124            )))
125            .service(http_client);
126
127        // create a client, overriding the default uri with the uri in the builder
128        let client = GNmiClient::with_origin(svc, uri);
129
130        Ok(Client { inner: client })
131    }
132}
133
134impl<'a> From<ClientBuilder<'a>> for DangerousClientBuilder<'a> {
135    fn from(builder: ClientBuilder<'a>) -> Self {
136        DangerousClientBuilder {
137            builder,
138            client_config: None,
139        }
140    }
141}
142
143#[derive(Debug)]
144/// ServerCertVerifier that always returns a successful certificate validation regardless of the reality.
145///
146/// This Verifier performs no actual Verification at all.
147struct NoCertificateVerification;
148
149impl ServerCertVerifier for NoCertificateVerification {
150    fn verify_server_cert(
151        &self,
152        _end_entity: &Certificate,
153        _intermediates: &[Certificate],
154        _server_name: &ServerName,
155        _scts: &mut dyn Iterator<Item = &[u8]>,
156        _ocsp_response: &[u8],
157        _now: SystemTime,
158    ) -> Result<ServerCertVerified, Error> {
159        Ok(ServerCertVerified::assertion())
160    }
161}