1use std::{
2 io::Cursor,
3 path::{Path, PathBuf},
4 sync::Arc,
5 time::Duration,
6};
7
8use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
9use hyper_util::client::legacy::connect::HttpConnector;
10use rustls::{
11 client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
12 crypto::CryptoProvider,
13 pki_types::{CertificateDer, ServerName, UnixTime},
14 version::TLS13,
15 CertificateError, ClientConfig, DigitallySignedStruct, ServerConfig, SignatureScheme,
16};
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 mut cert_reader = Cursor::new(&raw_cert_data);
123 let parsed_cert = rustls_pemfile::certs(&mut cert_reader)
124 .next()
125 .ok_or_else(|| generic_error!("No certificate found in file."))?
126 .with_error_context(|| format!("Failed to parse certificate file '{}'.", cert_path.as_ref().display()))?;
127
128 let mut key_reader = Cursor::new(&raw_cert_data);
129 let parsed_key = rustls_pemfile::private_key(&mut key_reader)
130 .with_error_context(|| format!("Failed to parse private key file '{}'.", cert_path.as_ref().display()))?
131 .ok_or_else(|| generic_error!("No private key found in file."))?;
132
133 let crypto_provider = rustls::crypto::CryptoProvider::get_default()
136 .map(Arc::clone)
137 .ok_or_else(|| generic_error!("Default cryptography provider not yet installed."))?;
138 let agent_cert_verifier = Arc::new(DatadogAgentServerCertVerifier::from_certificate_and_provider(
139 parsed_cert.clone(),
140 crypto_provider,
141 ));
142
143 let tls_client_config = ClientConfig::builder_with_protocol_versions(&[&TLS13])
144 .dangerous()
145 .with_custom_certificate_verifier(agent_cert_verifier)
146 .with_client_auth_cert(vec![parsed_cert], parsed_key)
147 .with_error_context(|| {
148 format!(
149 "Failed to configure TLS client authentication with certificate/private key from '{}'.",
150 cert_path.as_ref().display()
151 )
152 })?;
153 Ok(tls_client_config)
154}
155
156pub async fn build_datadog_agent_server_tls_config<P: AsRef<Path>>(cert_path: P) -> Result<ServerConfig, GenericError> {
162 let raw_cert_data = read_cert_file(
164 cert_path.as_ref(),
165 DEFAULT_CERT_READ_TIMEOUT,
166 DEFAULT_CERT_READ_INTERVAL,
167 )
168 .await?;
169
170 let mut cert_reader = Cursor::new(&raw_cert_data);
171 let parsed_cert = rustls_pemfile::certs(&mut cert_reader)
172 .next()
173 .ok_or_else(|| generic_error!("No certificate found in file."))?
174 .with_error_context(|| format!("Failed to parse certificate file '{}'.", cert_path.as_ref().display()))?;
175
176 let mut key_reader = Cursor::new(&raw_cert_data);
177 let parsed_key = rustls_pemfile::private_key(&mut key_reader)
178 .with_error_context(|| format!("Failed to parse private key file '{}'.", cert_path.as_ref().display()))?
179 .ok_or_else(|| generic_error!("No private key found in file."))?;
180
181 let tls_server_config = ServerConfig::builder()
183 .with_no_client_auth()
184 .with_single_cert(vec![parsed_cert], parsed_key)
185 .map_err(|e| generic_error!("Failed to configure TLS server: {}", e))?;
186
187 Ok(tls_server_config)
188}
189
190async fn read_cert_file(cert_path: &Path, timeout: Duration, interval: Duration) -> Result<Vec<u8>, GenericError> {
192 if timeout < interval {
193 return Err(generic_error!(
194 "Timeout is less than interval. Timeout: {}, Interval: {}",
195 timeout.as_secs(),
196 interval.as_secs()
197 ));
198 }
199
200 let start_time = std::time::Instant::now();
201 let mut last_error: String = String::new();
202 while start_time.elapsed() < timeout {
203 match tokio::fs::read(cert_path).await {
204 Ok(data) => return Ok(data),
205 Err(e) => {
206 last_error = e.to_string();
207 tokio::time::sleep(interval).await;
208 }
209 }
210 }
211 Err(generic_error!(
212 "Failed to read certificate file '{}' after {} seconds: {}",
213 cert_path.display(),
214 timeout.as_secs(),
215 last_error
216 ))
217}
218
219#[cfg(test)]
220mod tests {
221 use std::path::{Path, PathBuf};
222
223 use super::get_ipc_cert_file_path;
224
225 fn default_agent_auth_token_file_path() -> PathBuf {
226 PathBuf::from("/etc/datadog-agent/auth_token")
227 }
228
229 #[test]
230 fn ipc_cert_file_path_defaults() {
231 let default_auth_token_path = default_agent_auth_token_file_path();
232 let custom_auth_token_path = PathBuf::from("/secret/auth_token");
233 let invalid_auth_token_path = PathBuf::from("/");
234 let custom_ipc_cert_path = PathBuf::from("/tmp/custom_ipc_cert.pem");
235
236 let result = get_ipc_cert_file_path(Some(&custom_ipc_cert_path), &default_auth_token_path);
238 assert_eq!(result, custom_ipc_cert_path);
239
240 let result = get_ipc_cert_file_path(None, &default_auth_token_path);
243 assert_eq!(result.parent(), default_auth_token_path.as_path().parent());
244 assert_eq!(result.file_name().and_then(|s| s.to_str()), Some("ipc_cert.pem"));
245
246 let result = get_ipc_cert_file_path(None, &custom_auth_token_path);
248 assert_eq!(result.parent(), custom_auth_token_path.as_path().parent());
249 assert_eq!(result.file_name().and_then(|s| s.to_str()), Some("ipc_cert.pem"));
250
251 let result = get_ipc_cert_file_path(None, &invalid_auth_token_path);
254 assert_eq!(result.parent(), Some(Path::new("/etc/datadog-agent")));
255 assert_eq!(result.file_name().and_then(|s| s.to_str()), Some("ipc_cert.pem"));
256 }
257}