pulseengine_mcp_security_middleware/
config.rs

1//! Configuration management for security middleware
2
3use crate::auth::{ApiKeyValidator, TokenValidator};
4use crate::error::{SecurityError, SecurityResult};
5use crate::profiles::{SecurityProfile, SecuritySettings};
6use crate::utils::{generate_api_key, generate_jwt_secret};
7use serde::{Deserialize, Serialize};
8use std::env;
9use std::path::PathBuf;
10
11/// Main security configuration
12#[derive(Debug, Clone)]
13pub struct SecurityConfig {
14    /// Security profile
15    pub profile: SecurityProfile,
16
17    /// Security settings
18    pub settings: SecuritySettings,
19
20    /// API key for simple authentication
21    pub api_key: Option<String>,
22
23    /// JWT secret for token validation
24    pub jwt_secret: Option<String>,
25
26    /// JWT issuer
27    pub jwt_issuer: String,
28
29    /// JWT audience
30    pub jwt_audience: String,
31
32    /// Configuration file path (if loaded from file)
33    pub config_file_path: Option<PathBuf>,
34}
35
36impl SecurityConfig {
37    /// Create a new security configuration with custom settings
38    pub fn new(profile: SecurityProfile, settings: SecuritySettings) -> Self {
39        Self {
40            profile: profile.clone(),
41            settings,
42            api_key: None,
43            jwt_secret: None,
44            jwt_issuer: "mcp-security-middleware".to_string(),
45            jwt_audience: "mcp-server".to_string(),
46            config_file_path: None,
47        }
48    }
49
50    /// Create development configuration
51    pub fn development() -> Self {
52        let profile = SecurityProfile::Development;
53        let settings = SecuritySettings::for_profile(&profile);
54        let mut config = Self::new(profile, settings);
55
56        // Auto-generate keys for development
57        if config.settings.auto_generate_keys {
58            config.api_key = Some(generate_api_key());
59            config.jwt_secret = Some(generate_jwt_secret());
60        }
61
62        config
63    }
64
65    /// Create staging configuration
66    pub fn staging() -> Self {
67        let profile = SecurityProfile::Staging;
68        let settings = SecuritySettings::for_profile(&profile);
69        let mut config = Self::new(profile, settings);
70
71        // Auto-generate keys for staging
72        if config.settings.auto_generate_keys {
73            config.api_key = Some(generate_api_key());
74            config.jwt_secret = Some(generate_jwt_secret());
75        }
76
77        config
78    }
79
80    /// Create production configuration
81    pub fn production() -> Self {
82        let profile = SecurityProfile::Production;
83        let settings = SecuritySettings::for_profile(&profile);
84        Self::new(profile, settings)
85    }
86
87    /// Create configuration from environment variables
88    pub fn from_env() -> SecurityResult<Self> {
89        // Get security profile from environment
90        let profile = env::var("MCP_SECURITY_PROFILE")
91            .unwrap_or_else(|_| "production".to_string())
92            .parse::<SecurityProfile>()?;
93
94        // Start with profile defaults
95        let mut config = match profile {
96            SecurityProfile::Development => Self::development(),
97            SecurityProfile::Staging => Self::staging(),
98            SecurityProfile::Production => Self::production(),
99            SecurityProfile::Custom => Self::new(profile, SecuritySettings::default()),
100        };
101
102        // Override with environment variables
103        config.load_from_env()?;
104
105        Ok(config)
106    }
107
108    /// Load configuration overrides from environment variables
109    pub fn load_from_env(&mut self) -> SecurityResult<()> {
110        // API Key
111        if let Ok(api_key) = env::var("MCP_API_KEY") {
112            if api_key == "auto-generate" {
113                if self.settings.auto_generate_keys {
114                    self.api_key = Some(generate_api_key());
115                    tracing::info!("Auto-generated API key for MCP server");
116                } else {
117                    return Err(SecurityError::config(
118                        "Auto-generation disabled for this security profile",
119                    ));
120                }
121            } else {
122                self.api_key = Some(api_key);
123            }
124        }
125
126        // JWT Secret
127        if let Ok(jwt_secret) = env::var("MCP_JWT_SECRET") {
128            if jwt_secret == "auto-generate" {
129                if self.settings.auto_generate_keys {
130                    self.jwt_secret = Some(generate_jwt_secret());
131                    tracing::info!("Auto-generated JWT secret for MCP server");
132                } else {
133                    return Err(SecurityError::config(
134                        "Auto-generation disabled for this security profile",
135                    ));
136                }
137            } else {
138                self.jwt_secret = Some(jwt_secret);
139            }
140        }
141
142        // JWT Issuer
143        if let Ok(issuer) = env::var("MCP_JWT_ISSUER") {
144            self.jwt_issuer = issuer;
145        }
146
147        // JWT Audience
148        if let Ok(audience) = env::var("MCP_JWT_AUDIENCE") {
149            self.jwt_audience = audience;
150        }
151
152        // HTTPS requirement
153        if let Ok(require_https) = env::var("MCP_REQUIRE_HTTPS") {
154            self.settings.require_https =
155                require_https.parse().unwrap_or(self.settings.require_https);
156        }
157
158        // Authentication requirement
159        if let Ok(require_auth) = env::var("MCP_REQUIRE_AUTH") {
160            self.settings.require_authentication = require_auth
161                .parse()
162                .unwrap_or(self.settings.require_authentication);
163        }
164
165        // Audit logging
166        if let Ok(audit_log) = env::var("MCP_ENABLE_AUDIT_LOG") {
167            self.settings.enable_audit_logging = audit_log
168                .parse()
169                .unwrap_or(self.settings.enable_audit_logging);
170        }
171
172        // JWT expiry
173        if let Ok(jwt_expiry) = env::var("MCP_JWT_EXPIRY") {
174            if let Ok(expiry_seconds) = jwt_expiry.parse::<u64>() {
175                self.settings.jwt_expiry_seconds = expiry_seconds;
176            }
177        }
178
179        // Rate limiting
180        if let Ok(rate_limit) = env::var("MCP_RATE_LIMIT") {
181            if let Some(config) = parse_rate_limit(&rate_limit) {
182                self.settings.rate_limit = config;
183            }
184        }
185
186        // CORS origins
187        if let Ok(cors_origin) = env::var("MCP_CORS_ORIGIN") {
188            if cors_origin == "*" {
189                self.settings.cors.allowed_origins = vec!["*".to_string()];
190                self.settings.cors.allow_credentials = false; // Can't use credentials with wildcard
191            } else if cors_origin == "localhost" {
192                self.settings.cors = crate::profiles::CorsConfig::localhost_only();
193            } else {
194                // Parse comma-separated origins
195                self.settings.cors.allowed_origins = cors_origin
196                    .split(',')
197                    .map(|s| s.trim().to_string())
198                    .collect();
199            }
200        }
201
202        Ok(())
203    }
204
205    /// Validate the configuration
206    pub fn validate(&self) -> SecurityResult<()> {
207        // Validate security settings
208        self.settings.validate()?;
209
210        // Check required keys for authentication
211        if self.settings.require_authentication
212            && self.api_key.is_none()
213            && self.jwt_secret.is_none()
214        {
215            return Err(SecurityError::config(
216                "Authentication is required but no API key or JWT secret provided. \
217                     Set MCP_API_KEY or MCP_JWT_SECRET environment variables, \
218                     or use MCP_API_KEY=auto-generate for development.",
219            ));
220        }
221
222        // Validate JWT configuration if JWT secret is provided
223        if let Some(ref secret) = self.jwt_secret {
224            if secret.len() < 32 {
225                return Err(SecurityError::config(
226                    "JWT secret must be at least 32 characters long for security",
227                ));
228            }
229
230            if self.jwt_issuer.is_empty() {
231                return Err(SecurityError::config("JWT issuer cannot be empty"));
232            }
233
234            if self.jwt_audience.is_empty() {
235                return Err(SecurityError::config("JWT audience cannot be empty"));
236            }
237        }
238
239        // Validate API key format if provided
240        if let Some(ref api_key) = self.api_key {
241            crate::utils::validate_api_key_format(api_key)?;
242        }
243
244        Ok(())
245    }
246
247    /// Create an API key validator from this configuration
248    pub fn create_api_key_validator(&self) -> SecurityResult<Option<ApiKeyValidator>> {
249        if let Some(ref api_key) = self.api_key {
250            let mut validator = ApiKeyValidator::new();
251            validator.add_api_key(api_key, "default-user".to_string())?;
252            Ok(Some(validator))
253        } else {
254            Ok(None)
255        }
256    }
257
258    /// Create a token validator from this configuration
259    pub fn create_token_validator(&self) -> SecurityResult<Option<TokenValidator>> {
260        if let Some(ref jwt_secret) = self.jwt_secret {
261            let validator = TokenValidator::new(
262                jwt_secret,
263                self.jwt_issuer.clone(),
264                self.jwt_audience.clone(),
265            );
266            Ok(Some(validator))
267        } else {
268            Ok(None)
269        }
270    }
271
272    /// Create security middleware from this configuration
273    pub async fn create_middleware(&self) -> SecurityResult<crate::middleware::SecurityMiddleware> {
274        self.validate()?;
275
276        let api_key_validator = self.create_api_key_validator()?;
277        let token_validator = self.create_token_validator()?;
278
279        let middleware = crate::middleware::SecurityMiddleware::new(
280            self.clone(),
281            api_key_validator,
282            token_validator,
283        );
284
285        Ok(middleware)
286    }
287
288    /// Get configuration summary for logging
289    pub fn summary(&self) -> ConfigSummary {
290        ConfigSummary {
291            profile: self.profile.clone(),
292            security_level: self.settings.security_level_description().to_string(),
293            authentication_enabled: self.settings.require_authentication,
294            https_required: self.settings.require_https,
295            rate_limiting_enabled: self.settings.rate_limit.enabled,
296            cors_enabled: self.settings.cors.enabled,
297            audit_logging_enabled: self.settings.enable_audit_logging,
298            has_api_key: self.api_key.is_some(),
299            has_jwt_secret: self.jwt_secret.is_some(),
300            jwt_expiry_minutes: self.settings.jwt_expiry_seconds / 60,
301        }
302    }
303
304    /// Set API key
305    pub fn with_api_key<S: Into<String>>(mut self, api_key: S) -> Self {
306        self.api_key = Some(api_key.into());
307        self
308    }
309
310    /// Set JWT secret
311    pub fn with_jwt_secret<S: Into<String>>(mut self, jwt_secret: S) -> Self {
312        self.jwt_secret = Some(jwt_secret.into());
313        self
314    }
315
316    /// Set JWT issuer
317    pub fn with_jwt_issuer<S: Into<String>>(mut self, issuer: S) -> Self {
318        self.jwt_issuer = issuer.into();
319        self
320    }
321
322    /// Set JWT audience
323    pub fn with_jwt_audience<S: Into<String>>(mut self, audience: S) -> Self {
324        self.jwt_audience = audience.into();
325        self
326    }
327}
328
329/// Configuration summary for logging and display
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct ConfigSummary {
332    pub profile: SecurityProfile,
333    pub security_level: String,
334    pub authentication_enabled: bool,
335    pub https_required: bool,
336    pub rate_limiting_enabled: bool,
337    pub cors_enabled: bool,
338    pub audit_logging_enabled: bool,
339    pub has_api_key: bool,
340    pub has_jwt_secret: bool,
341    pub jwt_expiry_minutes: u64,
342}
343
344impl std::fmt::Display for ConfigSummary {
345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346        writeln!(f, "MCP Security Configuration Summary:")?;
347        writeln!(f, "  Profile: {}", self.profile)?;
348        writeln!(f, "  Security Level: {}", self.security_level)?;
349        writeln!(
350            f,
351            "  Authentication: {}",
352            if self.authentication_enabled {
353                "✓ Enabled"
354            } else {
355                "✗ Disabled"
356            }
357        )?;
358        writeln!(
359            f,
360            "  HTTPS Required: {}",
361            if self.https_required {
362                "✓ Yes"
363            } else {
364                "✗ No"
365            }
366        )?;
367        writeln!(
368            f,
369            "  Rate Limiting: {}",
370            if self.rate_limiting_enabled {
371                "✓ Enabled"
372            } else {
373                "✗ Disabled"
374            }
375        )?;
376        writeln!(
377            f,
378            "  CORS: {}",
379            if self.cors_enabled {
380                "✓ Enabled"
381            } else {
382                "✗ Disabled"
383            }
384        )?;
385        writeln!(
386            f,
387            "  Audit Logging: {}",
388            if self.audit_logging_enabled {
389                "✓ Enabled"
390            } else {
391                "✗ Disabled"
392            }
393        )?;
394        writeln!(
395            f,
396            "  API Key: {}",
397            if self.has_api_key {
398                "✓ Configured"
399            } else {
400                "✗ Not set"
401            }
402        )?;
403        writeln!(
404            f,
405            "  JWT Secret: {}",
406            if self.has_jwt_secret {
407                "✓ Configured"
408            } else {
409                "✗ Not set"
410            }
411        )?;
412        write!(f, "  JWT Expiry: {} minutes", self.jwt_expiry_minutes)
413    }
414}
415
416/// Parse rate limit string like "100/min" or "1000/hour"
417fn parse_rate_limit(rate_limit: &str) -> Option<crate::profiles::RateLimitConfig> {
418    let parts: Vec<&str> = rate_limit.split('/').collect();
419    if parts.len() != 2 {
420        return None;
421    }
422
423    let max_requests = parts[0].parse::<u32>().ok()?;
424    let window_duration = match parts[1] {
425        "sec" | "second" | "s" => std::time::Duration::from_secs(1),
426        "min" | "minute" | "m" => std::time::Duration::from_secs(60),
427        "hour" | "h" => std::time::Duration::from_secs(3600),
428        "day" | "d" => std::time::Duration::from_secs(86400),
429        _ => return None,
430    };
431
432    Some(crate::profiles::RateLimitConfig {
433        max_requests,
434        window_duration,
435        enabled: true,
436    })
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use std::env;
443
444    #[test]
445    fn test_development_config() {
446        let config = SecurityConfig::development();
447        assert_eq!(config.profile, SecurityProfile::Development);
448        assert!(!config.settings.require_authentication);
449        assert!(config.api_key.is_some());
450        assert!(config.jwt_secret.is_some());
451    }
452
453    #[test]
454    fn test_production_config() {
455        let config = SecurityConfig::production();
456        assert_eq!(config.profile, SecurityProfile::Production);
457        assert!(config.settings.require_authentication);
458        assert!(config.settings.require_https);
459        assert!(config.api_key.is_none()); // Must be explicitly configured
460        assert!(config.jwt_secret.is_none());
461    }
462
463    #[test]
464    fn test_config_validation() {
465        let mut config = SecurityConfig::development();
466        assert!(config.validate().is_ok());
467
468        // Remove keys but require authentication
469        config.api_key = None;
470        config.jwt_secret = None;
471        config.settings.require_authentication = true;
472        assert!(config.validate().is_err());
473    }
474
475    #[test]
476    #[serial_test::serial]
477    fn test_env_config_loading() {
478        unsafe {
479            env::set_var("MCP_SECURITY_PROFILE", "development");
480            env::set_var("MCP_API_KEY", "test-key-123");
481            env::set_var(
482                "MCP_JWT_SECRET",
483                "test-secret-very-long-secret-key-for-testing",
484            );
485        }
486
487        let config = SecurityConfig::from_env().unwrap();
488        assert_eq!(config.profile, SecurityProfile::Development);
489        assert_eq!(config.api_key.as_ref().unwrap(), "test-key-123");
490
491        // Clean up
492        unsafe {
493            env::remove_var("MCP_SECURITY_PROFILE");
494            env::remove_var("MCP_API_KEY");
495            env::remove_var("MCP_JWT_SECRET");
496        }
497    }
498
499    #[test]
500    fn test_parse_rate_limit() {
501        let config = parse_rate_limit("100/min").unwrap();
502        assert_eq!(config.max_requests, 100);
503        assert_eq!(config.window_duration, std::time::Duration::from_secs(60));
504
505        let config = parse_rate_limit("1000/hour").unwrap();
506        assert_eq!(config.max_requests, 1000);
507        assert_eq!(config.window_duration, std::time::Duration::from_secs(3600));
508
509        assert!(parse_rate_limit("invalid").is_none());
510    }
511
512    #[test]
513    fn test_config_summary() {
514        let config = SecurityConfig::development();
515        let summary = config.summary();
516
517        assert_eq!(summary.profile, SecurityProfile::Development);
518        assert!(!summary.authentication_enabled);
519        assert!(summary.has_api_key);
520        assert!(summary.has_jwt_secret);
521    }
522
523    #[test]
524    fn test_config_builder_methods() {
525        let config = SecurityConfig::development()
526            .with_api_key("custom-key")
527            .with_jwt_secret("custom-secret-very-long-for-security")
528            .with_jwt_issuer("custom-issuer")
529            .with_jwt_audience("custom-audience");
530
531        assert_eq!(config.api_key.as_ref().unwrap(), "custom-key");
532        assert_eq!(config.jwt_issuer, "custom-issuer");
533        assert_eq!(config.jwt_audience, "custom-audience");
534    }
535}