1use std::{
2 path::{Path, PathBuf},
3 sync::Arc,
4 time::Duration,
5};
6
7use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
8use hyper_util::client::legacy::connect::HttpConnector;
9use rustls::{
10 client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
11 crypto::CryptoProvider,
12 pki_types::{CertificateDer, ServerName, UnixTime},
13 version::TLS13,
14 CertificateError, ClientConfig, DigitallySignedStruct, ServerConfig, SignatureScheme,
15};
16use rustls_pki_types::{pem::PemObject as _, PrivateKeyDer};
17use saluki_error::{generic_error, ErrorContext as _, GenericError};
18
19const DEFAULT_DATADOG_AGENT_CONFIG_DIR: &str = "/etc/datadog-agent";
20const DEFAULT_IPC_CERT_FILE_NAME: &str = "ipc_cert.pem";
21const DEFAULT_CERT_READ_TIMEOUT: Duration = Duration::from_secs(20);
22const DEFAULT_CERT_READ_INTERVAL: Duration = Duration::from_millis(100);
23
24pub fn get_ipc_cert_file_path(ipc_cert_file_path: Option<&PathBuf>, auth_token_file_path: &Path) -> PathBuf {
29 if let Some(path) = ipc_cert_file_path {
31 if !path.as_os_str().is_empty() {
32 return path.clone();
33 }
34 }
35
36 let mut cert_path = auth_token_file_path
38 .parent()
39 .map(|p| p.to_path_buf())
40 .unwrap_or_else(|| PathBuf::from(DEFAULT_DATADOG_AGENT_CONFIG_DIR));
41
42 cert_path.push(DEFAULT_IPC_CERT_FILE_NAME);
43 cert_path
44}
45
46pub async fn build_datadog_agent_ipc_https_connector<P: AsRef<Path>>(
53 cert_path: P,
54) -> Result<HttpsConnector<HttpConnector>, GenericError> {
55 let tls_client_config = build_datadog_agent_client_ipc_tls_config(cert_path).await?;
56 let mut http_connector = HttpConnector::new();
57 http_connector.enforce_http(false);
58
59 Ok(HttpsConnectorBuilder::new()
60 .with_tls_config(tls_client_config)
61 .https_only()
62 .enable_http2()
63 .wrap_connector(http_connector))
64}
65
66#[derive(Debug)]
67struct DatadogAgentServerCertVerifier {
68 cert: CertificateDer<'static>,
69 provider: Arc<CryptoProvider>,
70}
71
72impl DatadogAgentServerCertVerifier {
73 fn from_certificate_and_provider(cert: CertificateDer<'static>, provider: Arc<CryptoProvider>) -> Self {
74 Self { cert, provider }
75 }
76}
77
78impl ServerCertVerifier for DatadogAgentServerCertVerifier {
79 fn verify_server_cert(
80 &self, end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], _server_name: &ServerName<'_>,
81 _ocsp_response: &[u8], _now: UnixTime,
82 ) -> Result<ServerCertVerified, rustls::Error> {
83 if end_entity != &self.cert {
88 return Err(rustls::Error::InvalidCertificate(CertificateError::UnknownIssuer));
89 }
90
91 Ok(ServerCertVerified::assertion())
92 }
93
94 fn verify_tls12_signature(
95 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
96 ) -> Result<HandshakeSignatureValid, rustls::Error> {
97 rustls::crypto::verify_tls12_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
98 }
99
100 fn verify_tls13_signature(
101 &self, message: &[u8], cert: &CertificateDer<'_>, dss: &DigitallySignedStruct,
102 ) -> Result<HandshakeSignatureValid, rustls::Error> {
103 rustls::crypto::verify_tls13_signature(message, cert, dss, &self.provider.signature_verification_algorithms)
104 }
105
106 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
107 self.provider.signature_verification_algorithms.supported_schemes()
108 }
109}
110
111pub async fn build_datadog_agent_client_ipc_tls_config<P: AsRef<Path>>(
112 cert_path: P,
113) -> Result<ClientConfig, GenericError> {
114 let raw_cert_data = read_cert_file(
116 cert_path.as_ref(),
117 DEFAULT_CERT_READ_TIMEOUT,
118 DEFAULT_CERT_READ_INTERVAL,
119 )
120 .await?;
121
122 let parsed_cert = CertificateDer::from_pem_slice(&raw_cert_data[..])
123 .with_error_context(|| format!("Failed to parse certificate file '{}'.", cert_path.as_ref().display()))?;
124
125 let parsed_key = PrivateKeyDer::from_pem_slice(&raw_cert_data[..])
126 .with_error_context(|| format!("Failed to parse private key file '{}'.", cert_path.as_ref().display()))?;
127
128 let crypto_provider = rustls::crypto::CryptoProvider::get_default()
131 .map(Arc::clone)
132 .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
133 let agent_cert_verifier = Arc::new(DatadogAgentServerCertVerifier::from_certificate_and_provider(
134 parsed_cert.clone(),
135 crypto_provider,
136 ));
137
138 let tls_client_config = ClientConfig::builder_with_protocol_versions(&[&TLS13])
139 .dangerous()
140 .with_custom_certificate_verifier(agent_cert_verifier)
141 .with_client_auth_cert(vec![parsed_cert], parsed_key)
142 .with_error_context(|| {
143 format!(
144 "Failed to configure TLS client authentication with certificate/private key from '{}'.",
145 cert_path.as_ref().display()
146 )
147 })?;
148 Ok(tls_client_config)
149}
150
151pub async fn build_datadog_agent_server_tls_config<P: AsRef<Path>>(cert_path: P) -> Result<ServerConfig, GenericError> {
157 let raw_cert_data = read_cert_file(
159 cert_path.as_ref(),
160 DEFAULT_CERT_READ_TIMEOUT,
161 DEFAULT_CERT_READ_INTERVAL,
162 )
163 .await?;
164
165 let parsed_cert = CertificateDer::from_pem_slice(&raw_cert_data[..])
166 .with_error_context(|| format!("Failed to parse certificate file '{}'.", cert_path.as_ref().display()))?;
167
168 let parsed_key = PrivateKeyDer::from_pem_slice(&raw_cert_data[..])
169 .with_error_context(|| format!("Failed to parse private key file '{}'.", cert_path.as_ref().display()))?;
170
171 let tls_server_config = ServerConfig::builder()
173 .with_no_client_auth()
174 .with_single_cert(vec![parsed_cert], parsed_key)
175 .map_err(|e| generic_error!("Failed to configure TLS server: {}", e))?;
176
177 Ok(tls_server_config)
178}
179
180async fn read_cert_file(cert_path: &Path, timeout: Duration, interval: Duration) -> Result<Vec<u8>, GenericError> {
182 if timeout < interval {
183 return Err(generic_error!(
184 "Timeout is less than interval. Timeout: {}, Interval: {}",
185 timeout.as_secs(),
186 interval.as_secs()
187 ));
188 }
189
190 let start_time = std::time::Instant::now();
191 let mut last_error: String = String::new();
192 while start_time.elapsed() < timeout {
193 match tokio::fs::read(cert_path).await {
194 Ok(data) => return Ok(data),
195 Err(e) => {
196 last_error = e.to_string();
197 tokio::time::sleep(interval).await;
198 }
199 }
200 }
201 Err(generic_error!(
202 "Failed to read certificate file '{}' after {} seconds: {}",
203 cert_path.display(),
204 timeout.as_secs(),
205 last_error
206 ))
207}
208
209#[cfg(test)]
210mod tests {
211 use std::path::{Path, PathBuf};
212
213 use super::get_ipc_cert_file_path;
214
215 fn default_agent_auth_token_file_path() -> PathBuf {
216 PathBuf::from("/etc/datadog-agent/auth_token")
217 }
218
219 #[test]
220 fn ipc_cert_file_path_defaults() {
221 let default_auth_token_path = default_agent_auth_token_file_path();
222 let custom_auth_token_path = PathBuf::from("/secret/auth_token");
223 let invalid_auth_token_path = PathBuf::from("/");
224 let custom_ipc_cert_path = PathBuf::from("/tmp/custom_ipc_cert.pem");
225
226 let result = get_ipc_cert_file_path(Some(&custom_ipc_cert_path), &default_auth_token_path);
228 assert_eq!(result, custom_ipc_cert_path);
229
230 let result = get_ipc_cert_file_path(None, &default_auth_token_path);
233 assert_eq!(result.parent(), default_auth_token_path.as_path().parent());
234 assert_eq!(result.file_name().and_then(|s| s.to_str()), Some("ipc_cert.pem"));
235
236 let result = get_ipc_cert_file_path(None, &custom_auth_token_path);
238 assert_eq!(result.parent(), custom_auth_token_path.as_path().parent());
239 assert_eq!(result.file_name().and_then(|s| s.to_str()), Some("ipc_cert.pem"));
240
241 let result = get_ipc_cert_file_path(None, &invalid_auth_token_path);
244 assert_eq!(result.parent(), Some(Path::new("/etc/datadog-agent")));
245 assert_eq!(result.file_name().and_then(|s| s.to_str()), Some("ipc_cert.pem"));
246 }
247}