use std::sync::Arc;
use serde::Deserialize;
use slim_auth::auth_provider::{AuthProvider, AuthVerifier};
use slim_config::auth::identity::{IdentityProviderConfig, IdentityVerifierConfig};
use slim_config::component::configuration::Configuration;
use slim_config::component::id::ID;
use slim_config::grpc::client::ClientConfig;
use slim_config::grpc::server::ServerConfig;
use slim_datapath::message_processing::MessageProcessor;
use crate::errors::ControllerError;
use crate::service::{ControlPlane, ControlPlaneSettings, from_server_config};
#[derive(Debug, Clone, Deserialize, Default, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub servers: Vec<ServerConfig>,
#[serde(default)]
pub clients: Vec<ClientConfig>,
#[serde(default)]
pub token_provider: IdentityProviderConfig,
#[serde(default)]
pub token_verifier: IdentityVerifierConfig,
#[serde(default)]
pub recovery_ttl: Option<duration_string::DurationString>,
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn is_default(&self) -> bool {
self == &Self::default()
}
pub fn with_servers(self, servers: Vec<ServerConfig>) -> Self {
Self { servers, ..self }
}
pub fn with_clients(self, clients: Vec<ClientConfig>) -> Self {
Self { clients, ..self }
}
pub fn with_token_provider_auth(self, auth: IdentityProviderConfig) -> Self {
Self {
token_provider: auth,
..self
}
}
pub fn with_token_verifier_auth(self, auth: IdentityVerifierConfig) -> Self {
Self {
token_verifier: auth,
..self
}
}
pub fn servers(&self) -> &[ServerConfig] {
&self.servers
}
pub fn clients(&self) -> &[ClientConfig] {
&self.clients
}
fn get_token_provider_auth(&self) -> Option<AuthProvider> {
match &self.token_provider {
IdentityProviderConfig::SharedSecret { id, data } => {
AuthProvider::shared_secret_from_str(id, data).ok()
}
IdentityProviderConfig::StaticJwt(static_jwt_config) => {
let provider = static_jwt_config
.build_static_token_provider()
.expect("Failed to build StaticTokenProvider");
Some(AuthProvider::static_token(provider))
}
IdentityProviderConfig::Jwt(jwt_config) => {
let provider = jwt_config
.get_provider()
.expect("Failed to build JwtTokenProvider");
Some(AuthProvider::jwt_signer(provider))
}
#[cfg(not(target_family = "windows"))]
IdentityProviderConfig::Spire(spire_config) => {
let manager = spire_config
.create_provider()
.expect("Failed to build SpireIdentityManager");
Some(AuthProvider::spire(manager))
}
IdentityProviderConfig::None => None,
}
}
fn get_token_verifier_auth(&self) -> Option<AuthVerifier> {
match &self.token_verifier {
IdentityVerifierConfig::SharedSecret { id, data } => {
AuthVerifier::shared_secret_from_str(id, data).ok()
}
IdentityVerifierConfig::Jwt(jwt_config) => {
let verifier = jwt_config
.get_verifier()
.expect("Failed to build JwtTokenVerifier");
Some(AuthVerifier::jwt_verifier(verifier))
}
#[cfg(not(target_family = "windows"))]
IdentityVerifierConfig::Spire(spire_config) => {
let manager = spire_config
.create_provider()
.expect("Failed to build SpireIdentityManager");
Some(AuthVerifier::spire(manager))
}
IdentityVerifierConfig::None => None,
}
}
pub fn into_service(
&self,
id: ID,
group_name: Option<String>,
message_processor: Arc<MessageProcessor>,
dataplane_servers: &[ServerConfig],
) -> ControlPlane {
let auth_provider = self.get_token_provider_auth();
let auth_verifier = self.get_token_verifier_auth();
let connection_details = dataplane_servers.iter().map(from_server_config).collect();
ControlPlane::new(ControlPlaneSettings {
id,
group_name,
servers: self.servers.clone(),
clients: self.clients.clone(),
message_processor,
auth_provider,
auth_verifier,
connection_details,
})
}
}
impl Configuration for Config {
type Error = ControllerError;
fn validate(&self) -> Result<(), Self::Error> {
for server in self.servers.iter() {
server.validate()?;
}
for client in &self.clients {
client.validate()?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use slim_config::auth::jwt::Config as JwtConfig;
use slim_config::auth::static_jwt::Config as StaticJwtConfig;
use slim_config::component::id::{ID, Kind};
use slim_config::grpc::client::ClientConfig;
use slim_config::grpc::server::ServerConfig;
use slim_datapath::message_processing::MessageProcessor;
use slim_testing::utils::TEST_VALID_SECRET;
use std::sync::Arc;
fn create_test_server_config() -> ServerConfig {
ServerConfig::with_endpoint("127.0.0.1:50051")
.with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure())
}
fn create_test_client_config() -> ClientConfig {
ClientConfig::with_endpoint("http://127.0.0.1:50051")
.with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure())
}
#[test]
fn test_config_new() {
let config = Config::new();
assert!(config.servers.is_empty());
assert!(config.clients.is_empty());
assert_eq!(config.token_provider, IdentityProviderConfig::None);
assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
}
#[test]
fn test_config_default() {
let config = Config::default();
assert!(config.servers.is_empty());
assert!(config.clients.is_empty());
assert_eq!(config.token_provider, IdentityProviderConfig::None);
assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
}
#[test]
fn test_config_with_servers() {
let server_config = create_test_server_config();
let config = Config::new().with_servers(vec![server_config.clone()]);
assert_eq!(config.servers.len(), 1);
assert_eq!(config.servers[0], server_config);
assert!(config.clients.is_empty());
}
#[test]
fn test_config_with_clients() {
let client_config = create_test_client_config();
let config = Config::new().with_clients(vec![client_config.clone()]);
assert_eq!(config.clients.len(), 1);
assert_eq!(config.clients[0], client_config);
assert!(config.servers.is_empty());
}
#[test]
fn test_config_with_token_provider_auth_shared_secret() {
let auth = IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: "test-secret".to_string(),
};
let config = Config::new().with_token_provider_auth(auth.clone());
assert_eq!(config.token_provider, auth);
assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
}
#[test]
fn test_config_with_token_provider_auth_static_jwt() {
let static_jwt_config = StaticJwtConfig::with_file("test-key".to_string());
let auth = IdentityProviderConfig::StaticJwt(static_jwt_config);
let config = Config::new().with_token_provider_auth(auth.clone());
assert_eq!(config.token_provider, auth);
}
#[test]
fn test_config_with_token_provider_auth_jwt() {
use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
use slim_config::auth::jwt::{Claims, JwtKey};
use std::time::Duration;
let claims = Claims::default();
let duration = Duration::from_secs(3600);
let key = JwtKey::Encoding(Key {
algorithm: Algorithm::HS256,
format: KeyFormat::Pem,
key: KeyData::Data("test-secret".to_string()),
});
let jwt_config = JwtConfig::new(claims, duration, key);
let auth = IdentityProviderConfig::Jwt(jwt_config);
let config = Config::new().with_token_provider_auth(auth.clone());
assert_eq!(config.token_provider, auth);
}
#[test]
fn test_config_with_token_verifier_auth_shared_secret() {
let auth = IdentityVerifierConfig::SharedSecret {
id: "test-verifier".to_string(),
data: "test-secret".to_string(),
};
let config = Config::new().with_token_verifier_auth(auth.clone());
assert_eq!(config.token_verifier, auth);
assert_eq!(config.token_provider, IdentityProviderConfig::None);
}
#[test]
fn test_config_with_token_verifier_auth_jwt() {
use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
use slim_config::auth::jwt::{Claims, JwtKey};
use std::time::Duration;
let claims = Claims::default();
let duration = Duration::from_secs(3600);
let key = JwtKey::Decoding(Key {
algorithm: Algorithm::HS256,
format: KeyFormat::Pem,
key: KeyData::Data("test-secret".to_string()),
});
let jwt_config = JwtConfig::new(claims, duration, key);
let auth = IdentityVerifierConfig::Jwt(jwt_config);
let config = Config::new().with_token_verifier_auth(auth.clone());
assert_eq!(config.token_verifier, auth);
}
#[test]
fn test_config_servers_getter() {
let server_config = create_test_server_config();
let config = Config::new().with_servers(vec![server_config.clone()]);
let servers = config.servers();
assert_eq!(servers.len(), 1);
assert_eq!(servers[0], server_config);
}
#[test]
fn test_config_clients_getter() {
let client_config = create_test_client_config();
let config = Config::new().with_clients(vec![client_config.clone()]);
let clients = config.clients();
assert_eq!(clients.len(), 1);
assert_eq!(clients[0], client_config);
}
#[test]
fn test_config_chaining() {
let server_config = create_test_server_config();
let client_config = create_test_client_config();
let provider_auth = IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: "provider-secret".to_string(),
};
let verifier_auth = IdentityVerifierConfig::SharedSecret {
id: "test-verifier".to_string(),
data: "verifier-secret".to_string(),
};
let config = Config::new()
.with_servers(vec![server_config.clone()])
.with_clients(vec![client_config.clone()])
.with_token_provider_auth(provider_auth.clone())
.with_token_verifier_auth(verifier_auth.clone());
assert_eq!(config.servers.len(), 1);
assert_eq!(config.clients.len(), 1);
assert_eq!(config.token_provider, provider_auth);
assert_eq!(config.token_verifier, verifier_auth);
}
#[test]
fn test_config_validate_empty() {
let config = Config::new();
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validate_with_valid_servers_and_clients() {
let server_config = create_test_server_config();
let client_config = create_test_client_config();
let config = Config::new()
.with_servers(vec![server_config])
.with_clients(vec![client_config]);
assert!(config.validate().is_ok());
}
#[test]
fn test_token_provider_auth_config_equality() {
let secret1 = IdentityProviderConfig::SharedSecret {
id: "test-id".to_string(),
data: "secret0".to_string(),
};
let secret2 = IdentityProviderConfig::SharedSecret {
id: "test-id".to_string(),
data: "secret0".to_string(),
};
let secret3 = IdentityProviderConfig::SharedSecret {
id: "test-id".to_string(),
data: "secret2".to_string(),
};
assert_eq!(secret1, secret2);
assert_ne!(secret1, secret3);
}
#[test]
fn test_token_verifier_auth_config_equality() {
let secret1 = IdentityVerifierConfig::SharedSecret {
id: "test-id".to_string(),
data: "secret0".to_string(),
};
let secret2 = IdentityVerifierConfig::SharedSecret {
id: "test-id".to_string(),
data: "secret0".to_string(),
};
let secret3 = IdentityVerifierConfig::SharedSecret {
id: "test-id".to_string(),
data: "secret2".to_string(),
};
assert_eq!(secret1, secret2);
assert_ne!(secret1, secret3);
}
#[test]
fn test_config_clone() {
let server_config = create_test_server_config();
let client_config = create_test_client_config();
let auth = IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: "secret0".to_string(),
};
let config1 = Config::new()
.with_servers(vec![server_config])
.with_clients(vec![client_config])
.with_token_provider_auth(auth);
let config2 = config1.clone();
assert_eq!(config1.servers, config2.servers);
assert_eq!(config1.clients, config2.clients);
assert_eq!(config1.token_provider, config2.token_provider);
assert_eq!(config1.token_verifier, config2.token_verifier);
}
#[tokio::test]
async fn test_config_into_service() {
let server_config = create_test_server_config();
let client_config = create_test_client_config();
let auth = IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: TEST_VALID_SECRET.to_string(),
};
let config = Config::new()
.with_servers(vec![server_config.clone()])
.with_clients(vec![client_config])
.with_token_provider_auth(auth);
let id = ID::new_with_name(Kind::new("slim").unwrap(), "test-instance").unwrap();
let group_name = Some("test-group".to_string());
let message_processor = Arc::new(MessageProcessor::new());
let _control_plane =
config.into_service(id, group_name, message_processor, &[server_config]);
}
#[test]
fn test_config_debug_trait() {
let config = Config::new();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("Config"));
assert!(debug_str.contains("servers"));
assert!(debug_str.contains("clients"));
}
mod serde_tests {
use super::*;
use serde_json;
#[test]
fn test_token_provider_auth_config_serialize_shared_secret() {
let auth = IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: "test-secret".to_string(),
};
let json = serde_json::to_string(&auth).unwrap();
assert!(json.contains("shared_secret"));
assert!(json.contains("test-secret"));
}
#[test]
fn test_token_provider_auth_config_deserialize_shared_secret() {
let json = r#"{"type": "shared_secret", "id": "test-provider", "data": "test-secret"}"#;
let auth: IdentityProviderConfig = serde_json::from_str(json).unwrap();
match auth {
IdentityProviderConfig::SharedSecret { id, data } => {
assert_eq!(id, "test-provider");
assert_eq!(data, "test-secret");
}
_ => panic!("Expected SharedSecret variant"),
}
}
#[test]
fn test_config_validate_with_multiple_servers() {
let server1 = create_test_server_config();
let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
.with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
let config = Config::new().with_servers(vec![server1, server2]);
assert!(config.validate().is_ok());
}
#[test]
fn test_config_validate_with_multiple_clients() {
let client1 = create_test_client_config();
let client2 = ClientConfig::with_endpoint("http://127.0.0.1:50052")
.with_tls_setting(slim_config::tls::client::TlsClientConfig::insecure());
let config = Config::new().with_clients(vec![client1, client2]);
assert!(config.validate().is_ok());
}
#[test]
fn test_config_with_all_auth_combinations() {
let provider_auth = IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: "provider-secret".to_string(),
};
let verifier_auth = IdentityVerifierConfig::SharedSecret {
id: "test-verifier".to_string(),
data: "verifier-secret".to_string(),
};
let config = Config::new()
.with_token_provider_auth(provider_auth.clone())
.with_token_verifier_auth(verifier_auth.clone());
assert_eq!(config.token_provider, provider_auth);
assert_eq!(config.token_verifier, verifier_auth);
}
#[test]
fn test_empty_servers_slice() {
let config = Config::new();
let servers = config.servers();
assert!(servers.is_empty());
assert_eq!(servers.len(), 0);
}
#[test]
fn test_empty_clients_slice() {
let config = Config::new();
let clients = config.clients();
assert!(clients.is_empty());
assert_eq!(clients.len(), 0);
}
#[test]
fn test_config_partial_eq() {
let config1 = Config::new();
let config2 = Config::new();
assert_eq!(config1, config2);
let server_config = create_test_server_config();
let config3 = config1.clone().with_servers(vec![server_config]);
assert_ne!(config1, config3);
}
#[test]
fn test_mixed_auth_types() {
use slim_auth::jwt::{Algorithm, Key, KeyData, KeyFormat};
let static_jwt =
IdentityProviderConfig::StaticJwt(StaticJwtConfig::with_file("test-token.jwt"));
let jwt = IdentityVerifierConfig::Jwt(JwtConfig::new(
slim_config::auth::jwt::Claims::default(),
std::time::Duration::from_secs(3600),
slim_config::auth::jwt::JwtKey::Decoding(Key {
algorithm: Algorithm::HS256,
format: KeyFormat::Pem,
key: KeyData::Data("test-key".to_string()),
}),
));
let config = Config::new()
.with_token_provider_auth(static_jwt.clone())
.with_token_verifier_auth(jwt.clone());
assert_eq!(config.token_provider, static_jwt);
assert_eq!(config.token_verifier, jwt);
}
mod edge_case_tests {
use super::*;
#[test]
fn test_config_builder_pattern_reuse() {
let base_config = Config::new();
let config1 = base_config
.clone()
.with_servers(vec![create_test_server_config()]);
let config2 = base_config
.clone()
.with_clients(vec![create_test_client_config()]);
assert!(base_config.servers.is_empty());
assert!(base_config.clients.is_empty());
assert_eq!(config1.servers.len(), 1);
assert!(config1.clients.is_empty());
assert!(config2.servers.is_empty());
assert_eq!(config2.clients.len(), 1);
}
#[test]
fn test_config_overwrite_behavior() {
let server1 = create_test_server_config();
let server2 = ServerConfig::with_endpoint("127.0.0.1:50052")
.with_tls_settings(slim_config::tls::server::TlsServerConfig::insecure());
let config = Config::new()
.with_servers(vec![server1])
.with_servers(vec![server2.clone()]);
assert_eq!(config.servers.len(), 1);
assert_eq!(config.servers[0], server2);
}
#[test]
fn test_auth_config_none_variants() {
let config = Config::new();
assert_eq!(config.token_provider, IdentityProviderConfig::None);
assert_eq!(config.token_verifier, IdentityVerifierConfig::None);
let config_with_provider =
config
.clone()
.with_token_provider_auth(IdentityProviderConfig::SharedSecret {
id: "test-provider".to_string(),
data: "secret".to_string(),
});
assert_ne!(
config_with_provider.token_provider,
IdentityProviderConfig::None
);
assert_eq!(
config_with_provider.token_verifier,
IdentityVerifierConfig::None
);
}
}
#[test]
fn test_token_verifier_auth_config_serialize_shared_secret() {
let auth = IdentityVerifierConfig::SharedSecret {
id: "test-verifier".to_string(),
data: "test-secret".to_string(),
};
let json = serde_json::to_string(&auth).unwrap();
assert!(json.contains("shared_secret"));
assert!(json.contains("test-secret"));
}
#[test]
fn test_token_verifier_auth_config_deserialize_shared_secret() {
let json = r#"{"type": "shared_secret", "id": "test-verifier", "data": "test-secret"}"#;
let auth: IdentityVerifierConfig = serde_json::from_str(json).unwrap();
match auth {
IdentityVerifierConfig::SharedSecret { id, data } => {
assert_eq!(id, "test-verifier");
assert_eq!(data, "test-secret");
}
_ => panic!("Expected SharedSecret variant"),
}
}
}
}