1use std::sync::Arc;
5
6use serde::Deserialize;
7
8use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
9use slim_config::auth::identity::{IdentityProviderConfig, IdentityVerifierConfig};
10use slim_config::component::configuration::Configuration;
11use slim_config::component::id::ID;
12use slim_config::grpc::client::ClientConfig;
13use slim_config::grpc::server::ServerConfig;
14use slim_datapath::message_processing::MessageProcessor;
15
16use crate::errors::ControllerError;
17use crate::service::{ControlPlane, ControlPlaneSettings, from_server_config};
18
19#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
21#[serde(deny_unknown_fields)]
22pub struct Config {
23 #[serde(default)]
25 pub servers: Vec<ServerConfig>,
26
27 #[serde(default)]
29 pub clients: Vec<ClientConfig>,
30
31 #[serde(default)]
33 pub token_provider: IdentityProviderConfig,
34
35 #[serde(default)]
37 pub token_verifier: IdentityVerifierConfig,
38
39 #[serde(default)]
43 pub recovery_ttl: Option<duration_string::DurationString>,
44}
45
46impl Config {
47 pub fn new() -> Self {
49 Self::default()
50 }
51
52 pub fn is_default(&self) -> bool {
53 self == &Self::default()
54 }
55
56 pub fn with_servers(self, servers: Vec<ServerConfig>) -> Self {
58 Self { servers, ..self }
59 }
60
61 pub fn with_clients(self, clients: Vec<ClientConfig>) -> Self {
63 Self { clients, ..self }
64 }
65
66 pub fn with_token_provider_auth(self, auth: IdentityProviderConfig) -> Self {
68 Self {
69 token_provider: auth,
70 ..self
71 }
72 }
73
74 pub fn with_token_verifier_auth(self, auth: IdentityVerifierConfig) -> Self {
76 Self {
77 token_verifier: auth,
78 ..self
79 }
80 }
81
82 pub fn servers(&self) -> &[ServerConfig] {
84 &self.servers
85 }
86
87 pub fn clients(&self) -> &[ClientConfig] {
89 &self.clients
90 }
91
92 fn get_token_provider_auth(&self) -> Option<AuthProvider> {
93 match &self.token_provider {
94 IdentityProviderConfig::SharedSecret { id, data } => {
95 AuthProvider::shared_secret_from_str(id, data).ok()
96 }
97 IdentityProviderConfig::StaticJwt(static_jwt_config) => {
98 let provider = static_jwt_config
99 .build_static_token_provider()
100 .expect("Failed to build StaticTokenProvider");
101 Some(AuthProvider::static_token(provider))
102 }
103 IdentityProviderConfig::Jwt(jwt_config) => {
104 let provider = jwt_config
105 .get_provider()
106 .expect("Failed to build JwtTokenProvider");
107 Some(AuthProvider::jwt_signer(provider))
108 }
109 #[cfg(not(target_family = "windows"))]
110 IdentityProviderConfig::Spire(spire_config) => {
111 let manager = spire_config
112 .create_provider()
113 .expect("Failed to build SpireIdentityManager");
114 Some(AuthProvider::spire(manager))
115 }
116 IdentityProviderConfig::None => None,
117 }
118 }
119
120 fn get_token_verifier_auth(&self) -> Option<AuthVerifier> {
121 match &self.token_verifier {
122 IdentityVerifierConfig::SharedSecret { id, data } => {
123 AuthVerifier::shared_secret_from_str(id, data).ok()
124 }
125 IdentityVerifierConfig::Jwt(jwt_config) => {
126 let verifier = jwt_config
127 .get_verifier()
128 .expect("Failed to build JwtTokenVerifier");
129 Some(AuthVerifier::jwt_verifier(verifier))
130 }
131 #[cfg(not(target_family = "windows"))]
132 IdentityVerifierConfig::Spire(spire_config) => {
133 let manager = spire_config
134 .create_provider()
135 .expect("Failed to build SpireIdentityManager");
136 Some(AuthVerifier::spire(manager))
137 }
138 IdentityVerifierConfig::None => None,
139 }
140 }
141
142 pub fn into_service(
144 &self,
145 id: ID,
146 group_name: Option<String>,
147 message_processor: Arc<MessageProcessor>,
148 dataplane_servers: &[ServerConfig],
152 ) -> ControlPlane {
153 let auth_provider = self.get_token_provider_auth();
154 let auth_verifier = self.get_token_verifier_auth();
155
156 let connection_details = dataplane_servers.iter().map(from_server_config).collect();
157
158 ControlPlane::new(ControlPlaneSettings {
159 id,
160 group_name,
161 servers: self.servers.clone(),
162 clients: self.clients.clone(),
163 message_processor,
164 auth_provider,
165 auth_verifier,
166 connection_details,
167 })
168 }
169}
170
171impl Configuration for Config {
172 type Error = ControllerError;
173
174 fn validate(&self) -> Result<(), Self::Error> {
175 for server in self.servers.iter() {
177 server.validate()?;
178 }
179
180 for client in &self.clients {
181 client.validate()?;
182 }
183
184 Ok(())
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use slim_config::auth::jwt::Config as JwtConfig;
192 use slim_config::auth::static_jwt::Config as StaticJwtConfig;
193 use slim_config::component::id::{ID, Kind};
194 use slim_config::grpc::client::ClientConfig;
195 use slim_config::grpc::server::ServerConfig;
196 use slim_datapath::message_processing::MessageProcessor;
197 use slim_testing::utils::TEST_VALID_SECRET;
198 use std::sync::Arc;
199
200 fn create_test_server_config() -> ServerConfig {
201 ServerConfig::with_endpoint("127.0.0.1:50051")
202 .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
203 }
204
205 fn create_test_client_config() -> ClientConfig {
206 ClientConfig::with_endpoint("http://127.0.0.1:50051")
207 .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
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 assert_eq!(config.token_provider, IdentityProviderConfig::None);
216 assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
217 }
218
219 #[test]
220 fn test_config_default() {
221 let config = Config::default();
222 assert!(config.servers.is_empty());
223 assert!(config.clients.is_empty());
224 assert_eq!(config.token_provider, IdentityProviderConfig::None);
225 assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
226 }
227
228 #[test]
229 fn test_config_with_servers() {
230 let server_config = create_test_server_config();
231 let config = Config::new().with_servers(vec![server_config.clone()]);
232
233 assert_eq!(config.servers.len(), 1);
234 assert_eq!(config.servers[0], server_config);
235 assert!(config.clients.is_empty());
236 }
237
238 #[test]
239 fn test_config_with_clients() {
240 let client_config = create_test_client_config();
241 let config = Config::new().with_clients(vec![client_config.clone()]);
242
243 assert_eq!(config.clients.len(), 1);
244 assert_eq!(config.clients[0], client_config);
245 assert!(config.servers.is_empty());
246 }
247
248 #[test]
249 fn test_config_with_token_provider_auth_shared_secret() {
250 let auth = IdentityProviderConfig::SharedSecret {
251 id: "test-provider".to_string(),
252 data: "test-secret".to_string(),
253 };
254 let config = Config::new().with_token_provider_auth(auth.clone());
255
256 assert_eq!(config.token_provider, auth);
257 assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
258 }
259
260 #[test]
261 fn test_config_with_token_provider_auth_static_jwt() {
262 let static_jwt_config = StaticJwtConfig::with_file("test-key".to_string());
263 let auth = IdentityProviderConfig::StaticJwt(static_jwt_config);
264 let config = Config::new().with_token_provider_auth(auth.clone());
265
266 assert_eq!(config.token_provider, auth);
267 }
268
269 #[test]
270 fn test_config_with_token_provider_auth_jwt() {
271 use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
272 use slim_config::auth::jwt::{Claims, JwtKey};
273 use std::time::Duration;
274
275 let claims = Claims::default();
276 let duration = Duration::from_secs(3600);
277 let key = JwtKey::Encoding(Key {
278 algorithm: Algorithm::HS256,
279 format: KeyFormat::Pem,
280 key: KeyData::Data("test-secret".to_string()),
281 });
282 let jwt_config = JwtConfig::new(claims, duration, key);
283 let auth = IdentityProviderConfig::Jwt(jwt_config);
284 let config = Config::new().with_token_provider_auth(auth.clone());
285
286 assert_eq!(config.token_provider, auth);
287 }
288
289 #[test]
290 fn test_config_with_token_verifier_auth_shared_secret() {
291 let auth = IdentityVerifierConfig::SharedSecret {
292 id: "test-verifier".to_string(),
293 data: "test-secret".to_string(),
294 };
295 let config = Config::new().with_token_verifier_auth(auth.clone());
296
297 assert_eq!(config.token_verifier, auth);
298 assert_eq!(config.token_provider, IdentityProviderConfig::None);
299 }
300
301 #[test]
302 fn test_config_with_token_verifier_auth_jwt() {
303 use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
304 use slim_config::auth::jwt::{Claims, JwtKey};
305 use std::time::Duration;
306
307 let claims = Claims::default();
308 let duration = Duration::from_secs(3600);
309 let key = JwtKey::Decoding(Key {
310 algorithm: Algorithm::HS256,
311 format: KeyFormat::Pem,
312 key: KeyData::Data("test-secret".to_string()),
313 });
314 let jwt_config = JwtConfig::new(claims, duration, key);
315 let auth = IdentityVerifierConfig::Jwt(jwt_config);
316 let config = Config::new().with_token_verifier_auth(auth.clone());
317
318 assert_eq!(config.token_verifier, auth);
319 }
320
321 #[test]
322 fn test_config_servers_getter() {
323 let server_config = create_test_server_config();
324 let config = Config::new().with_servers(vec![server_config.clone()]);
325
326 let servers = config.servers();
327 assert_eq!(servers.len(), 1);
328 assert_eq!(servers[0], server_config);
329 }
330
331 #[test]
332 fn test_config_clients_getter() {
333 let client_config = create_test_client_config();
334 let config = Config::new().with_clients(vec![client_config.clone()]);
335
336 let clients = config.clients();
337 assert_eq!(clients.len(), 1);
338 assert_eq!(clients[0], client_config);
339 }
340
341 #[test]
342 fn test_config_chaining() {
343 let server_config = create_test_server_config();
344 let client_config = create_test_client_config();
345 let provider_auth = IdentityProviderConfig::SharedSecret {
346 id: "test-provider".to_string(),
347 data: "provider-secret".to_string(),
348 };
349 let verifier_auth = IdentityVerifierConfig::SharedSecret {
350 id: "test-verifier".to_string(),
351 data: "verifier-secret".to_string(),
352 };
353
354 let config = Config::new()
355 .with_servers(vec![server_config.clone()])
356 .with_clients(vec![client_config.clone()])
357 .with_token_provider_auth(provider_auth.clone())
358 .with_token_verifier_auth(verifier_auth.clone());
359
360 assert_eq!(config.servers.len(), 1);
361 assert_eq!(config.clients.len(), 1);
362 assert_eq!(config.token_provider, provider_auth);
363 assert_eq!(config.token_verifier, verifier_auth);
364 }
365
366 #[test]
367 fn test_config_validate_empty() {
368 let config = Config::new();
369 assert!(config.validate().is_ok());
370 }
371
372 #[test]
373 fn test_config_validate_with_valid_servers_and_clients() {
374 let server_config = create_test_server_config();
375 let client_config = create_test_client_config();
376 let config = Config::new()
377 .with_servers(vec![server_config])
378 .with_clients(vec![client_config]);
379
380 assert!(config.validate().is_ok());
381 }
382
383 #[test]
384 fn test_token_provider_auth_config_equality() {
385 let secret1 = IdentityProviderConfig::SharedSecret {
386 id: "test-id".to_string(),
387 data: "secret0".to_string(),
388 };
389 let secret2 = IdentityProviderConfig::SharedSecret {
390 id: "test-id".to_string(),
391 data: "secret0".to_string(),
392 };
393 let secret3 = IdentityProviderConfig::SharedSecret {
394 id: "test-id".to_string(),
395 data: "secret2".to_string(),
396 };
397
398 assert_eq!(secret1, secret2);
399 assert_ne!(secret1, secret3);
400 }
401
402 #[test]
403 fn test_token_verifier_auth_config_equality() {
404 let secret1 = IdentityVerifierConfig::SharedSecret {
405 id: "test-id".to_string(),
406 data: "secret0".to_string(),
407 };
408 let secret2 = IdentityVerifierConfig::SharedSecret {
409 id: "test-id".to_string(),
410 data: "secret0".to_string(),
411 };
412 let secret3 = IdentityVerifierConfig::SharedSecret {
413 id: "test-id".to_string(),
414 data: "secret2".to_string(),
415 };
416
417 assert_eq!(secret1, secret2);
418 assert_ne!(secret1, secret3);
419 }
420
421 #[test]
422 fn test_config_clone() {
423 let server_config = create_test_server_config();
424 let client_config = create_test_client_config();
425 let auth = IdentityProviderConfig::SharedSecret {
426 id: "test-provider".to_string(),
427 data: "secret0".to_string(),
428 };
429
430 let config1 = Config::new()
431 .with_servers(vec![server_config])
432 .with_clients(vec![client_config])
433 .with_token_provider_auth(auth);
434
435 let config2 = config1.clone();
436
437 assert_eq!(config1.servers, config2.servers);
438 assert_eq!(config1.clients, config2.clients);
439 assert_eq!(config1.token_provider, config2.token_provider);
440 assert_eq!(config1.token_verifier, config2.token_verifier);
441 }
442
443 #[tokio::test]
444 async fn test_config_into_service() {
445 let server_config = create_test_server_config();
446 let client_config = create_test_client_config();
447 let auth = IdentityProviderConfig::SharedSecret {
448 id: "test-provider".to_string(),
449 data: TEST_VALID_SECRET.to_string(),
450 };
451
452 let config = Config::new()
453 .with_servers(vec![server_config.clone()])
454 .with_clients(vec![client_config])
455 .with_token_provider_auth(auth);
456
457 let id = ID::new_with_name(Kind::new("slim").unwrap(), "test-instance").unwrap();
458 let group_name = Some("test-group".to_string());
459 let message_processor = Arc::new(MessageProcessor::new());
460
461 let _control_plane =
462 config.into_service(id, group_name, message_processor, &[server_config]);
463 }
464
465 #[test]
466 fn test_config_debug_trait() {
467 let config = Config::new();
468 let debug_str = format!("{:?}", config);
469 assert!(debug_str.contains("Config"));
470 assert!(debug_str.contains("servers"));
471 assert!(debug_str.contains("clients"));
472 }
473
474 mod serde_tests {
475 use super::*;
476 use serde_json;
477
478 #[test]
479 fn test_token_provider_auth_config_serialize_shared_secret() {
480 let auth = IdentityProviderConfig::SharedSecret {
481 id: "test-provider".to_string(),
482 data: "test-secret".to_string(),
483 };
484 let json = serde_json::to_string(&auth).unwrap();
485 assert!(json.contains("shared_secret"));
486 assert!(json.contains("test-secret"));
487 }
488
489 #[test]
490 fn test_token_provider_auth_config_deserialize_shared_secret() {
491 let json = r#"{"type": "shared_secret", "id": "test-provider", "data": "test-secret"}"#;
492 let auth: IdentityProviderConfig = serde_json::from_str(json).unwrap();
493
494 match auth {
495 IdentityProviderConfig::SharedSecret { id, data } => {
496 assert_eq!(id, "test-provider");
497 assert_eq!(data, "test-secret");
498 }
499 _ => panic!("Expected SharedSecret variant"),
500 }
501 }
502
503 #[test]
504 fn test_config_validate_with_multiple_servers() {
505 let server1 = create_test_server_config();
506 let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
507 .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
508
509 let config = Config::new().with_servers(vec![server1, server2]);
510 assert!(config.validate().is_ok());
511 }
512
513 #[test]
514 fn test_config_validate_with_multiple_clients() {
515 let client1 = create_test_client_config();
516 let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
517 .with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
518
519 let config = Config::new().with_clients(vec![client1, client2]);
520 assert!(config.validate().is_ok());
521 }
522
523 #[test]
524 fn test_config_with_all_auth_combinations() {
525 let provider_auth = IdentityProviderConfig::SharedSecret {
526 id: "test-provider".to_string(),
527 data: "provider-secret".to_string(),
528 };
529 let verifier_auth = IdentityVerifierConfig::SharedSecret {
530 id: "test-verifier".to_string(),
531 data: "verifier-secret".to_string(),
532 };
533
534 let config = Config::new()
535 .with_token_provider_auth(provider_auth.clone())
536 .with_token_verifier_auth(verifier_auth.clone());
537
538 assert_eq!(config.token_provider, provider_auth);
539 assert_eq!(config.token_verifier, verifier_auth);
540 }
541
542 #[test]
543 fn test_empty_servers_slice() {
544 let config = Config::new();
545 let servers = config.servers();
546 assert!(servers.is_empty());
547 assert_eq!(servers.len(), 0);
548 }
549
550 #[test]
551 fn test_empty_clients_slice() {
552 let config = Config::new();
553 let clients = config.clients();
554 assert!(clients.is_empty());
555 assert_eq!(clients.len(), 0);
556 }
557
558 #[test]
559 fn test_config_partial_eq() {
560 let config1 = Config::new();
561 let config2 = Config::new();
562
563 assert_eq!(config1, config2);
565
566 let server_config = create_test_server_config();
568 let config3 = config1.clone().with_servers(vec![server_config]);
569
570 assert_ne!(config1, config3);
572 }
573
574 #[test]
575 fn test_mixed_auth_types() {
576 use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
577
578 let static_jwt =
579 IdentityProviderConfig::StaticJwt(StaticJwtConfig::with_file("test-token.jwt"));
580
581 let jwt = IdentityVerifierConfig::Jwt(JwtConfig::new(
582 slim_config::auth::jwt::Claims::default(),
583 std::time::Duration::from_secs(3600),
584 slim_config::auth::jwt::JwtKey::Decoding(Key {
585 algorithm: Algorithm::HS256,
586 format: KeyFormat::Pem,
587 key: KeyData::Data("test-key".to_string()),
588 }),
589 ));
590
591 let config = Config::new()
592 .with_token_provider_auth(static_jwt.clone())
593 .with_token_verifier_auth(jwt.clone());
594
595 assert_eq!(config.token_provider, static_jwt);
596 assert_eq!(config.token_verifier, jwt);
597 }
598
599 mod edge_case_tests {
600 use super::*;
601
602 #[test]
603 fn test_config_builder_pattern_reuse() {
604 let base_config = Config::new();
605
606 let config1 = base_config
607 .clone()
608 .with_servers(vec![create_test_server_config()]);
609 let config2 = base_config
610 .clone()
611 .with_clients(vec![create_test_client_config()]);
612
613 assert!(base_config.servers.is_empty());
615 assert!(base_config.clients.is_empty());
616
617 assert_eq!(config1.servers.len(), 1);
619 assert!(config1.clients.is_empty());
620
621 assert!(config2.servers.is_empty());
622 assert_eq!(config2.clients.len(), 1);
623 }
624
625 #[test]
626 fn test_config_overwrite_behavior() {
627 let server1 = create_test_server_config();
628 let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
629 .with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
630
631 let config = Config::new()
632 .with_servers(vec![server1])
633 .with_servers(vec![server2.clone()]); assert_eq!(config.servers.len(), 1);
636 assert_eq!(config.servers[0], server2);
637 }
638
639 #[test]
640 fn test_auth_config_none_variants() {
641 let config = Config::new();
642
643 assert_eq!(config.token_provider, IdentityProviderConfig::None);
644 assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
645
646 let config_with_provider =
648 config
649 .clone()
650 .with_token_provider_auth(IdentityProviderConfig::SharedSecret {
651 id: "test-provider".to_string(),
652 data: "secret".to_string(),
653 });
654
655 assert_ne!(
656 config_with_provider.token_provider,
657 IdentityProviderConfig::None
658 );
659 assert_eq!(
660 config_with_provider.token_verifier,
661 IdentityVerifierConfig::None
662 );
663 }
664 }
665
666 #[test]
667 fn test_token_verifier_auth_config_serialize_shared_secret() {
668 let auth = IdentityVerifierConfig::SharedSecret {
669 id: "test-verifier".to_string(),
670 data: "test-secret".to_string(),
671 };
672 let json = serde_json::to_string(&auth).unwrap();
673 assert!(json.contains("shared_secret"));
674 assert!(json.contains("test-secret"));
675 }
676
677 #[test]
678 fn test_token_verifier_auth_config_deserialize_shared_secret() {
679 let json = r#"{"type": "shared_secret", "id": "test-verifier", "data": "test-secret"}"#;
680 let auth: IdentityVerifierConfig = serde_json::from_str(json).unwrap();
681
682 match auth {
683 IdentityVerifierConfig::SharedSecret { id, data } => {
684 assert_eq!(id, "test-verifier");
685 assert_eq!(data, "test-secret");
686 }
687 _ => panic!("Expected SharedSecret variant"),
688 }
689 }
690 }
691}