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,447 @@
|
|
1
|
+
"""
|
2
|
+
FastAPI Authentication Middleware Module
|
3
|
+
|
4
|
+
This module provides FastAPI-specific authentication middleware implementation
|
5
|
+
for the MCP Security Framework. It handles authentication-only processing
|
6
|
+
for FastAPI applications.
|
7
|
+
|
8
|
+
Key Features:
|
9
|
+
- FastAPI-specific request/response handling
|
10
|
+
- Authentication caching and optimization
|
11
|
+
- Framework-specific error responses
|
12
|
+
- Security event logging
|
13
|
+
|
14
|
+
Classes:
|
15
|
+
FastAPIAuthMiddleware: FastAPI authentication middleware implementation
|
16
|
+
|
17
|
+
Dependencies:
|
18
|
+
- fastapi: For FastAPI request/response handling
|
19
|
+
- starlette: For HTTP status codes and responses
|
20
|
+
- pydantic: For configuration validation
|
21
|
+
|
22
|
+
Author: MCP Security Team
|
23
|
+
Version: 1.0.0
|
24
|
+
License: MIT
|
25
|
+
Created: 2024-01-15
|
26
|
+
Last Modified: 2024-01-20
|
27
|
+
"""
|
28
|
+
|
29
|
+
import json
|
30
|
+
import logging
|
31
|
+
from typing import Any, Dict, Optional
|
32
|
+
|
33
|
+
from fastapi import Request, Response, status
|
34
|
+
from fastapi.responses import JSONResponse
|
35
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
36
|
+
|
37
|
+
from mcp_security_framework.middleware.auth_middleware import AuthMiddleware
|
38
|
+
from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
|
39
|
+
from mcp_security_framework.schemas.config import SecurityConfig
|
40
|
+
|
41
|
+
|
42
|
+
class FastAPIAuthMiddleware(AuthMiddleware):
|
43
|
+
"""
|
44
|
+
FastAPI Authentication Middleware Implementation
|
45
|
+
|
46
|
+
This class provides FastAPI-specific authentication middleware that
|
47
|
+
handles authentication-only processing for FastAPI applications.
|
48
|
+
|
49
|
+
The middleware implements:
|
50
|
+
- FastAPI request/response handling
|
51
|
+
- Authentication caching and optimization
|
52
|
+
- Framework-specific error responses
|
53
|
+
- Security event logging
|
54
|
+
|
55
|
+
Attributes:
|
56
|
+
config (SecurityConfig): Security configuration settings
|
57
|
+
security_manager: Security manager instance
|
58
|
+
logger (Logger): Logger instance for authentication operations
|
59
|
+
_auth_cache (Dict): Authentication result cache
|
60
|
+
|
61
|
+
Example:
|
62
|
+
>>> from mcp_security_framework.middleware import create_auth_middleware
|
63
|
+
>>> middleware = create_auth_middleware(config, framework="fastapi")
|
64
|
+
>>> app.add_middleware(middleware)
|
65
|
+
|
66
|
+
Raises:
|
67
|
+
AuthMiddlewareError: When authentication processing fails
|
68
|
+
"""
|
69
|
+
|
70
|
+
def __init__(self, config: SecurityConfig, security_manager: Any):
|
71
|
+
"""
|
72
|
+
Initialize FastAPI Authentication Middleware.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
config (SecurityConfig): Security configuration settings
|
76
|
+
security_manager: Security manager instance
|
77
|
+
"""
|
78
|
+
super().__init__(config, security_manager)
|
79
|
+
self.logger = logging.getLogger(__name__)
|
80
|
+
|
81
|
+
async def __call__(self, request: Request, call_next: Any) -> Response:
|
82
|
+
"""
|
83
|
+
Process FastAPI request through authentication middleware.
|
84
|
+
|
85
|
+
This method implements the authentication-only processing
|
86
|
+
pipeline for FastAPI requests, focusing solely on user
|
87
|
+
authentication without authorization checks.
|
88
|
+
|
89
|
+
Args:
|
90
|
+
request (Request): FastAPI request object
|
91
|
+
call_next: FastAPI call_next function
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Response: FastAPI response object
|
95
|
+
|
96
|
+
Raises:
|
97
|
+
AuthMiddlewareError: If authentication processing fails
|
98
|
+
"""
|
99
|
+
try:
|
100
|
+
# Check if path is public (bypasses authentication)
|
101
|
+
if self._is_public_path(request):
|
102
|
+
return await call_next(request)
|
103
|
+
|
104
|
+
# Perform authentication
|
105
|
+
auth_result = await self._authenticate_only(request)
|
106
|
+
|
107
|
+
if not auth_result.is_valid:
|
108
|
+
return self._auth_error_response(auth_result)
|
109
|
+
|
110
|
+
# Add authentication info to request state
|
111
|
+
request.state.auth_result = auth_result
|
112
|
+
request.state.username = auth_result.username
|
113
|
+
request.state.user_roles = auth_result.roles
|
114
|
+
request.state.auth_method = auth_result.auth_method
|
115
|
+
|
116
|
+
# Process request
|
117
|
+
response = await call_next(request)
|
118
|
+
|
119
|
+
# Log successful authentication
|
120
|
+
self._log_auth_event("authentication_successful", {
|
121
|
+
"ip_address": self._get_client_ip(request),
|
122
|
+
"username": auth_result.username,
|
123
|
+
"path": str(request.url.path),
|
124
|
+
"method": request.method,
|
125
|
+
"auth_method": auth_result.auth_method
|
126
|
+
})
|
127
|
+
|
128
|
+
return response
|
129
|
+
|
130
|
+
except Exception as e:
|
131
|
+
self.logger.error(
|
132
|
+
"FastAPI authentication middleware processing failed",
|
133
|
+
extra={"error": str(e)},
|
134
|
+
exc_info=True
|
135
|
+
)
|
136
|
+
raise AuthMiddlewareError(
|
137
|
+
f"Authentication processing failed: {str(e)}",
|
138
|
+
error_code=-32035
|
139
|
+
)
|
140
|
+
|
141
|
+
def _is_public_path(self, request: Request) -> bool:
|
142
|
+
"""
|
143
|
+
Check if the request path is public (bypasses authentication).
|
144
|
+
|
145
|
+
Args:
|
146
|
+
request (Request): FastAPI request object
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
bool: True if path is public, False otherwise
|
150
|
+
"""
|
151
|
+
path = str(request.url.path)
|
152
|
+
|
153
|
+
# Check configured public paths
|
154
|
+
if hasattr(self.config.auth, 'public_paths'):
|
155
|
+
for public_path in self.config.auth.public_paths:
|
156
|
+
if path.startswith(public_path):
|
157
|
+
return True
|
158
|
+
|
159
|
+
# Check common public paths
|
160
|
+
public_paths = ["/health", "/status", "/metrics", "/docs", "/openapi.json"]
|
161
|
+
return any(path.startswith(public_path) for public_path in public_paths)
|
162
|
+
|
163
|
+
def _get_client_ip(self, request: Request) -> str:
|
164
|
+
"""
|
165
|
+
Get client IP address from FastAPI request.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
request (Request): FastAPI request object
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
str: Client IP address
|
172
|
+
"""
|
173
|
+
# Try X-Forwarded-For header
|
174
|
+
forwarded_for = request.headers.get("X-Forwarded-For")
|
175
|
+
if forwarded_for:
|
176
|
+
return forwarded_for.split(",")[0].strip()
|
177
|
+
|
178
|
+
# Try X-Real-IP header
|
179
|
+
real_ip = request.headers.get("X-Real-IP")
|
180
|
+
if real_ip:
|
181
|
+
return real_ip
|
182
|
+
|
183
|
+
# Use client host
|
184
|
+
if hasattr(request, 'client') and request.client:
|
185
|
+
return request.client.host
|
186
|
+
|
187
|
+
# Fallback to default IP from config or environment
|
188
|
+
default_ip = getattr(self.config, 'default_client_ip', None)
|
189
|
+
if default_ip:
|
190
|
+
return default_ip
|
191
|
+
|
192
|
+
# Use environment variable or default
|
193
|
+
import os
|
194
|
+
from ..constants import DEFAULT_CLIENT_IP
|
195
|
+
return os.environ.get('DEFAULT_CLIENT_IP', DEFAULT_CLIENT_IP)
|
196
|
+
|
197
|
+
def _get_cache_key(self, request: Request) -> str:
|
198
|
+
"""
|
199
|
+
Generate cache key for authentication result.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
request (Request): FastAPI request object
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
str: Cache key
|
206
|
+
"""
|
207
|
+
# Use combination of IP and user agent for cache key
|
208
|
+
ip = self._get_client_ip(request)
|
209
|
+
user_agent = request.headers.get("User-Agent", "")
|
210
|
+
return f"auth:{ip}:{hash(user_agent)}"
|
211
|
+
|
212
|
+
async def _try_auth_method(self, request: Request, method: str) -> AuthResult:
|
213
|
+
"""
|
214
|
+
Try authentication using specific method with FastAPI request.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
request (Request): FastAPI request object
|
218
|
+
method (str): Authentication method to try
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
AuthResult: Authentication result
|
222
|
+
"""
|
223
|
+
try:
|
224
|
+
if method == "api_key":
|
225
|
+
return await self._try_api_key_auth(request)
|
226
|
+
elif method == "jwt":
|
227
|
+
return await self._try_jwt_auth(request)
|
228
|
+
elif method == "certificate":
|
229
|
+
return await self._try_certificate_auth(request)
|
230
|
+
elif method == "basic":
|
231
|
+
return await self._try_basic_auth(request)
|
232
|
+
else:
|
233
|
+
return AuthResult(
|
234
|
+
is_valid=False,
|
235
|
+
status=AuthStatus.FAILED,
|
236
|
+
username=None,
|
237
|
+
roles=[],
|
238
|
+
auth_method=None,
|
239
|
+
error_code=-32022,
|
240
|
+
error_message=f"Unsupported authentication method: {method}"
|
241
|
+
)
|
242
|
+
except Exception as e:
|
243
|
+
self.logger.error(
|
244
|
+
f"Authentication method {method} failed",
|
245
|
+
extra={"error": str(e)},
|
246
|
+
exc_info=True
|
247
|
+
)
|
248
|
+
return AuthResult(
|
249
|
+
is_valid=False,
|
250
|
+
status=AuthStatus.FAILED,
|
251
|
+
username=None,
|
252
|
+
roles=[],
|
253
|
+
auth_method=None,
|
254
|
+
error_code=-32023,
|
255
|
+
error_message=f"Authentication method {method} failed: {str(e)}"
|
256
|
+
)
|
257
|
+
|
258
|
+
async def _try_api_key_auth(self, request: Request) -> AuthResult:
|
259
|
+
"""
|
260
|
+
Try API key authentication with FastAPI request.
|
261
|
+
|
262
|
+
Args:
|
263
|
+
request (Request): FastAPI request object
|
264
|
+
|
265
|
+
Returns:
|
266
|
+
AuthResult: Authentication result
|
267
|
+
"""
|
268
|
+
# Try to get API key from headers
|
269
|
+
api_key = request.headers.get("X-API-Key")
|
270
|
+
if not api_key:
|
271
|
+
# Try Authorization header
|
272
|
+
auth_header = request.headers.get("Authorization")
|
273
|
+
if auth_header and auth_header.startswith("Bearer "):
|
274
|
+
api_key = auth_header[7:] # Remove "Bearer " prefix
|
275
|
+
|
276
|
+
if not api_key:
|
277
|
+
return AuthResult(
|
278
|
+
is_valid=False,
|
279
|
+
status=AuthStatus.FAILED,
|
280
|
+
username=None,
|
281
|
+
roles=[],
|
282
|
+
auth_method=AuthMethod.API_KEY,
|
283
|
+
error_code=-32012,
|
284
|
+
error_message="API key not found in request"
|
285
|
+
)
|
286
|
+
|
287
|
+
# Authenticate using security manager
|
288
|
+
return self.security_manager.auth_manager.authenticate_api_key(api_key)
|
289
|
+
|
290
|
+
async def _try_jwt_auth(self, request: Request) -> AuthResult:
|
291
|
+
"""
|
292
|
+
Try JWT authentication with FastAPI request.
|
293
|
+
|
294
|
+
Args:
|
295
|
+
request (Request): FastAPI request object
|
296
|
+
|
297
|
+
Returns:
|
298
|
+
AuthResult: Authentication result
|
299
|
+
"""
|
300
|
+
# Try to get JWT token from Authorization header
|
301
|
+
auth_header = request.headers.get("Authorization")
|
302
|
+
if not auth_header or not auth_header.startswith("Bearer "):
|
303
|
+
return AuthResult(
|
304
|
+
is_valid=False,
|
305
|
+
status=AuthStatus.FAILED,
|
306
|
+
username=None,
|
307
|
+
roles=[],
|
308
|
+
auth_method=AuthMethod.JWT,
|
309
|
+
error_code=-32013,
|
310
|
+
error_message="JWT token not found in Authorization header"
|
311
|
+
)
|
312
|
+
|
313
|
+
token = auth_header[7:] # Remove "Bearer " prefix
|
314
|
+
|
315
|
+
# Authenticate using security manager
|
316
|
+
return self.security_manager.auth_manager.authenticate_jwt_token(token)
|
317
|
+
|
318
|
+
async def _try_certificate_auth(self, request: Request) -> AuthResult:
|
319
|
+
"""
|
320
|
+
Try certificate authentication with FastAPI request.
|
321
|
+
|
322
|
+
Args:
|
323
|
+
request (Request): FastAPI request object
|
324
|
+
|
325
|
+
Returns:
|
326
|
+
AuthResult: Authentication result
|
327
|
+
"""
|
328
|
+
# Certificate authentication is typically handled at the SSL/TLS level
|
329
|
+
# This method would extract certificate information from the request
|
330
|
+
# For now, return not implemented
|
331
|
+
return AuthResult(
|
332
|
+
is_valid=False,
|
333
|
+
status=AuthStatus.FAILED,
|
334
|
+
username=None,
|
335
|
+
roles=[],
|
336
|
+
auth_method=AuthMethod.CERTIFICATE,
|
337
|
+
error_code=-32014,
|
338
|
+
error_message="Certificate authentication not implemented"
|
339
|
+
)
|
340
|
+
|
341
|
+
async def _try_basic_auth(self, request: Request) -> AuthResult:
|
342
|
+
"""
|
343
|
+
Try basic authentication with FastAPI request.
|
344
|
+
|
345
|
+
Args:
|
346
|
+
request (Request): FastAPI request object
|
347
|
+
|
348
|
+
Returns:
|
349
|
+
AuthResult: Authentication result
|
350
|
+
"""
|
351
|
+
# Try to get basic auth credentials from Authorization header
|
352
|
+
auth_header = request.headers.get("Authorization")
|
353
|
+
if not auth_header or not auth_header.startswith("Basic "):
|
354
|
+
return AuthResult(
|
355
|
+
is_valid=False,
|
356
|
+
status=AuthStatus.FAILED,
|
357
|
+
username=None,
|
358
|
+
roles=[],
|
359
|
+
auth_method=AuthMethod.BASIC,
|
360
|
+
error_code=-32015,
|
361
|
+
error_message="Basic authentication credentials not found"
|
362
|
+
)
|
363
|
+
|
364
|
+
# Basic auth is not implemented in this version
|
365
|
+
return AuthResult(
|
366
|
+
is_valid=False,
|
367
|
+
status=AuthStatus.FAILED,
|
368
|
+
username=None,
|
369
|
+
roles=[],
|
370
|
+
auth_method=AuthMethod.BASIC,
|
371
|
+
error_code=-32016,
|
372
|
+
error_message="Basic authentication not implemented"
|
373
|
+
)
|
374
|
+
|
375
|
+
def _auth_error_response(self, auth_result: AuthResult) -> JSONResponse:
|
376
|
+
"""
|
377
|
+
Create authentication error response for FastAPI.
|
378
|
+
|
379
|
+
Args:
|
380
|
+
auth_result (AuthResult): Authentication result with error
|
381
|
+
|
382
|
+
Returns:
|
383
|
+
JSONResponse: FastAPI error response
|
384
|
+
"""
|
385
|
+
error_data = {
|
386
|
+
"error": "Authentication failed",
|
387
|
+
"error_code": auth_result.error_code,
|
388
|
+
"error_message": auth_result.error_message,
|
389
|
+
"auth_method": auth_result.auth_method.value if auth_result.auth_method else None
|
390
|
+
}
|
391
|
+
|
392
|
+
headers = {
|
393
|
+
"WWW-Authenticate": "Bearer",
|
394
|
+
"Content-Type": "application/json"
|
395
|
+
}
|
396
|
+
|
397
|
+
return JSONResponse(
|
398
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
399
|
+
content=error_data,
|
400
|
+
headers=headers
|
401
|
+
)
|
402
|
+
|
403
|
+
def _log_auth_event(self, event_type: str, details: Dict[str, Any]) -> None:
|
404
|
+
"""
|
405
|
+
Log authentication event.
|
406
|
+
|
407
|
+
Args:
|
408
|
+
event_type (str): Type of authentication event
|
409
|
+
details (Dict[str, Any]): Event details
|
410
|
+
"""
|
411
|
+
try:
|
412
|
+
self.logger.info(
|
413
|
+
f"Authentication event: {event_type}",
|
414
|
+
extra={
|
415
|
+
"event_type": event_type,
|
416
|
+
"timestamp": details.get("timestamp"),
|
417
|
+
"ip_address": details.get("ip_address"),
|
418
|
+
"username": details.get("username"),
|
419
|
+
"path": details.get("path"),
|
420
|
+
"method": details.get("method"),
|
421
|
+
"auth_method": details.get("auth_method"),
|
422
|
+
**details
|
423
|
+
}
|
424
|
+
)
|
425
|
+
except Exception as e:
|
426
|
+
self.logger.error(
|
427
|
+
"Failed to log authentication event",
|
428
|
+
extra={"error": str(e)},
|
429
|
+
exc_info=True
|
430
|
+
)
|
431
|
+
|
432
|
+
|
433
|
+
class AuthMiddlewareError(Exception):
|
434
|
+
"""
|
435
|
+
Authentication Middleware Error
|
436
|
+
|
437
|
+
This exception is raised when authentication middleware processing fails.
|
438
|
+
|
439
|
+
Attributes:
|
440
|
+
message (str): Error message
|
441
|
+
error_code (int): Error code for programmatic handling
|
442
|
+
"""
|
443
|
+
|
444
|
+
def __init__(self, message: str, error_code: int = -32030):
|
445
|
+
self.message = message
|
446
|
+
self.error_code = error_code
|
447
|
+
super().__init__(self.message)
|