datadog_agent_commons/ipc/
config.rs1use std::path::{Path, PathBuf};
4
5#[cfg(target_os = "linux")]
6use saluki_error::generic_error;
7use saluki_error::GenericError;
8use tonic::transport::Uri;
9#[cfg(not(target_os = "linux"))]
10use tracing::warn;
11
12use crate::platform::PlatformSettings;
13
14#[derive(Clone, Debug)]
16pub struct IpcAuthConfiguration {
17 auth_token_file_path: PathBuf,
24
25 ipc_cert_file_path: PathBuf,
34}
35
36impl IpcAuthConfiguration {
37 pub fn new(mut auth_token_file_path: PathBuf, mut ipc_cert_file_path: PathBuf) -> Self {
42 if auth_token_file_path.as_os_str().is_empty() {
43 auth_token_file_path = PlatformSettings::get_auth_token_path();
44 }
45
46 if ipc_cert_file_path.as_os_str().is_empty() {
47 let auth_token_dir = auth_token_file_path
48 .parent()
49 .unwrap_or(PlatformSettings::get_config_dir_path());
50 ipc_cert_file_path = auth_token_dir.join(PlatformSettings::get_ipc_cert_filename());
51 }
52
53 Self {
54 auth_token_file_path,
55 ipc_cert_file_path,
56 }
57 }
58
59 pub fn auth_token_file_path(&self) -> &Path {
61 &self.auth_token_file_path
62 }
63
64 pub fn ipc_cert_file_path(&self) -> &Path {
66 &self.ipc_cert_file_path
67 }
68}
69
70#[derive(Clone, Debug)]
72pub struct RemoteAgentClientConfiguration {
73 pub cmd_port: u16,
75
76 pub auth: IpcAuthConfiguration,
78
79 pub grpc_max_message_size: usize,
81
82 #[cfg(target_os = "linux")]
84 pub vsock_cid: Option<u32>,
85}
86
87impl RemoteAgentClientConfiguration {
88 pub fn from_parts(
98 cmd_port: u16, auth: IpcAuthConfiguration, grpc_max_message_size: usize, vsock_addr: &str,
99 ) -> Result<Self, GenericError> {
100 #[cfg(target_os = "linux")]
101 let vsock_cid = match vsock_addr {
102 "" => None,
103 "host" => Some(2),
104 "hypervisor" => Some(0),
105 "local" => Some(3),
106 other => {
107 return Err(generic_error!(
108 "invalid vsock address '{}'; expected one of: host, hypervisor, local",
109 other
110 ))
111 }
112 };
113
114 #[cfg(not(target_os = "linux"))]
115 if !vsock_addr.is_empty() {
116 warn!("`vsock_addr` is configured but vsock is only supported on Linux. Setting will be ignored.");
117 }
118
119 Ok(Self {
120 cmd_port,
121 auth,
122 grpc_max_message_size,
123 #[cfg(target_os = "linux")]
124 vsock_cid,
125 })
126 }
127
128 pub fn endpoint(&self) -> Uri {
130 format!("https://127.0.0.1:{}", self.cmd_port)
131 .parse()
132 .expect("a URI built from a u16 port is valid")
133 }
134
135 #[cfg(target_os = "linux")]
137 pub fn vsock_addr(&self) -> Option<tokio_vsock::VsockAddr> {
138 self.vsock_cid
139 .map(|cid| tokio_vsock::VsockAddr::new(cid, u32::from(self.cmd_port)))
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use std::path::{Path, PathBuf};
146
147 use super::{IpcAuthConfiguration, RemoteAgentClientConfiguration};
148 use crate::platform::PlatformSettings;
149
150 #[test]
151 fn ipc_cert_file_path_empty_config() {
152 let default_auth_token_path = PlatformSettings::get_auth_token_path();
153
154 let config = IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new());
157 assert_eq!(config.auth_token_file_path(), default_auth_token_path);
158 assert_eq!(
159 config.ipc_cert_file_path().parent(),
160 default_auth_token_path.as_path().parent()
161 );
162 assert_eq!(
163 config.ipc_cert_file_path().file_name().map(Path::new),
164 Some(PlatformSettings::get_ipc_cert_filename())
165 );
166 }
167
168 #[test]
169 fn ipc_cert_file_path_defaults() {
170 let default_auth_token_path = PlatformSettings::get_auth_token_path();
171
172 let config = IpcAuthConfiguration::new(default_auth_token_path.clone(), PathBuf::new());
175 assert_eq!(
176 config.ipc_cert_file_path().parent(),
177 default_auth_token_path.as_path().parent()
178 );
179 assert_eq!(
180 config.ipc_cert_file_path().file_name().map(Path::new),
181 Some(PlatformSettings::get_ipc_cert_filename())
182 );
183 }
184
185 #[test]
186 fn ipc_cert_file_path_explicitly_set() {
187 let default_auth_token_path = PlatformSettings::get_auth_token_path();
188 let custom_ipc_cert_path = PathBuf::from("/tmp/custom_ipc_cert.pem");
189
190 let config = IpcAuthConfiguration::new(default_auth_token_path, custom_ipc_cert_path.clone());
192 assert_eq!(custom_ipc_cert_path, config.ipc_cert_file_path());
193 }
194
195 #[test]
196 fn ipc_cert_file_path_custom_auth_token_path() {
197 let custom_auth_token_path = PathBuf::from("/secret/auth_token");
198
199 let config = IpcAuthConfiguration::new(custom_auth_token_path.clone(), PathBuf::new());
202 assert_eq!(
203 config.ipc_cert_file_path().parent(),
204 custom_auth_token_path.as_path().parent()
205 );
206 assert_eq!(
207 config.ipc_cert_file_path().file_name().map(Path::new),
208 Some(PlatformSettings::get_ipc_cert_filename())
209 );
210 }
211
212 #[test]
213 fn ipc_cert_file_path_invalid_auth_token_path() {
214 let invalid_auth_token_path = PathBuf::from("/");
215
216 let config = IpcAuthConfiguration::new(invalid_auth_token_path, PathBuf::new());
219 assert_eq!(
220 config.ipc_cert_file_path().parent(),
221 Some(PlatformSettings::get_config_dir_path())
222 );
223 assert_eq!(
224 config.ipc_cert_file_path().file_name().map(Path::new),
225 Some(PlatformSettings::get_ipc_cert_filename())
226 );
227 }
228
229 fn remote_agent_config(cmd_port: u16) -> RemoteAgentClientConfiguration {
230 RemoteAgentClientConfiguration {
231 cmd_port,
232 auth: IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new()),
233 grpc_max_message_size: 128 * 1024 * 1024,
234 #[cfg(target_os = "linux")]
235 vsock_cid: None,
236 }
237 }
238
239 #[test]
240 fn endpoint_uses_cmd_port() {
241 for (cmd_port, expected) in [(5001, "https://127.0.0.1:5001/"), (7777, "https://127.0.0.1:7777/")] {
242 assert_eq!(remote_agent_config(cmd_port).endpoint().to_string(), expected);
243 }
244 }
245
246 #[test]
247 fn from_parts_keeps_the_given_parts() {
248 let auth = IpcAuthConfiguration::new("/secret/auth_token".into(), "/secret/ipc_cert.pem".into());
249 let config = RemoteAgentClientConfiguration::from_parts(5001, auth, 4 * 1024 * 1024, "")
250 .expect("an empty vsock address is valid");
251
252 assert_eq!(config.cmd_port, 5001);
253 assert_eq!(config.grpc_max_message_size, 4 * 1024 * 1024);
254 assert_eq!(config.auth.auth_token_file_path(), Path::new("/secret/auth_token"));
255 assert_eq!(config.auth.ipc_cert_file_path(), Path::new("/secret/ipc_cert.pem"));
256 }
257
258 #[cfg(target_os = "linux")]
259 #[test]
260 fn from_parts_resolves_vsock_addresses() {
261 for (value, expected_cid) in [
262 ("", None),
263 ("host", Some(2)),
264 ("hypervisor", Some(0)),
265 ("local", Some(3)),
266 ] {
267 let auth = IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new());
268 let config = RemoteAgentClientConfiguration::from_parts(5001, auth, 4 * 1024 * 1024, value)
269 .expect("valid vsock address");
270 assert_eq!(config.vsock_cid, expected_cid, "input: {value:?}");
271 }
272 }
273
274 #[cfg(target_os = "linux")]
275 #[test]
276 fn from_parts_rejects_invalid_vsock_addresses() {
277 for value in ["invalid", "2", "HOST", "host ", "vm0"] {
278 let auth = IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new());
279 assert!(
280 RemoteAgentClientConfiguration::from_parts(5001, auth, 4 * 1024 * 1024, value).is_err(),
281 "expected error for input: {value:?}",
282 );
283 }
284 }
285
286 #[cfg(not(target_os = "linux"))]
287 #[test]
288 fn from_parts_ignores_vsock_addresses_off_linux() {
289 let auth = IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new());
290 assert!(RemoteAgentClientConfiguration::from_parts(5001, auth, 4 * 1024 * 1024, "host").is_ok());
291 }
292
293 #[cfg(target_os = "linux")]
294 #[test]
295 fn vsock_addr_uses_resolved_cid_and_cmd_port() {
296 let mut config = remote_agent_config(5001);
297 assert_eq!(config.vsock_addr(), None);
298
299 config.vsock_cid = Some(2);
300 let addr = config.vsock_addr().expect("vsock address should be configured");
301 assert_eq!(addr.cid(), 2);
302 assert_eq!(addr.port(), 5001);
303 }
304}