mcp-security-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
"""
|
2
|
+
Rate Limit Middleware Module
|
3
|
+
|
4
|
+
This module provides specialized rate limiting middleware that focuses
|
5
|
+
solely on request rate limiting without authentication or authorization.
|
6
|
+
|
7
|
+
Key Features:
|
8
|
+
- Rate limiting-only processing
|
9
|
+
- Multiple rate limiting strategies
|
10
|
+
- Rate limit result caching
|
11
|
+
- Rate limit event logging
|
12
|
+
- Framework-agnostic design
|
13
|
+
|
14
|
+
Classes:
|
15
|
+
RateLimitMiddleware: Rate limiting-only middleware
|
16
|
+
RateLimitMiddlewareError: Rate limit middleware-specific error exception
|
17
|
+
|
18
|
+
Author: MCP Security Team
|
19
|
+
Version: 1.0.0
|
20
|
+
License: MIT
|
21
|
+
"""
|
22
|
+
|
23
|
+
import logging
|
24
|
+
import time
|
25
|
+
from abc import ABC, abstractmethod
|
26
|
+
from typing import Any, Dict, List, Optional, Tuple
|
27
|
+
|
28
|
+
from .security_middleware import SecurityMiddleware, SecurityMiddlewareError
|
29
|
+
|
30
|
+
|
31
|
+
class RateLimitMiddlewareError(SecurityMiddlewareError):
|
32
|
+
"""Raised when rate limit middleware encounters an error."""
|
33
|
+
|
34
|
+
def __init__(self, message: str, error_code: int = -32035):
|
35
|
+
self.message = message
|
36
|
+
self.error_code = error_code
|
37
|
+
super().__init__(self.message)
|
38
|
+
|
39
|
+
|
40
|
+
class RateLimitMiddleware(SecurityMiddleware):
|
41
|
+
"""
|
42
|
+
Rate Limiting-Only Middleware Class
|
43
|
+
|
44
|
+
This class provides rate limiting-only middleware that focuses
|
45
|
+
solely on request rate limiting without performing authentication
|
46
|
+
or authorization checks. It's useful for scenarios where rate
|
47
|
+
limiting is handled separately from other security concerns.
|
48
|
+
|
49
|
+
The RateLimitMiddleware implements:
|
50
|
+
- Rate limiting-only request processing
|
51
|
+
- Multiple rate limiting strategies
|
52
|
+
- Rate limit result caching
|
53
|
+
- Rate limit event logging
|
54
|
+
- Framework-agnostic design
|
55
|
+
|
56
|
+
Key Responsibilities:
|
57
|
+
- Process requests through rate limiting pipeline only
|
58
|
+
- Handle multiple rate limiting strategies
|
59
|
+
- Cache rate limit results for performance
|
60
|
+
- Log rate limit events and violations
|
61
|
+
- Provide rate limit status to downstream components
|
62
|
+
|
63
|
+
Attributes:
|
64
|
+
Inherits all attributes from SecurityMiddleware
|
65
|
+
_rate_limit_cache (Dict): Cache for rate limit results
|
66
|
+
_rate_limit_strategies (Dict): Available rate limiting strategies
|
67
|
+
|
68
|
+
Example:
|
69
|
+
>>> from mcp_security_framework.middleware import RateLimitMiddleware
|
70
|
+
>>>
|
71
|
+
>>> security_manager = SecurityManager(config)
|
72
|
+
>>> rate_limit_middleware = RateLimitMiddleware(security_manager)
|
73
|
+
>>> app.add_middleware(rate_limit_middleware)
|
74
|
+
|
75
|
+
Note:
|
76
|
+
This middleware only handles rate limiting. Authentication and
|
77
|
+
authorization should be handled separately by other middleware
|
78
|
+
or application logic.
|
79
|
+
"""
|
80
|
+
|
81
|
+
def __init__(self, security_manager):
|
82
|
+
"""
|
83
|
+
Initialize Rate Limiting-Only Middleware.
|
84
|
+
|
85
|
+
Args:
|
86
|
+
security_manager: Security manager instance containing
|
87
|
+
all security components and configuration.
|
88
|
+
|
89
|
+
Raises:
|
90
|
+
RateLimitMiddlewareError: If initialization fails
|
91
|
+
"""
|
92
|
+
super().__init__(security_manager)
|
93
|
+
self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
|
94
|
+
|
95
|
+
# Initialize rate limiting strategies
|
96
|
+
self._rate_limit_strategies = {
|
97
|
+
"ip": self._rate_limit_by_ip,
|
98
|
+
"user": self._rate_limit_by_user,
|
99
|
+
"global": self._rate_limit_global,
|
100
|
+
"path": self._rate_limit_by_path,
|
101
|
+
"method": self._rate_limit_by_method
|
102
|
+
}
|
103
|
+
|
104
|
+
self.logger.info("Rate limit middleware initialized")
|
105
|
+
|
106
|
+
@abstractmethod
|
107
|
+
def __call__(self, request: Any, call_next: Any) -> Any:
|
108
|
+
"""
|
109
|
+
Process request through rate limiting middleware.
|
110
|
+
|
111
|
+
This method implements the rate limiting-only processing
|
112
|
+
pipeline, focusing solely on request rate limiting.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
request: Framework-specific request object
|
116
|
+
call_next: Framework-specific call_next function
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
Framework-specific response object
|
120
|
+
|
121
|
+
Raises:
|
122
|
+
RateLimitMiddlewareError: If rate limiting processing fails
|
123
|
+
"""
|
124
|
+
pass
|
125
|
+
|
126
|
+
def _check_rate_limit_only(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
|
127
|
+
"""
|
128
|
+
Perform rate limiting-only processing.
|
129
|
+
|
130
|
+
This method handles rate limiting without authentication
|
131
|
+
or authorization checks.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
request: Framework-specific request object
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
|
138
|
+
|
139
|
+
Raises:
|
140
|
+
RateLimitMiddlewareError: If rate limiting process fails
|
141
|
+
"""
|
142
|
+
try:
|
143
|
+
if not self.config.rate_limit.enabled:
|
144
|
+
return True, {"strategy": "disabled", "reason": "Rate limiting disabled"}
|
145
|
+
|
146
|
+
# Get rate limiting strategy from config
|
147
|
+
strategy = self.config.rate_limit.strategy if hasattr(self.config.rate_limit, 'strategy') else "ip"
|
148
|
+
|
149
|
+
if strategy not in self._rate_limit_strategies:
|
150
|
+
self.logger.warning(f"Unknown rate limiting strategy: {strategy}, using 'ip'")
|
151
|
+
strategy = "ip"
|
152
|
+
|
153
|
+
# Apply rate limiting strategy
|
154
|
+
is_allowed, rate_limit_info = self._rate_limit_strategies[strategy](request)
|
155
|
+
|
156
|
+
# Log rate limit event
|
157
|
+
self._log_rate_limit_event(request, is_allowed, rate_limit_info)
|
158
|
+
|
159
|
+
return is_allowed, rate_limit_info
|
160
|
+
|
161
|
+
except Exception as e:
|
162
|
+
self.logger.error(
|
163
|
+
"Rate limiting process failed",
|
164
|
+
extra={"error": str(e)},
|
165
|
+
exc_info=True
|
166
|
+
)
|
167
|
+
raise RateLimitMiddlewareError(
|
168
|
+
f"Rate limiting process failed: {str(e)}",
|
169
|
+
error_code=-32036
|
170
|
+
)
|
171
|
+
|
172
|
+
def _rate_limit_by_ip(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
|
173
|
+
"""
|
174
|
+
Rate limit by IP address.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
request: Framework-specific request object
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
|
181
|
+
"""
|
182
|
+
identifier = self._get_rate_limit_identifier(request)
|
183
|
+
is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
|
184
|
+
|
185
|
+
return is_allowed, {
|
186
|
+
"strategy": "ip",
|
187
|
+
"identifier": identifier,
|
188
|
+
"is_allowed": is_allowed
|
189
|
+
}
|
190
|
+
|
191
|
+
def _rate_limit_by_user(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
|
192
|
+
"""
|
193
|
+
Rate limit by user (requires authentication).
|
194
|
+
|
195
|
+
Args:
|
196
|
+
request: Framework-specific request object
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
|
200
|
+
"""
|
201
|
+
# Try to get user identifier from request
|
202
|
+
user_id = self._get_user_identifier(request)
|
203
|
+
if not user_id:
|
204
|
+
# Fall back to IP-based rate limiting
|
205
|
+
return self._rate_limit_by_ip(request)
|
206
|
+
|
207
|
+
is_allowed = self.security_manager.rate_limiter.check_rate_limit(f"user:{user_id}")
|
208
|
+
|
209
|
+
return is_allowed, {
|
210
|
+
"strategy": "user",
|
211
|
+
"identifier": user_id,
|
212
|
+
"is_allowed": is_allowed
|
213
|
+
}
|
214
|
+
|
215
|
+
def _rate_limit_global(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
|
216
|
+
"""
|
217
|
+
Global rate limiting (all requests).
|
218
|
+
|
219
|
+
Args:
|
220
|
+
request: Framework-specific request object
|
221
|
+
|
222
|
+
Returns:
|
223
|
+
Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
|
224
|
+
"""
|
225
|
+
is_allowed = self.security_manager.rate_limiter.check_rate_limit("global")
|
226
|
+
|
227
|
+
return is_allowed, {
|
228
|
+
"strategy": "global",
|
229
|
+
"identifier": "global",
|
230
|
+
"is_allowed": is_allowed
|
231
|
+
}
|
232
|
+
|
233
|
+
def _rate_limit_by_path(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
|
234
|
+
"""
|
235
|
+
Rate limit by request path.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
request: Framework-specific request object
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
|
242
|
+
"""
|
243
|
+
path = self._get_request_path(request)
|
244
|
+
identifier = f"path:{path}"
|
245
|
+
is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
|
246
|
+
|
247
|
+
return is_allowed, {
|
248
|
+
"strategy": "path",
|
249
|
+
"identifier": identifier,
|
250
|
+
"path": path,
|
251
|
+
"is_allowed": is_allowed
|
252
|
+
}
|
253
|
+
|
254
|
+
def _rate_limit_by_method(self, request: Any) -> Tuple[bool, Dict[str, Any]]:
|
255
|
+
"""
|
256
|
+
Rate limit by HTTP method.
|
257
|
+
|
258
|
+
Args:
|
259
|
+
request: Framework-specific request object
|
260
|
+
|
261
|
+
Returns:
|
262
|
+
Tuple[bool, Dict[str, Any]]: (is_allowed, rate_limit_info)
|
263
|
+
"""
|
264
|
+
method = self._get_request_method(request)
|
265
|
+
identifier = f"method:{method}"
|
266
|
+
is_allowed = self.security_manager.rate_limiter.check_rate_limit(identifier)
|
267
|
+
|
268
|
+
return is_allowed, {
|
269
|
+
"strategy": "method",
|
270
|
+
"identifier": identifier,
|
271
|
+
"method": method,
|
272
|
+
"is_allowed": is_allowed
|
273
|
+
}
|
274
|
+
|
275
|
+
def _get_user_identifier(self, request: Any) -> Optional[str]:
|
276
|
+
"""
|
277
|
+
Get user identifier from request.
|
278
|
+
|
279
|
+
Args:
|
280
|
+
request: Framework-specific request object
|
281
|
+
|
282
|
+
Returns:
|
283
|
+
Optional[str]: User identifier if available, None otherwise
|
284
|
+
"""
|
285
|
+
# This should be implemented by framework-specific subclasses
|
286
|
+
# For now, return None to indicate no user identifier available
|
287
|
+
return None
|
288
|
+
|
289
|
+
def _get_request_method(self, request: Any) -> str:
|
290
|
+
"""
|
291
|
+
Get HTTP method from request.
|
292
|
+
|
293
|
+
Args:
|
294
|
+
request: Framework-specific request object
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
str: HTTP method
|
298
|
+
"""
|
299
|
+
# This should be implemented by framework-specific subclasses
|
300
|
+
return "GET"
|
301
|
+
|
302
|
+
def _log_rate_limit_event(self, request: Any, is_allowed: bool,
|
303
|
+
rate_limit_info: Dict[str, Any]) -> None:
|
304
|
+
"""
|
305
|
+
Log rate limit event.
|
306
|
+
|
307
|
+
Args:
|
308
|
+
request: Framework-specific request object
|
309
|
+
is_allowed (bool): Whether request is allowed
|
310
|
+
rate_limit_info (Dict[str, Any]): Rate limit information
|
311
|
+
"""
|
312
|
+
log_level = logging.WARNING if not is_allowed else logging.DEBUG
|
313
|
+
|
314
|
+
self.logger.log(
|
315
|
+
log_level,
|
316
|
+
f"Rate limit event: {'allowed' if is_allowed else 'blocked'}",
|
317
|
+
extra={
|
318
|
+
"event_type": "rate_limit",
|
319
|
+
"is_allowed": is_allowed,
|
320
|
+
"ip_address": self._get_rate_limit_identifier(request),
|
321
|
+
"path": self._get_request_path(request),
|
322
|
+
"method": self._get_request_method(request),
|
323
|
+
**rate_limit_info
|
324
|
+
}
|
325
|
+
)
|
326
|
+
|
327
|
+
def get_rate_limit_status(self, identifier: str) -> Dict[str, Any]:
|
328
|
+
"""
|
329
|
+
Get current rate limit status for an identifier.
|
330
|
+
|
331
|
+
Args:
|
332
|
+
identifier (str): Rate limit identifier
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
Dict[str, Any]: Rate limit status information
|
336
|
+
"""
|
337
|
+
try:
|
338
|
+
status = self.security_manager.rate_limiter.get_rate_limit_status(identifier)
|
339
|
+
return {
|
340
|
+
"identifier": identifier,
|
341
|
+
"current_count": status.current_count,
|
342
|
+
"limit": status.limit,
|
343
|
+
"window_seconds": status.window_seconds,
|
344
|
+
"remaining": status.remaining,
|
345
|
+
"reset_time": status.reset_time
|
346
|
+
}
|
347
|
+
except Exception as e:
|
348
|
+
self.logger.error(
|
349
|
+
"Failed to get rate limit status",
|
350
|
+
extra={"identifier": identifier, "error": str(e)}
|
351
|
+
)
|
352
|
+
return {
|
353
|
+
"identifier": identifier,
|
354
|
+
"error": str(e)
|
355
|
+
}
|
356
|
+
|
357
|
+
def reset_rate_limit(self, identifier: str) -> bool:
|
358
|
+
"""
|
359
|
+
Reset rate limit for an identifier.
|
360
|
+
|
361
|
+
Args:
|
362
|
+
identifier (str): Rate limit identifier
|
363
|
+
|
364
|
+
Returns:
|
365
|
+
bool: True if reset successful, False otherwise
|
366
|
+
"""
|
367
|
+
try:
|
368
|
+
self.security_manager.rate_limiter.reset_rate_limit(identifier)
|
369
|
+
self.logger.info(
|
370
|
+
"Rate limit reset",
|
371
|
+
extra={"identifier": identifier}
|
372
|
+
)
|
373
|
+
return True
|
374
|
+
except Exception as e:
|
375
|
+
self.logger.error(
|
376
|
+
"Failed to reset rate limit",
|
377
|
+
extra={"identifier": identifier, "error": str(e)}
|
378
|
+
)
|
379
|
+
return False
|
380
|
+
|
381
|
+
def get_rate_limit_statistics(self) -> Dict[str, Any]:
|
382
|
+
"""
|
383
|
+
Get rate limiting statistics.
|
384
|
+
|
385
|
+
Returns:
|
386
|
+
Dict[str, Any]: Rate limiting statistics
|
387
|
+
"""
|
388
|
+
try:
|
389
|
+
# This would need to be implemented based on the rate limiter
|
390
|
+
# implementation to provide actual statistics
|
391
|
+
return {
|
392
|
+
"total_requests": 0,
|
393
|
+
"blocked_requests": 0,
|
394
|
+
"active_identifiers": 0,
|
395
|
+
"cache_hits": 0,
|
396
|
+
"cache_misses": 0
|
397
|
+
}
|
398
|
+
except Exception as e:
|
399
|
+
self.logger.error(
|
400
|
+
"Failed to get rate limit statistics",
|
401
|
+
extra={"error": str(e)}
|
402
|
+
)
|
403
|
+
return {"error": str(e)}
|