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