datadog_agent_commons/ipc/
config.rs

1//! IPC configuration.
2
3use std::path::{Path, PathBuf};
4
5use tonic::transport::Uri;
6
7use crate::platform::PlatformSettings;
8
9/// Datadog Agent IPC bearer-token and exact shared-certificate mTLS configuration.
10#[derive(Clone, Debug)]
11pub struct IpcAuthConfiguration {
12    /// Path to the Agent authentication token file.
13    ///
14    /// The contents of the file are passed as a bearer token in RPC requests to the IPC endpoint.
15    ///
16    /// Defaults to `<conf dir>/auth_token`, where `<conf dir>` is the platform-specific directory containing the Agent
17    /// configuration.
18    auth_token_file_path: PathBuf,
19
20    /// Path to the shared Agent IPC mTLS identity file.
21    ///
22    /// The PEM file contains one certificate and its private key. IPC peers require exact leaf-certificate DER equality
23    /// rather than CA-based trust, and each peer proves possession of the corresponding private key during the TLS
24    /// handshake. The same identity authenticates both the client and server, so a CA chain cannot broaden it.
25    ///
26    /// Defaults to `ipc_cert.pem` in the same directory as the Agent authentication token file. (for example, if
27    /// `auth_token_file_path` is `/etc/datadog-agent/auth_token`, this will be `/etc/datadog-agent/ipc_cert.pem`.)
28    ipc_cert_file_path: PathBuf,
29}
30
31impl IpcAuthConfiguration {
32    /// Creates an `IpcAuthConfiguration`, resolving empty paths to Agent defaults.
33    ///
34    /// An empty token path selects the platform-specific token path. An empty certificate path selects `ipc_cert.pem`
35    /// beside the resolved token, or in the platform configuration directory when the token has no parent.
36    pub fn new(mut auth_token_file_path: PathBuf, mut ipc_cert_file_path: PathBuf) -> Self {
37        if auth_token_file_path.as_os_str().is_empty() {
38            auth_token_file_path = PlatformSettings::get_auth_token_path();
39        }
40
41        if ipc_cert_file_path.as_os_str().is_empty() {
42            let auth_token_dir = auth_token_file_path
43                .parent()
44                .unwrap_or(PlatformSettings::get_config_dir_path());
45            ipc_cert_file_path = auth_token_dir.join(PlatformSettings::get_ipc_cert_filename());
46        }
47
48        Self {
49            auth_token_file_path,
50            ipc_cert_file_path,
51        }
52    }
53
54    /// Gets the path to the Agent authentication token file from the configuration.
55    pub fn auth_token_file_path(&self) -> &Path {
56        &self.auth_token_file_path
57    }
58
59    /// Gets the shared IPC mTLS identity file path from the configuration.
60    pub fn ipc_cert_file_path(&self) -> &Path {
61        &self.ipc_cert_file_path
62    }
63}
64
65/// Datadog Agent IPC client configuration.
66#[derive(Clone, Debug)]
67pub struct RemoteAgentClientConfiguration {
68    /// Core Agent CMD API port used for remote-agent gRPC IPC on localhost.
69    pub cmd_port: u16,
70
71    /// Authentication configuration for the IPC endpoint.
72    pub auth: IpcAuthConfiguration,
73
74    /// Maximum message size for gRPC messages.
75    pub grpc_max_message_size: usize,
76
77    /// Resolved CID for connecting to the Agent IPC endpoint via AF_VSOCK.
78    #[cfg(target_os = "linux")]
79    pub vsock_cid: Option<u32>,
80}
81
82impl RemoteAgentClientConfiguration {
83    /// Returns the Core Agent CMD API gRPC endpoint URI.
84    pub fn endpoint(&self) -> Uri {
85        format!("https://127.0.0.1:{}", self.cmd_port)
86            .parse()
87            .expect("a URI built from a u16 port is valid")
88    }
89
90    /// Returns the vsock address to use for connecting to the IPC endpoint, if configured.
91    #[cfg(target_os = "linux")]
92    pub fn vsock_addr(&self) -> Option<tokio_vsock::VsockAddr> {
93        self.vsock_cid
94            .map(|cid| tokio_vsock::VsockAddr::new(cid, u32::from(self.cmd_port)))
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use std::path::{Path, PathBuf};
101
102    use super::{IpcAuthConfiguration, RemoteAgentClientConfiguration};
103    use crate::platform::PlatformSettings;
104
105    #[test]
106    fn ipc_cert_file_path_empty_config() {
107        let default_auth_token_path = PlatformSettings::get_auth_token_path();
108
109        // When the auth token file path _and_ IPC cert file path are both unset, we should default to looking for the
110        // IPC cert in the same directory as the auth token.
111        let config = IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new());
112        assert_eq!(config.auth_token_file_path(), default_auth_token_path);
113        assert_eq!(
114            config.ipc_cert_file_path().parent(),
115            default_auth_token_path.as_path().parent()
116        );
117        assert_eq!(
118            config.ipc_cert_file_path().file_name().map(Path::new),
119            Some(PlatformSettings::get_ipc_cert_filename())
120        );
121    }
122
123    #[test]
124    fn ipc_cert_file_path_defaults() {
125        let default_auth_token_path = PlatformSettings::get_auth_token_path();
126
127        // When the IPC cert file path is not set, it should default to the same directory as the auth token file using
128        // the default certificate file name.
129        let config = IpcAuthConfiguration::new(default_auth_token_path.clone(), PathBuf::new());
130        assert_eq!(
131            config.ipc_cert_file_path().parent(),
132            default_auth_token_path.as_path().parent()
133        );
134        assert_eq!(
135            config.ipc_cert_file_path().file_name().map(Path::new),
136            Some(PlatformSettings::get_ipc_cert_filename())
137        );
138    }
139
140    #[test]
141    fn ipc_cert_file_path_explicitly_set() {
142        let default_auth_token_path = PlatformSettings::get_auth_token_path();
143        let custom_ipc_cert_path = PathBuf::from("/tmp/custom_ipc_cert.pem");
144
145        // When the IPC cert file path is explicitly set, it should be used.
146        let config = IpcAuthConfiguration::new(default_auth_token_path, custom_ipc_cert_path.clone());
147        assert_eq!(custom_ipc_cert_path, config.ipc_cert_file_path());
148    }
149
150    #[test]
151    fn ipc_cert_file_path_custom_auth_token_path() {
152        let custom_auth_token_path = PathBuf::from("/secret/auth_token");
153
154        // When the IPC cert file path is not set, but there's a custom auth token path (explicitly set, different from the default),
155        // we should still look in the same directory as the auth token file using the default certificate file name.
156        let config = IpcAuthConfiguration::new(custom_auth_token_path.clone(), PathBuf::new());
157        assert_eq!(
158            config.ipc_cert_file_path().parent(),
159            custom_auth_token_path.as_path().parent()
160        );
161        assert_eq!(
162            config.ipc_cert_file_path().file_name().map(Path::new),
163            Some(PlatformSettings::get_ipc_cert_filename())
164        );
165    }
166
167    #[test]
168    fn ipc_cert_file_path_invalid_auth_token_path() {
169        let invalid_auth_token_path = PathBuf::from("/");
170
171        // If the auth token file path is somehow unset or invalid (for example, no parent directory), we should use the same
172        // logic but with the default Datadog Agent configuration directory.
173        let config = IpcAuthConfiguration::new(invalid_auth_token_path, PathBuf::new());
174        assert_eq!(
175            config.ipc_cert_file_path().parent(),
176            Some(PlatformSettings::get_config_dir_path())
177        );
178        assert_eq!(
179            config.ipc_cert_file_path().file_name().map(Path::new),
180            Some(PlatformSettings::get_ipc_cert_filename())
181        );
182    }
183
184    fn remote_agent_config(cmd_port: u16) -> RemoteAgentClientConfiguration {
185        RemoteAgentClientConfiguration {
186            cmd_port,
187            auth: IpcAuthConfiguration::new(PathBuf::new(), PathBuf::new()),
188            grpc_max_message_size: 128 * 1024 * 1024,
189            #[cfg(target_os = "linux")]
190            vsock_cid: None,
191        }
192    }
193
194    #[test]
195    fn endpoint_uses_cmd_port() {
196        for (cmd_port, expected) in [(5001, "https://127.0.0.1:5001/"), (7777, "https://127.0.0.1:7777/")] {
197            assert_eq!(remote_agent_config(cmd_port).endpoint().to_string(), expected);
198        }
199    }
200
201    #[cfg(target_os = "linux")]
202    #[test]
203    fn vsock_addr_uses_resolved_cid_and_cmd_port() {
204        let mut config = remote_agent_config(5001);
205        assert_eq!(config.vsock_addr(), None);
206
207        config.vsock_cid = Some(2);
208        let addr = config.vsock_addr().expect("vsock address should be configured");
209        assert_eq!(addr.cid(), 2);
210        assert_eq!(addr.port(), 5001);
211    }
212}