pulseengine_mcp_security_middleware/
config.rs1use 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#[derive(Debug, Clone)]
13pub struct SecurityConfig {
14 pub profile: SecurityProfile,
16
17 pub settings: SecuritySettings,
19
20 pub api_key: Option<String>,
22
23 pub jwt_secret: Option<String>,
25
26 pub jwt_issuer: String,
28
29 pub jwt_audience: String,
31
32 pub config_file_path: Option<PathBuf>,
34}
35
36impl SecurityConfig {
37 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 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 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 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 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 pub fn production() -> Self {
82 let profile = SecurityProfile::Production;
83 let settings = SecuritySettings::for_profile(&profile);
84 Self::new(profile, settings)
85 }
86
87 pub fn from_env() -> SecurityResult<Self> {
89 let profile = env::var("MCP_SECURITY_PROFILE")
91 .unwrap_or_else(|_| "production".to_string())
92 .parse::<SecurityProfile>()?;
93
94 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 config.load_from_env()?;
104
105 Ok(config)
106 }
107
108 pub fn load_from_env(&mut self) -> SecurityResult<()> {
110 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 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 if let Ok(issuer) = env::var("MCP_JWT_ISSUER") {
144 self.jwt_issuer = issuer;
145 }
146
147 if let Ok(audience) = env::var("MCP_JWT_AUDIENCE") {
149 self.jwt_audience = audience;
150 }
151
152 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 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 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 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 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 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; } else if cors_origin == "localhost" {
192 self.settings.cors = crate::profiles::CorsConfig::localhost_only();
193 } else {
194 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 pub fn validate(&self) -> SecurityResult<()> {
207 self.settings.validate()?;
209
210 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 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 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 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 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 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 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 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 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 pub fn with_jwt_issuer<S: Into<String>>(mut self, issuer: S) -> Self {
318 self.jwt_issuer = issuer.into();
319 self
320 }
321
322 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#[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
416fn 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()); 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 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 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}