//! Server configuration //! //! Configuration for the IAM server including storage backend, authentication, and server settings. use std::net::SocketAddr; use std::path::Path; use serde::{Deserialize, Serialize}; /// Main server configuration #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ServerConfig { /// Server settings pub server: ServerSettings, /// Storage backend configuration pub store: StoreConfig, /// Authentication configuration pub authn: AuthnConfig, /// Logging configuration #[serde(default)] pub logging: LoggingConfig, } impl ServerConfig { /// Load configuration from a TOML file pub fn from_file(path: impl AsRef) -> Result { let content = std::fs::read_to_string(path).map_err(|e| ConfigError::Io(e.to_string()))?; toml::from_str(&content).map_err(|e| ConfigError::Parse(e.to_string())) } /// Load configuration from environment variables pub fn from_env() -> Result { // Try to find config file from environment if let Ok(path) = std::env::var("IAM_CONFIG") { return Self::from_file(path); } // Default paths to try let default_paths = ["iam.toml", "/etc/iam/iam.toml", "config/iam.toml"]; for path in &default_paths { if Path::new(path).exists() { return Self::from_file(path); } } // Use default configuration Ok(Self::default()) } /// Create a minimal configuration for testing #[cfg(test)] pub fn for_testing() -> Self { Self { server: ServerSettings { addr: "127.0.0.1:50051".parse().unwrap(), http_addr: "127.0.0.1:8083".parse().unwrap(), tls: None, }, store: StoreConfig { backend: BackendKind::Memory, chainfire_endpoints: None, flaredb_endpoint: None, flaredb_namespace: None, }, authn: AuthnConfig { jwt: None, internal_token: InternalTokenConfig { signing_key: "test-signing-key-for-development-only".into(), issuer: "iam-test".into(), default_ttl_seconds: 3600, max_ttl_seconds: 86400, }, }, logging: LoggingConfig::default(), } } } /// Server settings #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerSettings { /// Listen address #[serde(default = "default_addr")] pub addr: SocketAddr, /// HTTP REST API listen address #[serde(default = "default_http_addr")] pub http_addr: SocketAddr, /// TLS configuration pub tls: Option, } impl Default for ServerSettings { fn default() -> Self { Self { addr: default_addr(), http_addr: default_http_addr(), tls: None, } } } fn default_addr() -> SocketAddr { "0.0.0.0:50051".parse().unwrap() } fn default_http_addr() -> SocketAddr { "127.0.0.1:8083".parse().unwrap() } /// TLS configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TlsConfig { /// Path to certificate file (PEM) pub cert_file: String, /// Path to private key file (PEM) pub key_file: String, /// Path to CA certificate file for client verification (optional) pub ca_file: Option, /// Require client certificates #[serde(default)] pub require_client_cert: bool, } /// Storage backend configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StoreConfig { /// Backend type #[serde(default)] pub backend: BackendKind, /// Chainfire endpoints (for chainfire backend) pub chainfire_endpoints: Option>, /// FlareDB endpoint (for flaredb backend) pub flaredb_endpoint: Option, /// FlareDB namespace (for flaredb backend) pub flaredb_namespace: Option, } impl Default for StoreConfig { fn default() -> Self { Self { backend: BackendKind::Memory, chainfire_endpoints: None, flaredb_endpoint: None, flaredb_namespace: Some("iam".into()), } } } /// Backend type #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum BackendKind { /// In-memory backend (for testing) #[default] Memory, /// Chainfire distributed KV Chainfire, /// FlareDB distributed database FlareDb, } /// Authentication configuration #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct AuthnConfig { /// JWT/OIDC configuration pub jwt: Option, /// Internal token configuration pub internal_token: InternalTokenConfig, } /// JWT/OIDC configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JwtConfig { /// JWKS endpoint URL pub jwks_url: String, /// Expected issuer pub issuer: String, /// Expected audience pub audience: String, /// JWKS cache TTL in seconds #[serde(default = "default_jwks_cache_ttl")] pub jwks_cache_ttl_seconds: u64, } fn default_jwks_cache_ttl() -> u64 { 3600 } /// Internal token configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InternalTokenConfig { /// Signing key (base64 encoded or raw string) pub signing_key: String, /// Token issuer identifier #[serde(default = "default_issuer")] pub issuer: String, /// Default token TTL in seconds #[serde(default = "default_token_ttl")] pub default_ttl_seconds: u64, /// Maximum token TTL in seconds #[serde(default = "default_max_token_ttl")] pub max_ttl_seconds: u64, } impl Default for InternalTokenConfig { fn default() -> Self { Self { signing_key: String::new(), issuer: default_issuer(), default_ttl_seconds: default_token_ttl(), max_ttl_seconds: default_max_token_ttl(), } } } fn default_issuer() -> String { "iam".into() } fn default_token_ttl() -> u64 { 3600 // 1 hour } fn default_max_token_ttl() -> u64 { 86400 * 7 // 7 days } /// Logging configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoggingConfig { /// Log level #[serde(default = "default_log_level")] pub level: String, /// Log format (json or text) #[serde(default = "default_log_format")] pub format: String, } impl Default for LoggingConfig { fn default() -> Self { Self { level: default_log_level(), format: default_log_format(), } } } fn default_log_level() -> String { "info".into() } fn default_log_format() -> String { "text".into() } /// Configuration errors #[derive(Debug)] pub enum ConfigError { /// IO error Io(String), /// Parse error Parse(String), } impl std::fmt::Display for ConfigError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ConfigError::Io(e) => write!(f, "IO error: {}", e), ConfigError::Parse(e) => write!(f, "Parse error: {}", e), } } } impl std::error::Error for ConfigError {} #[cfg(test)] mod tests { use super::*; #[test] fn test_default_config() { let config = ServerConfig::default(); assert_eq!(config.server.addr, "0.0.0.0:50051".parse().unwrap()); assert!(matches!(config.store.backend, BackendKind::Memory)); } #[test] fn test_for_testing() { let config = ServerConfig::for_testing(); assert!(!config.authn.internal_token.signing_key.is_empty()); } #[test] fn test_toml_parse() { let toml = r#" [server] addr = "127.0.0.1:8080" [store] backend = "memory" [authn.internal_token] signing_key = "secret" issuer = "test" default_ttl_seconds = 1800 [logging] level = "debug" "#; let config: ServerConfig = toml::from_str(toml).unwrap(); assert_eq!(config.server.addr, "127.0.0.1:8080".parse().unwrap()); assert_eq!(config.authn.internal_token.default_ttl_seconds, 1800); assert_eq!(config.logging.level, "debug"); } }