slim_controller/
config.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::Arc;
5
6use serde::Deserialize;
7
8use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
9use slim_config::auth::jwt::Config as JwtConfig;
10use slim_config::auth::static_jwt::Config as StaticJwtConfig;
11use slim_config::component::configuration::{Configuration, ConfigurationError};
12use slim_config::component::id::ID;
13use slim_config::grpc::client::ClientConfig;
14use slim_config::grpc::server::ServerConfig;
15use slim_datapath::message_processing::MessageProcessor;
16
17use crate::service::{ControlPlane, ControlPlaneSettings};
18
19#[derive(Default, Debug, Clone, Deserialize, PartialEq, serde::Serialize)]
20#[serde(rename_all = "snake_case")]
21pub enum TokenProviderAuthConfig {
22    SharedSecret(String),
23    StaticJwt(StaticJwtConfig),
24    Jwt(JwtConfig),
25    #[default]
26    None,
27}
28
29#[derive(Default, Debug, Clone, Deserialize, PartialEq, serde::Serialize)]
30#[serde(rename_all = "snake_case")]
31pub enum TokenVerifierAuthConfig {
32    SharedSecret(String),
33    Jwt(JwtConfig),
34    #[default]
35    None,
36}
37
38/// Configuration for the Control-Plane / Data-Plane component
39#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
40pub struct Config {
41    /// Controller GRPC server settings
42    #[serde(default)]
43    pub servers: Vec<ServerConfig>,
44
45    /// Controller client config to connect to control plane
46    #[serde(default)]
47    pub clients: Vec<ClientConfig>,
48
49    /// Token provider authentication configuration
50    #[serde(with = "serde_yaml::with::singleton_map")]
51    #[serde(default)]
52    pub token_provider: TokenProviderAuthConfig,
53
54    /// Token verifier authentication configuration
55    #[serde(with = "serde_yaml::with::singleton_map")]
56    #[serde(default)]
57    pub token_verifier: TokenVerifierAuthConfig,
58}
59
60impl Config {
61    /// Create a new Config instance with default values
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Create a new Config instance with the given servers
67    pub fn with_servers(self, servers: Vec<ServerConfig>) -> Self {
68        Self { servers, ..self }
69    }
70
71    /// Create a new Config instance with the given clients
72    pub fn with_clients(self, clients: Vec<ClientConfig>) -> Self {
73        Self { clients, ..self }
74    }
75
76    /// Set the token provider authentication configuration
77    pub fn with_token_provider_auth(self, auth: TokenProviderAuthConfig) -> Self {
78        Self {
79            token_provider: auth,
80            ..self
81        }
82    }
83
84    /// Set the token verifier authentication configuration
85    pub fn with_token_verifier_auth(self, auth: TokenVerifierAuthConfig) -> Self {
86        Self {
87            token_verifier: auth,
88            ..self
89        }
90    }
91
92    /// Get the list of server configurations
93    pub fn servers(&self) -> &[ServerConfig] {
94        &self.servers
95    }
96
97    /// Get the list of client configurations
98    pub fn clients(&self) -> &[ClientConfig] {
99        &self.clients
100    }
101
102    fn get_token_provider_auth(&self) -> Option<AuthProvider> {
103        match &self.token_provider {
104            TokenProviderAuthConfig::SharedSecret(secret) => Some(
105                AuthProvider::shared_secret_from_str("control-plane", secret),
106            ),
107            TokenProviderAuthConfig::StaticJwt(static_jwt_config) => {
108                let provider = static_jwt_config
109                    .build_static_token_provider()
110                    .expect("Failed to build StaticTokenProvider");
111                Some(AuthProvider::static_token(provider))
112            }
113            TokenProviderAuthConfig::Jwt(jwt_config) => {
114                let provider = jwt_config
115                    .get_provider()
116                    .expect("Failed to build JwtTokenProvider");
117                Some(AuthProvider::jwt_signer(provider))
118            }
119            TokenProviderAuthConfig::None => None,
120        }
121    }
122
123    fn get_token_verifier_auth(&self) -> Option<AuthVerifier> {
124        match &self.token_verifier {
125            TokenVerifierAuthConfig::SharedSecret(secret) => Some(
126                AuthVerifier::shared_secret_from_str("control-plane", secret),
127            ),
128            TokenVerifierAuthConfig::Jwt(jwt_config) => {
129                let verifier = jwt_config
130                    .get_verifier()
131                    .expect("Failed to build JwtTokenVerifier");
132                Some(AuthVerifier::jwt_verifier(verifier))
133            }
134            TokenVerifierAuthConfig::None => None,
135        }
136    }
137
138    /// Create a ControlPlane service instance from this configuration
139    pub fn into_service(
140        &self,
141        id: ID,
142        group_name: Option<String>,
143        rx_drain: drain::Watch,
144        message_processor: Arc<MessageProcessor>,
145        servers: &[ServerConfig],
146    ) -> ControlPlane {
147        let auth_provider = self.get_token_provider_auth();
148        let auth_verifier = self.get_token_verifier_auth();
149
150        ControlPlane::new(ControlPlaneSettings {
151            id,
152            group_name,
153            servers: self.servers.clone(),
154            clients: self.clients.clone(),
155            drain_rx: rx_drain,
156            message_processor,
157            pubsub_servers: servers.to_vec(),
158            auth_provider,
159            auth_verifier,
160        })
161    }
162}
163
164impl Configuration for Config {
165    fn validate(&self) -> Result<(), ConfigurationError> {
166        // Validate client and server configurations
167        for server in self.servers.iter() {
168            server.validate()?;
169        }
170
171        for client in &self.clients {
172            client.validate()?;
173        }
174
175        Ok(())
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use slim_config::auth::jwt::Config as JwtConfig;
183    use slim_config::auth::static_jwt::Config as StaticJwtConfig;
184    use slim_config::component::id::{ID, Kind};
185    use slim_config::grpc::client::ClientConfig;
186    use slim_config::grpc::server::ServerConfig;
187    use slim_datapath::message_processing::MessageProcessor;
188    use std::sync::Arc;
189
190    fn create_test_server_config() -> ServerConfig {
191        ServerConfig::with_endpoint("127.0.0.1:50051")
192            .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
193    }
194
195    fn create_test_client_config() -> ClientConfig {
196        ClientConfig::with_endpoint("http://127.0.0.1:50051")
197            .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
198    }
199
200    #[test]
201    fn test_config_new() {
202        let config = Config::new();
203        assert!(config.servers.is_empty());
204        assert!(config.clients.is_empty());
205        assert_eq!(config.token_provider, TokenProviderAuthConfig::None);
206        assert_eq!(config.token_verifier, TokenVerifierAuthConfig::None);
207    }
208
209    #[test]
210    fn test_config_default() {
211        let config = Config::default();
212        assert!(config.servers.is_empty());
213        assert!(config.clients.is_empty());
214        assert_eq!(config.token_provider, TokenProviderAuthConfig::None);
215        assert_eq!(config.token_verifier, TokenVerifierAuthConfig::None);
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_with_token_provider_auth_shared_secret() {
240        let auth = TokenProviderAuthConfig::SharedSecret("test-secret".to_string());
241        let config = Config::new().with_token_provider_auth(auth.clone());
242
243        assert_eq!(config.token_provider, auth);
244        assert_eq!(config.token_verifier, TokenVerifierAuthConfig::None);
245    }
246
247    #[test]
248    fn test_config_with_token_provider_auth_static_jwt() {
249        let static_jwt_config = StaticJwtConfig::with_file("test-key".to_string());
250        let auth = TokenProviderAuthConfig::StaticJwt(static_jwt_config);
251        let config = Config::new().with_token_provider_auth(auth.clone());
252
253        assert_eq!(config.token_provider, auth);
254    }
255
256    #[test]
257    fn test_config_with_token_provider_auth_jwt() {
258        use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
259        use slim_config::auth::jwt::{Claims, JwtKey};
260        use std::time::Duration;
261
262        let claims = Claims::default();
263        let duration = Duration::from_secs(3600);
264        let key = JwtKey::Encoding(Key {
265            algorithm: Algorithm::HS256,
266            format: KeyFormat::Pem,
267            key: KeyData::Str("test-secret".to_string()),
268        });
269        let jwt_config = JwtConfig::new(claims, duration, key);
270        let auth = TokenProviderAuthConfig::Jwt(jwt_config);
271        let config = Config::new().with_token_provider_auth(auth.clone());
272
273        assert_eq!(config.token_provider, auth);
274    }
275
276    #[test]
277    fn test_config_with_token_verifier_auth_shared_secret() {
278        let auth = TokenVerifierAuthConfig::SharedSecret("test-secret".to_string());
279        let config = Config::new().with_token_verifier_auth(auth.clone());
280
281        assert_eq!(config.token_verifier, auth);
282        assert_eq!(config.token_provider, TokenProviderAuthConfig::None);
283    }
284
285    #[test]
286    fn test_config_with_token_verifier_auth_jwt() {
287        use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
288        use slim_config::auth::jwt::{Claims, JwtKey};
289        use std::time::Duration;
290
291        let claims = Claims::default();
292        let duration = Duration::from_secs(3600);
293        let key = JwtKey::Decoding(Key {
294            algorithm: Algorithm::HS256,
295            format: KeyFormat::Pem,
296            key: KeyData::Str("test-secret".to_string()),
297        });
298        let jwt_config = JwtConfig::new(claims, duration, key);
299        let auth = TokenVerifierAuthConfig::Jwt(jwt_config);
300        let config = Config::new().with_token_verifier_auth(auth.clone());
301
302        assert_eq!(config.token_verifier, auth);
303    }
304
305    #[test]
306    fn test_config_servers_getter() {
307        let server_config = create_test_server_config();
308        let config = Config::new().with_servers(vec![server_config.clone()]);
309
310        let servers = config.servers();
311        assert_eq!(servers.len(), 1);
312        assert_eq!(servers[0], server_config);
313    }
314
315    #[test]
316    fn test_config_clients_getter() {
317        let client_config = create_test_client_config();
318        let config = Config::new().with_clients(vec![client_config.clone()]);
319
320        let clients = config.clients();
321        assert_eq!(clients.len(), 1);
322        assert_eq!(clients[0], client_config);
323    }
324
325    #[test]
326    fn test_config_chaining() {
327        let server_config = create_test_server_config();
328        let client_config = create_test_client_config();
329        let provider_auth = TokenProviderAuthConfig::SharedSecret("provider-secret".to_string());
330        let verifier_auth = TokenVerifierAuthConfig::SharedSecret("verifier-secret".to_string());
331
332        let config = Config::new()
333            .with_servers(vec![server_config.clone()])
334            .with_clients(vec![client_config.clone()])
335            .with_token_provider_auth(provider_auth.clone())
336            .with_token_verifier_auth(verifier_auth.clone());
337
338        assert_eq!(config.servers.len(), 1);
339        assert_eq!(config.clients.len(), 1);
340        assert_eq!(config.token_provider, provider_auth);
341        assert_eq!(config.token_verifier, verifier_auth);
342    }
343
344    #[test]
345    fn test_config_validate_empty() {
346        let config = Config::new();
347        assert!(config.validate().is_ok());
348    }
349
350    #[test]
351    fn test_config_validate_with_valid_servers_and_clients() {
352        let server_config = create_test_server_config();
353        let client_config = create_test_client_config();
354        let config = Config::new()
355            .with_servers(vec![server_config])
356            .with_clients(vec![client_config]);
357
358        assert!(config.validate().is_ok());
359    }
360
361    #[test]
362    fn test_token_provider_auth_config_equality() {
363        let secret1 = TokenProviderAuthConfig::SharedSecret("secret1".to_string());
364        let secret2 = TokenProviderAuthConfig::SharedSecret("secret1".to_string());
365        let secret3 = TokenProviderAuthConfig::SharedSecret("secret2".to_string());
366
367        assert_eq!(secret1, secret2);
368        assert_ne!(secret1, secret3);
369    }
370
371    #[test]
372    fn test_token_verifier_auth_config_equality() {
373        let secret1 = TokenVerifierAuthConfig::SharedSecret("secret1".to_string());
374        let secret2 = TokenVerifierAuthConfig::SharedSecret("secret1".to_string());
375        let secret3 = TokenVerifierAuthConfig::SharedSecret("secret2".to_string());
376
377        assert_eq!(secret1, secret2);
378        assert_ne!(secret1, secret3);
379    }
380
381    #[test]
382    fn test_config_clone() {
383        let server_config = create_test_server_config();
384        let client_config = create_test_client_config();
385        let auth = TokenProviderAuthConfig::SharedSecret("test-secret".to_string());
386
387        let config1 = Config::new()
388            .with_servers(vec![server_config])
389            .with_clients(vec![client_config])
390            .with_token_provider_auth(auth);
391
392        let config2 = config1.clone();
393
394        assert_eq!(config1.servers, config2.servers);
395        assert_eq!(config1.clients, config2.clients);
396        assert_eq!(config1.token_provider, config2.token_provider);
397        assert_eq!(config1.token_verifier, config2.token_verifier);
398    }
399
400    #[tokio::test]
401    async fn test_config_into_service() {
402        let server_config = create_test_server_config();
403        let client_config = create_test_client_config();
404        let config = Config::new()
405            .with_servers(vec![server_config.clone()])
406            .with_clients(vec![client_config]);
407
408        let id = ID::new_with_name(Kind::new("slim").unwrap(), "test-instance").unwrap();
409        let group_name = Some("test-group".to_string());
410        let (_, rx_drain) = drain::channel();
411        let message_processor = Arc::new(MessageProcessor::with_drain_channel(rx_drain.clone()));
412        let servers = vec![server_config];
413
414        let _control_plane =
415            config.into_service(id, group_name, rx_drain, message_processor, &servers);
416    }
417
418    #[test]
419    fn test_config_debug_trait() {
420        let config = Config::new();
421        let debug_str = format!("{:?}", config);
422        assert!(debug_str.contains("Config"));
423        assert!(debug_str.contains("servers"));
424        assert!(debug_str.contains("clients"));
425    }
426
427    mod serde_tests {
428        use super::*;
429        use serde_json;
430
431        #[test]
432        fn test_token_provider_auth_config_serialize_shared_secret() {
433            let auth = TokenProviderAuthConfig::SharedSecret("test-secret".to_string());
434            let json = serde_json::to_string(&auth).unwrap();
435            assert!(json.contains("shared_secret"));
436            assert!(json.contains("test-secret"));
437        }
438
439        #[test]
440        fn test_token_provider_auth_config_deserialize_shared_secret() {
441            let json = r#"{"shared_secret":"test-secret"}"#;
442            let auth: TokenProviderAuthConfig = serde_json::from_str(json).unwrap();
443
444            match auth {
445                TokenProviderAuthConfig::SharedSecret(secret) => {
446                    assert_eq!(secret, "test-secret");
447                }
448                _ => panic!("Expected SharedSecret variant"),
449            }
450        }
451
452        #[test]
453        fn test_config_validate_with_multiple_servers() {
454            let server1 = create_test_server_config();
455            let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
456                .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
457
458            let config = Config::new().with_servers(vec![server1, server2]);
459            assert!(config.validate().is_ok());
460        }
461
462        #[test]
463        fn test_config_validate_with_multiple_clients() {
464            let client1 = create_test_client_config();
465            let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
466                .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
467
468            let config = Config::new().with_clients(vec![client1, client2]);
469            assert!(config.validate().is_ok());
470        }
471
472        #[test]
473        fn test_config_with_all_auth_combinations() {
474            let provider_auth =
475                TokenProviderAuthConfig::SharedSecret("provider-secret".to_string());
476            let verifier_auth =
477                TokenVerifierAuthConfig::SharedSecret("verifier-secret".to_string());
478
479            let config = Config::new()
480                .with_token_provider_auth(provider_auth.clone())
481                .with_token_verifier_auth(verifier_auth.clone());
482
483            assert_eq!(config.token_provider, provider_auth);
484            assert_eq!(config.token_verifier, verifier_auth);
485        }
486
487        #[test]
488        fn test_empty_servers_slice() {
489            let config = Config::new();
490            let servers = config.servers();
491            assert!(servers.is_empty());
492            assert_eq!(servers.len(), 0);
493        }
494
495        #[test]
496        fn test_empty_clients_slice() {
497            let config = Config::new();
498            let clients = config.clients();
499            assert!(clients.is_empty());
500            assert_eq!(clients.len(), 0);
501        }
502
503        #[test]
504        fn test_config_partial_eq() {
505            let config1 = Config::new();
506            let config2 = Config::new();
507
508            // Default configs should be equal
509            assert_eq!(config1, config2);
510
511            // Add server to one config
512            let server_config = create_test_server_config();
513            let config3 = config1.clone().with_servers(vec![server_config]);
514
515            // Should not be equal anymore
516            assert_ne!(config1, config3);
517        }
518
519        #[test]
520        fn test_mixed_auth_types() {
521            use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
522
523            let static_jwt =
524                TokenProviderAuthConfig::StaticJwt(StaticJwtConfig::with_file("test-token.jwt"));
525
526            let jwt = TokenVerifierAuthConfig::Jwt(JwtConfig::new(
527                slim_config::auth::jwt::Claims::default(),
528                std::time::Duration::from_secs(3600),
529                slim_config::auth::jwt::JwtKey::Decoding(Key {
530                    algorithm: Algorithm::HS256,
531                    format: KeyFormat::Pem,
532                    key: KeyData::Str("test-key".to_string()),
533                }),
534            ));
535
536            let config = Config::new()
537                .with_token_provider_auth(static_jwt.clone())
538                .with_token_verifier_auth(jwt.clone());
539
540            assert_eq!(config.token_provider, static_jwt);
541            assert_eq!(config.token_verifier, jwt);
542        }
543
544        mod edge_case_tests {
545            use super::*;
546
547            #[test]
548            fn test_config_builder_pattern_reuse() {
549                let base_config = Config::new();
550
551                let config1 = base_config
552                    .clone()
553                    .with_servers(vec![create_test_server_config()]);
554                let config2 = base_config
555                    .clone()
556                    .with_clients(vec![create_test_client_config()]);
557
558                // Base config should still be empty
559                assert!(base_config.servers.is_empty());
560                assert!(base_config.clients.is_empty());
561
562                // Derived configs should have their respective additions
563                assert_eq!(config1.servers.len(), 1);
564                assert!(config1.clients.is_empty());
565
566                assert!(config2.servers.is_empty());
567                assert_eq!(config2.clients.len(), 1);
568            }
569
570            #[test]
571            fn test_config_overwrite_behavior() {
572                let server1 = create_test_server_config();
573                let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
574                    .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
575
576                let config = Config::new()
577                    .with_servers(vec![server1])
578                    .with_servers(vec![server2.clone()]); // This should overwrite, not append
579
580                assert_eq!(config.servers.len(), 1);
581                assert_eq!(config.servers[0], server2);
582            }
583
584            #[test]
585            fn test_auth_config_none_variants() {
586                let config = Config::new();
587
588                assert_eq!(config.token_provider, TokenProviderAuthConfig::None);
589                assert_eq!(config.token_verifier, TokenVerifierAuthConfig::None);
590
591                // Adding one shouldn't affect the other
592                let config_with_provider =
593                    config
594                        .clone()
595                        .with_token_provider_auth(TokenProviderAuthConfig::SharedSecret(
596                            "secret".to_string(),
597                        ));
598
599                assert_ne!(
600                    config_with_provider.token_provider,
601                    TokenProviderAuthConfig::None
602                );
603                assert_eq!(
604                    config_with_provider.token_verifier,
605                    TokenVerifierAuthConfig::None
606                );
607            }
608        }
609
610        #[test]
611        fn test_token_verifier_auth_config_serialize_shared_secret() {
612            let auth = TokenVerifierAuthConfig::SharedSecret("test-secret".to_string());
613            let json = serde_json::to_string(&auth).unwrap();
614            assert!(json.contains("shared_secret"));
615            assert!(json.contains("test-secret"));
616        }
617
618        #[test]
619        fn test_token_verifier_auth_config_deserialize_shared_secret() {
620            let json = r#"{"shared_secret":"test-secret"}"#;
621            let auth: TokenVerifierAuthConfig = serde_json::from_str(json).unwrap();
622
623            match auth {
624                TokenVerifierAuthConfig::SharedSecret(secret) => {
625                    assert_eq!(secret, "test-secret");
626                }
627                _ => panic!("Expected SharedSecret variant"),
628            }
629        }
630    }
631}