saluki_io/net/
ipc.rs

1use std::{io::Cursor, path::Path, sync::Arc};
2
3use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
4use hyper_util::client::legacy::connect::HttpConnector;
5use rustls::{
6    client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
7    crypto::CryptoProvider,
8    pki_types::{CertificateDer, ServerName, UnixTime},
9    version::TLS13,
10    CertificateError, ClientConfig, DigitallySignedStruct, SignatureScheme,
11};
12use saluki_error::{generic_error, ErrorContext as _, GenericError};
13
14/// Builds an HTTPS connector for connecting to the Datadog Agent's IPC endpoint.
15///
16/// This connector is configured to load a bundled TLS certificate/private key file, generated by the Datadog Agent, and
17/// use it for both server verification and client authentication. The given certificate path is expected to be a
18/// PEM-encoded file containing both the certificate and private key. The private key is only used for client
19/// authentication.
20pub async fn build_datadog_agent_ipc_https_connector<P: AsRef<Path>>(
21    cert_path: P,
22) -> Result<HttpsConnector<HttpConnector>, GenericError> {
23    let tls_client_config = build_datadog_agent_ipc_tls_config(cert_path).await?;
24    let mut http_connector = HttpConnector::new();
25    http_connector.enforce_http(false);
26
27    Ok(HttpsConnectorBuilder::new()
28        .with_tls_config(tls_client_config)
29        .https_only()
30        .enable_http2()
31        .wrap_connector(http_connector))
32}
33
34#[derive(Debug)]
35struct DatadogAgentServerCertVerifier {
36    cert: CertificateDer<'static>,
37    provider: Arc<CryptoProvider>,
38}
39
40impl DatadogAgentServerCertVerifier {
41    fn from_certificate_and_provider(cert: CertificateDer<'static>, provider: Arc<CryptoProvider>) -> Self {
42        Self { cert, provider }
43    }
44}
45
46impl ServerCertVerifier for DatadogAgentServerCertVerifier {
47    fn verify_server_cert(
48        &self, end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>,
49        _ocsp_response: &[u8], _now: UnixTime,
50    ) -> Result<ServerCertVerified, rustls::Error> {
51        // We only care about if the server certificate matches the one we have.
52        //
53        // This explicitly ignores things like the server using a CA certificate as an end-entity certificate and all of
54        // that. We just want to verify that the server certificate is the one we expect.
55        if end_entity != &self.cert {
56            return Err(rustls::Error::InvalidCertificate(CertificateError::UnknownIssuer));
57        }
58
59        Ok(ServerCertVerified::assertion())
60    }
61
62    fn verify_tls12_signature(
63        &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
64    ) -> Result<HandshakeSignatureValid, rustls::Error> {
65        rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
66    }
67
68    fn verify_tls13_signature(
69        &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
70    ) -> Result<HandshakeSignatureValid, rustls::Error> {
71        rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
72    }
73
74    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
75        self.provider.signature_verification_algorithms.supported_schemes()
76    }
77}
78
79pub async fn build_datadog_agent_ipc_tls_config<P: AsRef<Path>>(cert_path: P) -> Result<ClientConfig, GenericError> {
80    // Read the certificate file, and extract the certificate and private key from it.
81    let raw_cert_data = tokio::fs::read(cert_path.as_ref()).await.map_err(|e| {
82        generic_error!(
83            "Failed to read certificate file '{}' ({}).",
84            cert_path.as_ref().display(),
85            e.kind()
86        )
87    })?;
88
89    let mut cert_reader = Cursor::new(&raw_cert_data);
90    let parsed_cert = rustls_pemfile::certs(&mut cert_reader)
91        .next()
92        .ok_or_else(|| generic_error!("No certificate found in file."))?
93        .with_error_context(|| format!("Failed to parse certificate file '{}'.", cert_path.as_ref().display()))?;
94
95    let mut key_reader = Cursor::new(&raw_cert_data);
96    let parsed_key = rustls_pemfile::private_key(&mut key_reader)
97        .with_error_context(|| format!("Failed to parse private key file '{}'.", cert_path.as_ref().display()))?
98        .ok_or_else(|| generic_error!("No private key found in file."))?;
99
100    // Build our client TLS configuration to use the parsed certificate for server verification, and then to send it for
101    // client authentication.
102    let crypto_provider = rustls::crypto::CryptoProvider::get_default()
103        .map(Arc::clone)
104        .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
105    let agent_cert_verifier = Arc::new(DatadogAgentServerCertVerifier::from_certificate_and_provider(
106        parsed_cert.clone(),
107        crypto_provider,
108    ));
109
110    let tls_client_config = ClientConfig::builder_with_protocol_versions(&[&TLS13])
111        .dangerous()
112        .with_custom_certificate_verifier(agent_cert_verifier)
113        .with_client_auth_cert(vec![parsed_cert], parsed_key)
114        .with_error_context(|| {
115            format!(
116                "Failed to configure TLS client authentication with certificate/private key from '{}'.",
117                cert_path.as_ref().display()
118            )
119        })?;
120    Ok(tls_client_config)
121}