Skip to main content

slim_controller/
config.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use serde::Deserialize;
5
6use slim_config::client::ClientConfig;
7use slim_config::component::configuration::Configuration;
8use slim_config::pqc::EnforcePqcPolicy;
9use slim_config::server::ServerConfig;
10use slim_datapath::message_processing::MessageProcessor;
11
12use crate::errors::ControllerError;
13use crate::service::{ControlPlane, ControlPlaneSettings, from_server_config};
14
15/// Configuration for the Control-Plane / Data-Plane component
16#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
17#[serde(deny_unknown_fields)]
18pub struct Config {
19    /// Applies to TLS, link negotiation, and MLS for this dataplane.
20    #[serde(default)]
21    pub enforce_pqc: EnforcePqcPolicy,
22
23    /// Controller GRPC server settings
24    #[serde(default)]
25    pub servers: Vec<ServerConfig>,
26
27    /// Controller client config to connect to control plane
28    #[serde(default)]
29    pub clients: Vec<ClientConfig>,
30
31    /// Controller client config to connect to data-plane server nodes
32    #[serde(default)]
33    pub outbound_clients: Vec<ClientConfig>,
34}
35
36impl Config {
37    /// Create a new Config instance with default values
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    pub fn is_default(&self) -> bool {
43        self == &Self::default()
44    }
45
46    /// Resolved PQC policy for this dataplane (also written to TLS runtime config on normalize).
47    pub fn enforce_pqc(&self) -> EnforcePqcPolicy {
48        self.enforce_pqc
49    }
50
51    /// Write the dataplane policy to every TLS endpoint runtime config.
52    pub fn normalize_pqc(&mut self) -> Result<(), ControllerError> {
53        self.apply_pqc_policy(self.enforce_pqc)?;
54        Ok(())
55    }
56
57    fn check_pqc_policy(&self) -> Result<(), ControllerError> {
58        self.validate_pqc_for_endpoints(self.enforce_pqc)
59    }
60
61    fn apply_pqc_policy(&mut self, policy: EnforcePqcPolicy) -> Result<(), ControllerError> {
62        for server in &mut self.servers {
63            server.tls_setting.config.enforce_pqc = policy.is_enforced();
64            policy
65                .validate_tls_version(&server.tls_setting.config.tls_version)
66                .map_err(|e| ControllerError::ConfigError(e.into()))?;
67        }
68        for client in &mut self.clients {
69            client.tls_setting.config.enforce_pqc = policy.is_enforced();
70            policy
71                .validate_tls_version(&client.tls_setting.config.tls_version)
72                .map_err(|e| ControllerError::ConfigError(e.into()))?;
73        }
74        for client in &mut self.outbound_clients {
75            client.tls_setting.config.enforce_pqc = policy.is_enforced();
76            policy
77                .validate_tls_version(&client.tls_setting.config.tls_version)
78                .map_err(|e| ControllerError::ConfigError(e.into()))?;
79        }
80        Ok(())
81    }
82
83    fn validate_pqc_for_endpoints(&self, policy: EnforcePqcPolicy) -> Result<(), ControllerError> {
84        for server in &self.servers {
85            policy
86                .validate_tls_version(&server.tls_setting.config.tls_version)
87                .map_err(|e| ControllerError::ConfigError(e.into()))?;
88        }
89        for client in &self.clients {
90            policy
91                .validate_tls_version(&client.tls_setting.config.tls_version)
92                .map_err(|e| ControllerError::ConfigError(e.into()))?;
93        }
94        for client in &self.outbound_clients {
95            policy
96                .validate_tls_version(&client.tls_setting.config.tls_version)
97                .map_err(|e| ControllerError::ConfigError(e.into()))?;
98        }
99        Ok(())
100    }
101
102    /// Create a new Config instance with the given servers
103    pub fn with_servers(self, servers: Vec<ServerConfig>) -> Self {
104        Self { servers, ..self }
105    }
106
107    /// Create a new Config instance with the given clients
108    pub fn with_clients(self, clients: Vec<ClientConfig>) -> Self {
109        Self { clients, ..self }
110    }
111
112    /// Get the list of server configurations
113    pub fn servers(&self) -> &[ServerConfig] {
114        &self.servers
115    }
116
117    /// Get the list of client configurations
118    pub fn clients(&self) -> &[ClientConfig] {
119        &self.clients
120    }
121
122    /// Create a ControlPlane service instance from this configuration
123    pub fn into_service(
124        &self,
125        node_id: String,
126        domain_name: Option<String>,
127        message_processor: MessageProcessor,
128        // List of server configurations for the dataplane services.
129        // Used to extract connection type information required to connect to the node
130        // (e.g., TLS settings). This information is used by the control plane.
131        dataplane_servers: &[ServerConfig],
132        auth_provider: Option<slim_auth::auth_provider::AuthProvider>,
133    ) -> ControlPlane {
134        let connection_details = dataplane_servers.iter().map(from_server_config).collect();
135
136        ControlPlane::new(ControlPlaneSettings {
137            id: node_id,
138            domain_name,
139            servers: self.servers.clone(),
140            clients: self.clients.clone(),
141            outbound_clients: self.outbound_clients.clone(),
142            message_processor,
143            connection_details,
144            auth_provider,
145        })
146    }
147}
148
149impl Configuration for Config {
150    type Error = ControllerError;
151
152    fn validate(&self) -> Result<(), Self::Error> {
153        self.check_pqc_policy()?;
154
155        // Validate client and server configurations
156        for server in self.servers.iter() {
157            server.validate()?;
158        }
159
160        for client in &self.clients {
161            client.validate()?;
162        }
163
164        Ok(())
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use slim_config::server::ServerConfig;
172    use slim_datapath::message_processing::MessageProcessor;
173
174    fn create_test_server_config() -> ServerConfig {
175        ServerConfig::with_endpoint("127.0.0.1:50051")
176            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
177    }
178
179    fn create_test_client_config() -> ClientConfig {
180        ClientConfig::with_endpoint("http://127.0.0.1:50051")
181            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
182    }
183
184    fn server_with_pqc(enforced: bool) -> ServerConfig {
185        let mut server = create_test_server_config();
186        server.tls_setting.config.enforce_pqc = enforced;
187        server
188    }
189
190    #[test]
191    fn test_normalize_pqc_propagates_to_tls_runtime_config() {
192        let mut config = Config {
193            enforce_pqc: EnforcePqcPolicy::enforced(),
194            servers: vec![server_with_pqc(false)],
195            ..Config::default()
196        };
197
198        config.normalize_pqc().unwrap();
199
200        assert!(config.enforce_pqc().is_enforced());
201        assert!(config.servers[0].tls_setting.config.enforce_pqc);
202    }
203
204    #[test]
205    fn test_config_new() {
206        let config = Config::new();
207        assert!(config.servers.is_empty());
208        assert!(config.clients.is_empty());
209    }
210
211    #[test]
212    fn test_config_default() {
213        let config = Config::default();
214        assert!(config.servers.is_empty());
215        assert!(config.clients.is_empty());
216    }
217
218    #[test]
219    fn test_config_with_servers() {
220        let server_config = create_test_server_config();
221        let config = Config::new().with_servers(vec![server_config.clone()]);
222
223        assert_eq!(config.servers.len(), 1);
224        assert_eq!(config.servers[0], server_config);
225        assert!(config.clients.is_empty());
226    }
227
228    #[test]
229    fn test_config_with_clients() {
230        let client_config = create_test_client_config();
231        let config = Config::new().with_clients(vec![client_config.clone()]);
232
233        assert_eq!(config.clients.len(), 1);
234        assert_eq!(config.clients[0], client_config);
235        assert!(config.servers.is_empty());
236    }
237
238    #[test]
239    fn test_config_servers_getter() {
240        let server_config = create_test_server_config();
241        let config = Config::new().with_servers(vec![server_config.clone()]);
242
243        let servers = config.servers();
244        assert_eq!(servers.len(), 1);
245        assert_eq!(servers[0], server_config);
246    }
247
248    #[test]
249    fn test_config_clients_getter() {
250        let client_config = create_test_client_config();
251        let config = Config::new().with_clients(vec![client_config.clone()]);
252
253        let clients = config.clients();
254        assert_eq!(clients.len(), 1);
255        assert_eq!(clients[0], client_config);
256    }
257
258    #[test]
259    fn test_config_chaining() {
260        let server_config = create_test_server_config();
261        let client_config = create_test_client_config();
262
263        let config = Config::new()
264            .with_servers(vec![server_config.clone()])
265            .with_clients(vec![client_config.clone()]);
266
267        assert_eq!(config.servers.len(), 1);
268        assert_eq!(config.clients.len(), 1);
269    }
270
271    #[test]
272    fn test_config_validate_empty() {
273        let config = Config::new();
274        assert!(config.validate().is_ok());
275    }
276
277    #[test]
278    fn test_config_validate_with_valid_servers_and_clients() {
279        let server_config = create_test_server_config();
280        let client_config = create_test_client_config();
281        let config = Config::new()
282            .with_servers(vec![server_config])
283            .with_clients(vec![client_config]);
284
285        assert!(config.validate().is_ok());
286    }
287
288    #[test]
289    fn test_config_clone() {
290        let server_config = create_test_server_config();
291        let client_config = create_test_client_config();
292
293        let config1 = Config::new()
294            .with_servers(vec![server_config])
295            .with_clients(vec![client_config]);
296
297        let config2 = config1.clone();
298
299        assert_eq!(config1.servers, config2.servers);
300        assert_eq!(config1.clients, config2.clients);
301    }
302
303    #[tokio::test]
304    async fn test_config_into_service() {
305        let server_config = create_test_server_config();
306        let client_config = create_test_client_config();
307
308        let config = Config::new()
309            .with_servers(vec![server_config.clone()])
310            .with_clients(vec![client_config]);
311
312        let domain_name = Some("test-domain".to_string());
313        let message_processor = MessageProcessor::new();
314
315        let _control_plane = config.into_service(
316            "test-instance".to_string(),
317            domain_name,
318            message_processor,
319            &[server_config],
320            None,
321        );
322    }
323
324    #[test]
325    fn test_config_debug_trait() {
326        let config = Config::new();
327        let debug_str = format!("{:?}", config);
328        assert!(debug_str.contains("Config"));
329        assert!(debug_str.contains("servers"));
330        assert!(debug_str.contains("clients"));
331    }
332
333    #[test]
334    fn test_config_validate_with_multiple_servers() {
335        let server1 = create_test_server_config();
336        let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
337            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
338
339        let config = Config::new().with_servers(vec![server1, server2]);
340        assert!(config.validate().is_ok());
341    }
342
343    #[test]
344    fn test_config_validate_with_multiple_clients() {
345        let client1 = create_test_client_config();
346        let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
347            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
348
349        let config = Config::new().with_clients(vec![client1, client2]);
350        assert!(config.validate().is_ok());
351    }
352
353    #[test]
354    fn test_config_partial_eq() {
355        let config1 = Config::new();
356        let config2 = Config::new();
357
358        assert_eq!(config1, config2);
359
360        let server_config = create_test_server_config();
361        let config3 = config1.clone().with_servers(vec![server_config]);
362
363        assert_ne!(config1, config3);
364    }
365
366    #[test]
367    fn test_config_builder_pattern_reuse() {
368        let base_config = Config::new();
369
370        let config1 = base_config
371            .clone()
372            .with_servers(vec![create_test_server_config()]);
373        let config2 = base_config
374            .clone()
375            .with_clients(vec![create_test_client_config()]);
376
377        assert!(base_config.servers.is_empty());
378        assert!(base_config.clients.is_empty());
379
380        assert_eq!(config1.servers.len(), 1);
381        assert!(config1.clients.is_empty());
382
383        assert!(config2.servers.is_empty());
384        assert_eq!(config2.clients.len(), 1);
385    }
386
387    #[test]
388    fn test_config_overwrite_behavior() {
389        let server1 = create_test_server_config();
390        let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
391            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
392
393        let config = Config::new()
394            .with_servers(vec![server1])
395            .with_servers(vec![server2.clone()]);
396
397        assert_eq!(config.servers.len(), 1);
398        assert_eq!(config.servers[0], server2);
399    }
400}