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        // List of client configurations for the dataplane services.
133        // Used as a credential fallback for CP-managed outbound links when no
134        // matching outbound_clients entry exists, so the client does not need to
135        // duplicate credentials it already has for a given endpoint.
136        dataplane_clients: &[ClientConfig],
137        auth_provider: Option<slim_auth::auth_provider::AuthProvider>,
138    ) -> ControlPlane {
139        let connection_details = dataplane_servers.iter().map(from_server_config).collect();
140
141        ControlPlane::new(ControlPlaneSettings {
142            id: node_id,
143            domain_name,
144            servers: self.servers.clone(),
145            clients: self.clients.clone(),
146            outbound_clients: self.outbound_clients.clone(),
147            dataplane_clients: dataplane_clients.to_vec(),
148            message_processor,
149            connection_details,
150            auth_provider,
151        })
152    }
153}
154
155impl Configuration for Config {
156    type Error = ControllerError;
157
158    fn validate(&self) -> Result<(), Self::Error> {
159        self.check_pqc_policy()?;
160
161        // Validate client and server configurations
162        for server in self.servers.iter() {
163            server.validate()?;
164        }
165
166        for client in &self.clients {
167            client.validate()?;
168        }
169
170        Ok(())
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use slim_config::server::ServerConfig;
178    use slim_datapath::message_processing::MessageProcessor;
179
180    fn create_test_server_config() -> ServerConfig {
181        ServerConfig::with_endpoint("127.0.0.1:50051")
182            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
183    }
184
185    fn create_test_client_config() -> ClientConfig {
186        ClientConfig::with_endpoint("http://127.0.0.1:50051")
187            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
188    }
189
190    fn server_with_pqc(enforced: bool) -> ServerConfig {
191        let mut server = create_test_server_config();
192        server.tls_setting.config.enforce_pqc = enforced;
193        server
194    }
195
196    #[test]
197    fn test_normalize_pqc_propagates_to_tls_runtime_config() {
198        let mut config = Config {
199            enforce_pqc: EnforcePqcPolicy::enforced(),
200            servers: vec![server_with_pqc(false)],
201            ..Config::default()
202        };
203
204        config.normalize_pqc().unwrap();
205
206        assert!(config.enforce_pqc().is_enforced());
207        assert!(config.servers[0].tls_setting.config.enforce_pqc);
208    }
209
210    #[test]
211    fn test_config_new() {
212        let config = Config::new();
213        assert!(config.servers.is_empty());
214        assert!(config.clients.is_empty());
215    }
216
217    #[test]
218    fn test_config_default() {
219        let config = Config::default();
220        assert!(config.servers.is_empty());
221        assert!(config.clients.is_empty());
222    }
223
224    #[test]
225    fn test_config_with_servers() {
226        let server_config = create_test_server_config();
227        let config = Config::new().with_servers(vec![server_config.clone()]);
228
229        assert_eq!(config.servers.len(), 1);
230        assert_eq!(config.servers[0], server_config);
231        assert!(config.clients.is_empty());
232    }
233
234    #[test]
235    fn test_config_with_clients() {
236        let client_config = create_test_client_config();
237        let config = Config::new().with_clients(vec![client_config.clone()]);
238
239        assert_eq!(config.clients.len(), 1);
240        assert_eq!(config.clients[0], client_config);
241        assert!(config.servers.is_empty());
242    }
243
244    #[test]
245    fn test_config_servers_getter() {
246        let server_config = create_test_server_config();
247        let config = Config::new().with_servers(vec![server_config.clone()]);
248
249        let servers = config.servers();
250        assert_eq!(servers.len(), 1);
251        assert_eq!(servers[0], server_config);
252    }
253
254    #[test]
255    fn test_config_clients_getter() {
256        let client_config = create_test_client_config();
257        let config = Config::new().with_clients(vec![client_config.clone()]);
258
259        let clients = config.clients();
260        assert_eq!(clients.len(), 1);
261        assert_eq!(clients[0], client_config);
262    }
263
264    #[test]
265    fn test_config_chaining() {
266        let server_config = create_test_server_config();
267        let client_config = create_test_client_config();
268
269        let config = Config::new()
270            .with_servers(vec![server_config.clone()])
271            .with_clients(vec![client_config.clone()]);
272
273        assert_eq!(config.servers.len(), 1);
274        assert_eq!(config.clients.len(), 1);
275    }
276
277    #[test]
278    fn test_config_validate_empty() {
279        let config = Config::new();
280        assert!(config.validate().is_ok());
281    }
282
283    #[test]
284    fn test_config_validate_with_valid_servers_and_clients() {
285        let server_config = create_test_server_config();
286        let client_config = create_test_client_config();
287        let config = Config::new()
288            .with_servers(vec![server_config])
289            .with_clients(vec![client_config]);
290
291        assert!(config.validate().is_ok());
292    }
293
294    #[test]
295    fn test_config_clone() {
296        let server_config = create_test_server_config();
297        let client_config = create_test_client_config();
298
299        let config1 = Config::new()
300            .with_servers(vec![server_config])
301            .with_clients(vec![client_config]);
302
303        let config2 = config1.clone();
304
305        assert_eq!(config1.servers, config2.servers);
306        assert_eq!(config1.clients, config2.clients);
307    }
308
309    #[tokio::test]
310    async fn test_config_into_service() {
311        let server_config = create_test_server_config();
312        let client_config = create_test_client_config();
313
314        let config = Config::new()
315            .with_servers(vec![server_config.clone()])
316            .with_clients(vec![client_config]);
317
318        let domain_name = Some("test-domain".to_string());
319        let message_processor = MessageProcessor::new();
320
321        let _control_plane = config.into_service(
322            "test-instance".to_string(),
323            domain_name,
324            message_processor,
325            &[server_config],
326            &[],
327            None,
328        );
329    }
330
331    #[test]
332    fn test_config_debug_trait() {
333        let config = Config::new();
334        let debug_str = format!("{:?}", config);
335        assert!(debug_str.contains("Config"));
336        assert!(debug_str.contains("servers"));
337        assert!(debug_str.contains("clients"));
338    }
339
340    #[test]
341    fn test_config_validate_with_multiple_servers() {
342        let server1 = create_test_server_config();
343        let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
344            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
345
346        let config = Config::new().with_servers(vec![server1, server2]);
347        assert!(config.validate().is_ok());
348    }
349
350    #[test]
351    fn test_config_validate_with_multiple_clients() {
352        let client1 = create_test_client_config();
353        let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
354            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
355
356        let config = Config::new().with_clients(vec![client1, client2]);
357        assert!(config.validate().is_ok());
358    }
359
360    #[test]
361    fn test_config_partial_eq() {
362        let config1 = Config::new();
363        let config2 = Config::new();
364
365        assert_eq!(config1, config2);
366
367        let server_config = create_test_server_config();
368        let config3 = config1.clone().with_servers(vec![server_config]);
369
370        assert_ne!(config1, config3);
371    }
372
373    #[test]
374    fn test_config_builder_pattern_reuse() {
375        let base_config = Config::new();
376
377        let config1 = base_config
378            .clone()
379            .with_servers(vec![create_test_server_config()]);
380        let config2 = base_config
381            .clone()
382            .with_clients(vec![create_test_client_config()]);
383
384        assert!(base_config.servers.is_empty());
385        assert!(base_config.clients.is_empty());
386
387        assert_eq!(config1.servers.len(), 1);
388        assert!(config1.clients.is_empty());
389
390        assert!(config2.servers.is_empty());
391        assert_eq!(config2.clients.len(), 1);
392    }
393
394    #[test]
395    fn test_config_overwrite_behavior() {
396        let server1 = create_test_server_config();
397        let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
398            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
399
400        let config = Config::new()
401            .with_servers(vec![server1])
402            .with_servers(vec![server2.clone()]);
403
404        assert_eq!(config.servers.len(), 1);
405        assert_eq!(config.servers[0], server2);
406    }
407}