mcp-security-framework 1.1.0__py3-none-any.whl → 1.1.2__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.
Files changed (58) hide show
  1. mcp_security_framework/__init__.py +26 -15
  2. mcp_security_framework/cli/__init__.py +1 -1
  3. mcp_security_framework/cli/cert_cli.py +233 -197
  4. mcp_security_framework/cli/security_cli.py +324 -234
  5. mcp_security_framework/constants.py +21 -27
  6. mcp_security_framework/core/auth_manager.py +41 -22
  7. mcp_security_framework/core/cert_manager.py +210 -147
  8. mcp_security_framework/core/permission_manager.py +9 -9
  9. mcp_security_framework/core/rate_limiter.py +2 -2
  10. mcp_security_framework/core/security_manager.py +284 -229
  11. mcp_security_framework/examples/__init__.py +6 -0
  12. mcp_security_framework/examples/comprehensive_example.py +349 -279
  13. mcp_security_framework/examples/django_example.py +247 -206
  14. mcp_security_framework/examples/fastapi_example.py +315 -283
  15. mcp_security_framework/examples/flask_example.py +274 -203
  16. mcp_security_framework/examples/gateway_example.py +304 -237
  17. mcp_security_framework/examples/microservice_example.py +258 -189
  18. mcp_security_framework/examples/standalone_example.py +255 -230
  19. mcp_security_framework/examples/test_all_examples.py +151 -135
  20. mcp_security_framework/middleware/__init__.py +46 -55
  21. mcp_security_framework/middleware/auth_middleware.py +62 -63
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +119 -118
  23. mcp_security_framework/middleware/fastapi_middleware.py +156 -148
  24. mcp_security_framework/middleware/flask_auth_middleware.py +160 -147
  25. mcp_security_framework/middleware/flask_middleware.py +183 -157
  26. mcp_security_framework/middleware/mtls_middleware.py +106 -117
  27. mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
  28. mcp_security_framework/middleware/security_middleware.py +109 -124
  29. mcp_security_framework/schemas/config.py +2 -1
  30. mcp_security_framework/schemas/models.py +18 -6
  31. mcp_security_framework/utils/cert_utils.py +14 -8
  32. mcp_security_framework/utils/datetime_compat.py +116 -0
  33. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/METADATA +4 -3
  34. mcp_security_framework-1.1.2.dist-info/RECORD +84 -0
  35. tests/conftest.py +63 -66
  36. tests/test_cli/test_cert_cli.py +184 -146
  37. tests/test_cli/test_security_cli.py +274 -247
  38. tests/test_core/test_cert_manager.py +24 -10
  39. tests/test_core/test_security_manager.py +2 -2
  40. tests/test_examples/test_comprehensive_example.py +190 -137
  41. tests/test_examples/test_fastapi_example.py +124 -101
  42. tests/test_examples/test_flask_example.py +124 -101
  43. tests/test_examples/test_standalone_example.py +73 -80
  44. tests/test_integration/test_auth_flow.py +213 -197
  45. tests/test_integration/test_certificate_flow.py +180 -149
  46. tests/test_integration/test_fastapi_integration.py +108 -111
  47. tests/test_integration/test_flask_integration.py +141 -140
  48. tests/test_integration/test_standalone_integration.py +290 -259
  49. tests/test_middleware/test_fastapi_auth_middleware.py +195 -174
  50. tests/test_middleware/test_fastapi_middleware.py +147 -132
  51. tests/test_middleware/test_flask_auth_middleware.py +260 -202
  52. tests/test_middleware/test_flask_middleware.py +201 -179
  53. tests/test_middleware/test_security_middleware.py +145 -130
  54. tests/test_utils/test_datetime_compat.py +147 -0
  55. mcp_security_framework-1.1.0.dist-info/RECORD +0 -82
  56. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/WHEEL +0 -0
  57. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/entry_points.txt +0 -0
  58. {mcp_security_framework-1.1.0.dist-info → mcp_security_framework-1.1.2.dist-info}/top_level.txt +0 -0
@@ -17,31 +17,37 @@ Version: 1.0.0
17
17
  License: MIT
18
18
  """
19
19
 
20
- import os
20
+ import asyncio
21
21
  import json
22
22
  import logging
23
- import asyncio
24
- import aiohttp
25
- from typing import Dict, List, Any, Optional
23
+ import os
26
24
  from datetime import datetime, timedelta, timezone
25
+ from typing import Any, Dict, List, Optional
27
26
 
28
- from mcp_security_framework.core.security_manager import SecurityManager
27
+ import aiohttp
28
+
29
+ from mcp_security_framework.constants import (
30
+ AUTH_METHODS,
31
+ DEFAULT_CLIENT_IP,
32
+ DEFAULT_SECURITY_HEADERS,
33
+ HTTP_FORBIDDEN,
34
+ HTTP_TOO_MANY_REQUESTS,
35
+ HTTP_UNAUTHORIZED,
36
+ ErrorCodes,
37
+ )
29
38
  from mcp_security_framework.core.auth_manager import AuthManager
30
- from mcp_security_framework.core.ssl_manager import SSLManager
31
39
  from mcp_security_framework.core.permission_manager import PermissionManager
32
40
  from mcp_security_framework.core.rate_limiter import RateLimiter
33
- from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, SSLConfig
34
- from mcp_security_framework.schemas.models import AuthResult, AuthStatus, AuthMethod
35
- from mcp_security_framework.constants import (
36
- DEFAULT_CLIENT_IP, DEFAULT_SECURITY_HEADERS, AUTH_METHODS,
37
- ErrorCodes, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_TOO_MANY_REQUESTS
38
- )
41
+ from mcp_security_framework.core.security_manager import SecurityManager
42
+ from mcp_security_framework.core.ssl_manager import SSLManager
43
+ from mcp_security_framework.schemas.config import AuthConfig, SecurityConfig, SSLConfig
44
+ from mcp_security_framework.schemas.models import AuthMethod, AuthResult, AuthStatus
39
45
 
40
46
 
41
47
  class APIGatewayExample:
42
48
  """
43
49
  Complete API Gateway Example with Security Framework Implementation
44
-
50
+
45
51
  This class demonstrates a production-ready API Gateway
46
52
  with comprehensive security features including:
47
53
  - Request routing and load balancing
@@ -51,11 +57,11 @@ class APIGatewayExample:
51
57
  - Request/response transformation
52
58
  - Comprehensive logging and monitoring
53
59
  """
54
-
60
+
55
61
  def __init__(self, config_path: Optional[str] = None):
56
62
  """
57
63
  Initialize API Gateway example with security configuration.
58
-
64
+
59
65
  Args:
60
66
  config_path: Path to security configuration file
61
67
  """
@@ -65,46 +71,59 @@ class APIGatewayExample:
65
71
  self._setup_logging()
66
72
  self._setup_routing_rules()
67
73
  self._setup_load_balancer()
68
-
74
+
69
75
  def _load_config(self, config_path: Optional[str]) -> SecurityConfig:
70
76
  """
71
77
  Load security configuration from file or create default.
72
-
78
+
73
79
  Args:
74
80
  config_path: Path to configuration file
75
-
81
+
76
82
  Returns:
77
83
  SecurityConfig: Loaded configuration
78
84
  """
79
85
  if config_path and os.path.exists(config_path):
80
- with open(config_path, 'r') as f:
86
+ with open(config_path, "r") as f:
81
87
  config_data = json.load(f)
82
88
  return SecurityConfig(**config_data)
83
-
89
+
84
90
  # Create production-ready API Gateway configuration
85
91
  return SecurityConfig(
86
92
  auth=AuthConfig(
87
93
  enabled=True,
88
- methods=[AUTH_METHODS["API_KEY"], AUTH_METHODS["JWT"], AUTH_METHODS["CERTIFICATE"]],
94
+ methods=[
95
+ AUTH_METHODS["API_KEY"],
96
+ AUTH_METHODS["JWT"],
97
+ AUTH_METHODS["CERTIFICATE"],
98
+ ],
89
99
  api_keys={
90
- "gateway_key_123": {"username": "gateway-admin", "roles": ["gateway", "admin"]},
91
- "client_key_456": {"username": "client-app", "roles": ["client", "user"]},
92
- "service_key_789": {"username": "internal-service", "roles": ["service", "internal"]}
100
+ "gateway_key_123": {
101
+ "username": "gateway-admin",
102
+ "roles": ["gateway", "admin"],
103
+ },
104
+ "client_key_456": {
105
+ "username": "client-app",
106
+ "roles": ["client", "user"],
107
+ },
108
+ "service_key_789": {
109
+ "username": "internal-service",
110
+ "roles": ["service", "internal"],
111
+ },
93
112
  },
94
113
  jwt_secret="your-super-secret-jwt-key-change-in-production",
95
114
  jwt_algorithm="HS256",
96
115
  jwt_expiry_hours=24,
97
116
  public_paths=["/health", "/metrics", "/status"],
98
- security_headers=DEFAULT_SECURITY_HEADERS
117
+ security_headers=DEFAULT_SECURITY_HEADERS,
118
+ ),
119
+ ssl=SSLConfig(
120
+ enabled=False, # Disable SSL for example
121
+ cert_file=None,
122
+ key_file=None,
123
+ ca_cert_file=None,
124
+ verify_mode="CERT_REQUIRED",
125
+ min_version="TLSv1.2",
99
126
  ),
100
- ssl=SSLConfig(
101
- enabled=False, # Disable SSL for example
102
- cert_file=None,
103
- key_file=None,
104
- ca_cert_file=None,
105
- verify_mode="CERT_REQUIRED",
106
- min_version="TLSv1.2"
107
- ),
108
127
  rate_limit={
109
128
  "enabled": True,
110
129
  "default_requests_per_minute": 500, # Gateway-level limits
@@ -116,16 +135,16 @@ class APIGatewayExample:
116
135
  "host": "redis-cluster",
117
136
  "port": 6379,
118
137
  "db": 0,
119
- "password": None
138
+ "password": None,
120
139
  },
121
140
  "exempt_paths": ["/health", "/metrics", "/status"],
122
- "exempt_roles": ["gateway", "admin"]
141
+ "exempt_roles": ["gateway", "admin"],
123
142
  },
124
143
  permissions={
125
144
  "enabled": True,
126
145
  "roles_file": "config/roles.json",
127
146
  "default_role": "user",
128
- "hierarchy_enabled": True
147
+ "hierarchy_enabled": True,
129
148
  },
130
149
  logging={
131
150
  "enabled": True,
@@ -135,10 +154,10 @@ class APIGatewayExample:
135
154
  "max_file_size": 10,
136
155
  "backup_count": 5,
137
156
  "console_output": True,
138
- "json_format": True
139
- }
157
+ "json_format": True,
158
+ },
140
159
  )
141
-
160
+
142
161
  def _setup_logging(self):
143
162
  """Setup logging configuration."""
144
163
  if self.config.logging.enabled:
@@ -146,61 +165,78 @@ class APIGatewayExample:
146
165
  level=getattr(logging, self.config.logging.level),
147
166
  format=self.config.logging.format,
148
167
  handlers=[
149
- logging.FileHandler(self.config.logging.file_path) if self.config.logging.file_path else logging.NullHandler(),
150
- logging.StreamHandler() if self.config.logging.console_output else logging.NullHandler()
151
- ]
168
+ (
169
+ logging.FileHandler(self.config.logging.file_path)
170
+ if self.config.logging.file_path
171
+ else logging.NullHandler()
172
+ ),
173
+ (
174
+ logging.StreamHandler()
175
+ if self.config.logging.console_output
176
+ else logging.NullHandler()
177
+ ),
178
+ ],
152
179
  )
153
-
180
+
154
181
  def _setup_routing_rules(self):
155
182
  """Setup routing rules for different services."""
156
183
  self.routing_rules = {
157
184
  "/api/v1/users": {
158
185
  "service": "user-service",
159
- "endpoints": ["https://user-service-1:8080", "https://user-service-2:8080"],
186
+ "endpoints": [
187
+ "https://user-service-1:8080",
188
+ "https://user-service-2:8080",
189
+ ],
160
190
  "timeout": 30,
161
191
  "retries": 3,
162
- "required_permissions": ["read", "user"]
192
+ "required_permissions": ["read", "user"],
163
193
  },
164
194
  "/api/v1/orders": {
165
195
  "service": "order-service",
166
- "endpoints": ["https://order-service-1:8081", "https://order-service-2:8081"],
196
+ "endpoints": [
197
+ "https://order-service-1:8081",
198
+ "https://order-service-2:8081",
199
+ ],
167
200
  "timeout": 60,
168
201
  "retries": 3,
169
- "required_permissions": ["read", "write", "order"]
202
+ "required_permissions": ["read", "write", "order"],
170
203
  },
171
204
  "/api/v1/payments": {
172
205
  "service": "payment-service",
173
- "endpoints": ["https://payment-service-1:8082", "https://payment-service-2:8082"],
206
+ "endpoints": [
207
+ "https://payment-service-1:8082",
208
+ "https://payment-service-2:8082",
209
+ ],
174
210
  "timeout": 45,
175
211
  "retries": 2,
176
- "required_permissions": ["write", "payment"]
212
+ "required_permissions": ["write", "payment"],
177
213
  },
178
214
  "/api/v1/admin": {
179
215
  "service": "admin-service",
180
216
  "endpoints": ["https://admin-service:8083"],
181
217
  "timeout": 30,
182
218
  "retries": 1,
183
- "required_permissions": ["admin"]
184
- }
219
+ "required_permissions": ["admin"],
220
+ },
185
221
  }
186
-
222
+
187
223
  def _setup_load_balancer(self):
188
224
  """Setup load balancer for service endpoints."""
189
225
  self.load_balancer = {
190
226
  "algorithm": "round_robin", # round_robin, least_connections, weighted
191
227
  "health_check_interval": 30,
192
228
  "health_check_timeout": 5,
193
- "max_failures": 3
229
+ "max_failures": 3,
194
230
  }
195
231
  self.current_endpoint_index = {} # Track current endpoint for round-robin
196
-
232
+
197
233
  async def route_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
198
234
  """
199
235
  Route request through API Gateway with security validation.
200
-
236
+
201
237
  Args:
202
238
  request_data: Request data including path, method, headers, body
203
-
239
+
204
240
  Returns:
205
241
  Dict[str, Any]: Response data
206
242
  """
@@ -212,16 +248,16 @@ class APIGatewayExample:
212
248
  body = request_data.get("body", {})
213
249
  client_ip = request_data.get("client_ip", DEFAULT_CLIENT_IP)
214
250
  request_id = request_data.get("request_id", self._generate_request_id())
215
-
251
+
216
252
  # Step 1: Rate limiting check
217
253
  if not self.check_rate_limit(client_ip):
218
254
  return self._create_error_response(
219
255
  "Rate limit exceeded",
220
256
  ErrorCodes.RATE_LIMIT_EXCEEDED_ERROR,
221
257
  HTTP_TOO_MANY_REQUESTS,
222
- request_id
258
+ request_id,
223
259
  )
224
-
260
+
225
261
  # Step 2: Authentication
226
262
  auth_result = self.authenticate_request(headers)
227
263
  if not auth_result.is_valid:
@@ -230,82 +266,88 @@ class APIGatewayExample:
230
266
  auth_result.error_code,
231
267
  HTTP_UNAUTHORIZED,
232
268
  request_id,
233
- auth_result.error_message
269
+ auth_result.error_message,
234
270
  )
235
-
271
+
236
272
  # Step 3: Find routing rule
237
273
  routing_rule = self._find_routing_rule(path)
238
274
  if not routing_rule:
239
275
  return self._create_error_response(
240
- "Service not found",
241
- ErrorCodes.GENERAL_ERROR,
242
- 404,
243
- request_id
276
+ "Service not found", ErrorCodes.GENERAL_ERROR, 404, request_id
244
277
  )
245
-
278
+
246
279
  # Step 4: Authorization check
247
- if not self.check_permissions(auth_result.roles, routing_rule["required_permissions"]):
280
+ if not self.check_permissions(
281
+ auth_result.roles, routing_rule["required_permissions"]
282
+ ):
248
283
  return self._create_error_response(
249
284
  "Insufficient permissions",
250
285
  ErrorCodes.PERMISSION_DENIED_ERROR,
251
286
  HTTP_FORBIDDEN,
252
- request_id
287
+ request_id,
253
288
  )
254
-
289
+
255
290
  # Step 5: Route request to backend service
256
291
  response = await self._forward_request(
257
292
  routing_rule, method, path, headers, body, request_id
258
293
  )
259
-
294
+
260
295
  # Step 6: Log security event
261
- self._log_security_event("request_routed", {
262
- "username": auth_result.username,
263
- "path": path,
264
- "method": method,
265
- "service": routing_rule["service"],
266
- "success": True,
267
- "request_id": request_id,
268
- "timestamp": datetime.now(timezone.utc).isoformat()
269
- })
270
-
296
+ self._log_security_event(
297
+ "request_routed",
298
+ {
299
+ "username": auth_result.username,
300
+ "path": path,
301
+ "method": method,
302
+ "service": routing_rule["service"],
303
+ "success": True,
304
+ "request_id": request_id,
305
+ "timestamp": datetime.now(timezone.utc).isoformat(),
306
+ },
307
+ )
308
+
271
309
  return response
272
-
310
+
273
311
  except Exception as e:
274
312
  self.logger.error(f"Request routing failed: {str(e)}")
275
313
  return self._create_error_response(
276
314
  "Internal server error",
277
315
  ErrorCodes.GENERAL_ERROR,
278
316
  500,
279
- request_id if 'request_id' in locals() else self._generate_request_id()
317
+ request_id if "request_id" in locals() else self._generate_request_id(),
280
318
  )
281
-
319
+
282
320
  def authenticate_request(self, headers: Dict[str, str]) -> AuthResult:
283
321
  """
284
322
  Authenticate request using headers.
285
-
323
+
286
324
  Args:
287
325
  headers: Request headers
288
-
326
+
289
327
  Returns:
290
328
  AuthResult: Authentication result
291
329
  """
292
330
  try:
293
331
  # Try API key authentication
294
- api_key = headers.get("X-API-Key") or headers.get("Authorization", "").replace("Bearer ", "")
332
+ api_key = headers.get("X-API-Key") or headers.get(
333
+ "Authorization", ""
334
+ ).replace("Bearer ", "")
295
335
  if api_key:
296
336
  return self.security_manager.auth_manager.authenticate_api_key(api_key)
297
-
337
+
298
338
  # Try JWT authentication
299
339
  auth_header = headers.get("Authorization", "")
300
340
  if auth_header.startswith("Bearer "):
301
341
  token = auth_header[7:]
302
342
  return self.security_manager.auth_manager.authenticate_jwt_token(token)
303
-
343
+
304
344
  # Try certificate authentication (would be handled at TLS level)
305
345
  client_cert = headers.get("X-Client-Cert")
306
346
  if client_cert:
307
- return self.security_manager.auth_manager.authenticate_certificate(client_cert)
308
-
347
+ return self.security_manager.auth_manager.authenticate_certificate(
348
+ client_cert
349
+ )
350
+
309
351
  return AuthResult(
310
352
  is_valid=False,
311
353
  status=AuthStatus.FAILED,
@@ -313,9 +355,9 @@ class APIGatewayExample:
313
355
  roles=[],
314
356
  auth_method=None,
315
357
  error_code=ErrorCodes.AUTHENTICATION_ERROR,
316
- error_message="No valid authentication credentials found"
358
+ error_message="No valid authentication credentials found",
317
359
  )
318
-
360
+
319
361
  except Exception as e:
320
362
  self.logger.error(f"Authentication failed: {str(e)}")
321
363
  return AuthResult(
@@ -325,17 +367,19 @@ class APIGatewayExample:
325
367
  roles=[],
326
368
  auth_method=None,
327
369
  error_code=ErrorCodes.AUTHENTICATION_ERROR,
328
- error_message=str(e)
370
+ error_message=str(e),
329
371
  )
330
-
331
- def check_permissions(self, user_roles: List[str], required_permissions: List[str]) -> bool:
372
+
373
+ def check_permissions(
374
+ self, user_roles: List[str], required_permissions: List[str]
375
+ ) -> bool:
332
376
  """
333
377
  Check if user has required permissions.
334
-
378
+
335
379
  Args:
336
380
  user_roles: User roles
337
381
  required_permissions: Required permissions
338
-
382
+
339
383
  Returns:
340
384
  bool: True if user has required permissions
341
385
  """
@@ -346,14 +390,14 @@ class APIGatewayExample:
346
390
  except Exception as e:
347
391
  self.logger.error(f"Permission check failed: {str(e)}")
348
392
  return False
349
-
393
+
350
394
  def check_rate_limit(self, identifier: str) -> bool:
351
395
  """
352
396
  Check if request is within rate limits.
353
-
397
+
354
398
  Args:
355
399
  identifier: Request identifier (IP, user ID, etc.)
356
-
400
+
357
401
  Returns:
358
402
  bool: True if request is within rate limits
359
403
  """
@@ -362,14 +406,14 @@ class APIGatewayExample:
362
406
  except Exception as e:
363
407
  self.logger.error(f"Rate limit check failed: {str(e)}")
364
408
  return True # Allow request if rate limiting fails
365
-
409
+
366
410
  def _find_routing_rule(self, path: str) -> Optional[Dict[str, Any]]:
367
411
  """
368
412
  Find routing rule for the given path.
369
-
413
+
370
414
  Args:
371
415
  path: Request path
372
-
416
+
373
417
  Returns:
374
418
  Optional[Dict[str, Any]]: Routing rule or None
375
419
  """
@@ -377,12 +421,19 @@ class APIGatewayExample:
377
421
  if path.startswith(route_path):
378
422
  return rule
379
423
  return None
380
-
381
- async def _forward_request(self, routing_rule: Dict[str, Any], method: str, path: str,
382
- headers: Dict[str, str], body: Dict[str, Any], request_id: str) -> Dict[str, Any]:
424
+
425
+ async def _forward_request(
426
+ self,
427
+ routing_rule: Dict[str, Any],
428
+ method: str,
429
+ path: str,
430
+ headers: Dict[str, str],
431
+ body: Dict[str, Any],
432
+ request_id: str,
433
+ ) -> Dict[str, Any]:
383
434
  """
384
435
  Forward request to backend service.
385
-
436
+
386
437
  Args:
387
438
  routing_rule: Routing rule for the service
388
439
  method: HTTP method
@@ -390,136 +441,150 @@ class APIGatewayExample:
390
441
  headers: Request headers
391
442
  body: Request body
392
443
  request_id: Request ID
393
-
444
+
394
445
  Returns:
395
446
  Dict[str, Any]: Response from backend service
396
447
  """
397
448
  try:
398
449
  # Select endpoint using load balancer
399
- endpoint = self._select_endpoint(routing_rule["service"], routing_rule["endpoints"])
400
-
450
+ endpoint = self._select_endpoint(
451
+ routing_rule["service"], routing_rule["endpoints"]
452
+ )
453
+
401
454
  # Prepare headers for backend service
402
455
  backend_headers = {
403
456
  **headers,
404
457
  "X-Request-ID": request_id,
405
458
  "X-Gateway": "true",
406
459
  "X-Forwarded-For": headers.get("X-Forwarded-For", DEFAULT_CLIENT_IP),
407
- "X-Original-Path": path
460
+ "X-Original-Path": path,
408
461
  }
409
-
462
+
410
463
  # Create SSL context if needed
411
464
  ssl_context = None
412
465
  if self.config.ssl.enabled:
413
466
  ssl_context = self.security_manager.ssl_manager.create_client_context()
414
-
467
+
415
468
  # Make request to backend service
416
469
  timeout = aiohttp.ClientTimeout(total=routing_rule["timeout"])
417
-
470
+
418
471
  async with aiohttp.ClientSession(timeout=timeout) as session:
419
472
  url = f"{endpoint}{path}"
420
-
473
+
421
474
  if method.upper() == "GET":
422
- async with session.get(url, headers=backend_headers, ssl=ssl_context) as response:
475
+ async with session.get(
476
+ url, headers=backend_headers, ssl=ssl_context
477
+ ) as response:
423
478
  return await self._process_response(response, request_id)
424
479
  elif method.upper() == "POST":
425
- async with session.post(url, headers=backend_headers, json=body, ssl=ssl_context) as response:
480
+ async with session.post(
481
+ url, headers=backend_headers, json=body, ssl=ssl_context
482
+ ) as response:
426
483
  return await self._process_response(response, request_id)
427
484
  elif method.upper() == "PUT":
428
- async with session.put(url, headers=backend_headers, json=body, ssl=ssl_context) as response:
485
+ async with session.put(
486
+ url, headers=backend_headers, json=body, ssl=ssl_context
487
+ ) as response:
429
488
  return await self._process_response(response, request_id)
430
489
  elif method.upper() == "DELETE":
431
- async with session.delete(url, headers=backend_headers, ssl=ssl_context) as response:
490
+ async with session.delete(
491
+ url, headers=backend_headers, ssl=ssl_context
492
+ ) as response:
432
493
  return await self._process_response(response, request_id)
433
494
  else:
434
495
  raise ValueError(f"Unsupported HTTP method: {method}")
435
-
496
+
436
497
  except Exception as e:
437
498
  self.logger.error(f"Request forwarding failed: {str(e)}")
438
499
  return self._create_error_response(
439
- "Backend service unavailable",
440
- ErrorCodes.GENERAL_ERROR,
441
- 503,
442
- request_id
500
+ "Backend service unavailable", ErrorCodes.GENERAL_ERROR, 503, request_id
443
501
  )
444
-
502
+
445
503
  def _select_endpoint(self, service: str, endpoints: List[str]) -> str:
446
504
  """
447
505
  Select endpoint using load balancer algorithm.
448
-
506
+
449
507
  Args:
450
508
  service: Service name
451
509
  endpoints: List of available endpoints
452
-
510
+
453
511
  Returns:
454
512
  str: Selected endpoint
455
513
  """
456
514
  if not endpoints:
457
515
  raise ValueError(f"No endpoints available for service: {service}")
458
-
516
+
459
517
  if self.load_balancer["algorithm"] == "round_robin":
460
518
  # Simple round-robin implementation
461
519
  if service not in self.current_endpoint_index:
462
520
  self.current_endpoint_index[service] = 0
463
-
521
+
464
522
  endpoint = endpoints[self.current_endpoint_index[service]]
465
- self.current_endpoint_index[service] = (self.current_endpoint_index[service] + 1) % len(endpoints)
523
+ self.current_endpoint_index[service] = (
524
+ self.current_endpoint_index[service] + 1
525
+ ) % len(endpoints)
466
526
  return endpoint
467
527
  else:
468
528
  # Default to first endpoint
469
529
  return endpoints[0]
470
-
471
- async def _process_response(self, response: aiohttp.ClientResponse, request_id: str) -> Dict[str, Any]:
530
+
531
+ async def _process_response(
532
+ self, response: aiohttp.ClientResponse, request_id: str
533
+ ) -> Dict[str, Any]:
472
534
  """
473
535
  Process response from backend service.
474
-
536
+
475
537
  Args:
476
538
  response: Response from backend service
477
539
  request_id: Request ID
478
-
540
+
479
541
  Returns:
480
542
  Dict[str, Any]: Processed response
481
543
  """
482
544
  try:
483
545
  response_data = await response.json()
484
-
546
+
485
547
  # Add gateway headers
486
548
  response_headers = {
487
549
  "X-Gateway": "true",
488
550
  "X-Request-ID": request_id,
489
551
  "X-Response-Time": str(response.headers.get("X-Response-Time", "")),
490
- **DEFAULT_SECURITY_HEADERS
552
+ **DEFAULT_SECURITY_HEADERS,
491
553
  }
492
-
554
+
493
555
  return {
494
556
  "success": True,
495
557
  "status_code": response.status,
496
558
  "headers": response_headers,
497
559
  "data": response_data,
498
560
  "request_id": request_id,
499
- "timestamp": datetime.now(timezone.utc).isoformat()
561
+ "timestamp": datetime.now(timezone.utc).isoformat(),
500
562
  }
501
-
563
+
502
564
  except Exception as e:
503
565
  self.logger.error(f"Response processing failed: {str(e)}")
504
566
  return self._create_error_response(
505
- "Response processing failed",
506
- ErrorCodes.GENERAL_ERROR,
507
- 500,
508
- request_id
567
+ "Response processing failed", ErrorCodes.GENERAL_ERROR, 500, request_id
509
568
  )
510
-
511
- def _create_error_response(self, message: str, error_code: int, status_code: int,
512
- request_id: str, details: Optional[str] = None) -> Dict[str, Any]:
569
+
570
+ def _create_error_response(
571
+ self,
572
+ message: str,
573
+ error_code: int,
574
+ status_code: int,
575
+ request_id: str,
576
+ details: Optional[str] = None,
577
+ ) -> Dict[str, Any]:
513
578
  """
514
579
  Create error response.
515
-
580
+
516
581
  Args:
517
582
  message: Error message
518
583
  error_code: Error code
519
584
  status_code: HTTP status code
520
585
  request_id: Request ID
521
586
  details: Additional error details
522
-
587
+
523
588
  Returns:
524
589
  Dict[str, Any]: Error response
525
590
  """
@@ -530,18 +595,19 @@ class APIGatewayExample:
530
595
  "status_code": status_code,
531
596
  "request_id": request_id,
532
597
  "details": details,
533
- "timestamp": datetime.now(timezone.utc).isoformat()
598
+ "timestamp": datetime.now(timezone.utc).isoformat(),
534
599
  }
535
-
600
+
536
601
  def _generate_request_id(self) -> str:
537
602
  """Generate unique request ID for tracing."""
538
603
  import uuid
604
+
539
605
  return str(uuid.uuid4())
540
-
606
+
541
607
  def _log_security_event(self, event_type: str, details: Dict[str, Any]):
542
608
  """
543
609
  Log security event.
544
-
610
+
545
611
  Args:
546
612
  event_type: Type of security event
547
613
  details: Event details
@@ -559,39 +625,38 @@ class APIGatewayExample:
559
625
  "service": details.get("service"),
560
626
  "success": details.get("success"),
561
627
  "request_id": details.get("request_id"),
562
- **details
563
- }
628
+ **details,
629
+ },
564
630
  )
565
631
  except Exception as e:
566
632
  self.logger.error(f"Failed to log security event: {str(e)}")
567
-
633
+
568
634
  async def health_check(self) -> Dict[str, Any]:
569
635
  """
570
636
  Perform health check for the API Gateway.
571
-
637
+
572
638
  Returns:
573
639
  Dict[str, Any]: Health check result
574
640
  """
575
641
  try:
576
642
  # Check security manager health
577
643
  security_healthy = self.security_manager is not None
578
-
644
+
579
645
  # Check rate limiter health
580
646
  rate_limit_healthy = self.security_manager.rate_limiter is not None
581
-
647
+
582
648
  # Check routing rules
583
649
  routing_healthy = len(self.routing_rules) > 0
584
-
650
+
585
651
  # Check SSL configuration
586
- ssl_healthy = self.config.ssl.enabled and os.path.exists(self.config.ssl.cert_file)
587
-
588
- overall_healthy = all([
589
- security_healthy,
590
- rate_limit_healthy,
591
- routing_healthy,
592
- ssl_healthy
593
- ])
594
-
652
+ ssl_healthy = self.config.ssl.enabled and os.path.exists(
653
+ self.config.ssl.cert_file
654
+ )
655
+
656
+ overall_healthy = all(
657
+ [security_healthy, rate_limit_healthy, routing_healthy, ssl_healthy]
658
+ )
659
+
595
660
  return {
596
661
  "status": "healthy" if overall_healthy else "unhealthy",
597
662
  "gateway": "api-gateway",
@@ -600,8 +665,8 @@ class APIGatewayExample:
600
665
  "security_manager": security_healthy,
601
666
  "rate_limiter": rate_limit_healthy,
602
667
  "routing_rules": routing_healthy,
603
- "ssl_configuration": ssl_healthy
604
- }
668
+ "ssl_configuration": ssl_healthy,
669
+ },
605
670
  }
606
671
  except Exception as e:
607
672
  self.logger.error(f"Health check failed: {str(e)}")
@@ -609,46 +674,48 @@ class APIGatewayExample:
609
674
  "status": "unhealthy",
610
675
  "gateway": "api-gateway",
611
676
  "error": str(e),
612
- "timestamp": datetime.now(timezone.utc).isoformat()
677
+ "timestamp": datetime.now(timezone.utc).isoformat(),
613
678
  }
614
-
679
+
615
680
  async def get_metrics(self) -> Dict[str, Any]:
616
681
  """
617
682
  Get API Gateway metrics.
618
-
683
+
619
684
  Returns:
620
685
  Dict[str, Any]: Metrics data
621
686
  """
622
687
  try:
623
688
  # Get rate limiter metrics
624
689
  rate_limit_stats = self.security_manager.rate_limiter.get_statistics()
625
-
690
+
626
691
  # Get routing statistics
627
692
  routing_stats = {
628
693
  "total_routes": len(self.routing_rules),
629
- "services": list(set(rule["service"] for rule in self.routing_rules.values())),
630
- "load_balancer_algorithm": self.load_balancer["algorithm"]
694
+ "services": list(
695
+ set(rule["service"] for rule in self.routing_rules.values())
696
+ ),
697
+ "load_balancer_algorithm": self.load_balancer["algorithm"],
631
698
  }
632
-
699
+
633
700
  return {
634
701
  "gateway": "api-gateway",
635
702
  "timestamp": datetime.now(timezone.utc).isoformat(),
636
703
  "rate_limiting": rate_limit_stats,
637
704
  "routing": routing_stats,
638
- "security": self.get_security_status()
705
+ "security": self.get_security_status(),
639
706
  }
640
707
  except Exception as e:
641
708
  self.logger.error(f"Failed to get metrics: {str(e)}")
642
709
  return {
643
710
  "gateway": "api-gateway",
644
711
  "error": str(e),
645
- "timestamp": datetime.now(timezone.utc).isoformat()
712
+ "timestamp": datetime.now(timezone.utc).isoformat(),
646
713
  }
647
-
714
+
648
715
  def get_security_status(self) -> Dict[str, Any]:
649
716
  """
650
717
  Get security framework status.
651
-
718
+
652
719
  Returns:
653
720
  Dict[str, Any]: Security status information
654
721
  """
@@ -660,103 +727,103 @@ class APIGatewayExample:
660
727
  "permissions_enabled": self.config.permissions.enabled,
661
728
  "logging_enabled": self.config.logging.enabled,
662
729
  "auth_methods": self.config.auth.methods,
663
- "timestamp": datetime.now(timezone.utc).isoformat()
730
+ "timestamp": datetime.now(timezone.utc).isoformat(),
664
731
  }
665
732
 
666
733
 
667
734
  # Example usage and testing
668
735
  class APIGatewayExampleTest:
669
736
  """Test class for API Gateway example functionality."""
670
-
737
+
671
738
  @staticmethod
672
739
  async def test_authentication():
673
740
  """Test authentication functionality."""
674
741
  gateway = APIGatewayExample()
675
-
742
+
676
743
  # Test API key authentication
677
744
  headers = {"X-API-Key": "gateway_key_123"}
678
745
  auth_result = gateway.authenticate_request(headers)
679
746
  assert auth_result.is_valid
680
747
  assert auth_result.username == "gateway-admin"
681
748
  assert "gateway" in auth_result.roles
682
-
749
+
683
750
  print("✅ API Key authentication test passed")
684
-
751
+
685
752
  @staticmethod
686
753
  async def test_permissions():
687
754
  """Test permission checking."""
688
755
  gateway = APIGatewayExample()
689
-
756
+
690
757
  # Test gateway permissions
691
758
  gateway_roles = ["gateway"]
692
759
  client_roles = ["client"]
693
760
  admin_roles = ["admin"]
694
-
761
+
695
762
  # Gateway should have gateway permissions
696
763
  assert gateway.check_permissions(gateway_roles, ["read", "write"])
697
-
764
+
698
765
  # Client should have client permissions
699
766
  assert gateway.check_permissions(client_roles, ["read"])
700
-
767
+
701
768
  # Admin should have all permissions
702
769
  assert gateway.check_permissions(admin_roles, ["read", "write", "delete"])
703
-
770
+
704
771
  print("✅ Permission checking test passed")
705
-
772
+
706
773
  @staticmethod
707
774
  async def test_rate_limiting():
708
775
  """Test rate limiting functionality."""
709
776
  gateway = APIGatewayExample()
710
-
777
+
711
778
  # Test rate limiting
712
779
  identifier = "test_client"
713
780
  for i in range(5):
714
781
  is_allowed = gateway.check_rate_limit(identifier)
715
782
  print(f"Request {i+1}: {'Allowed' if is_allowed else 'Blocked'}")
716
-
783
+
717
784
  print("✅ Rate limiting test completed")
718
-
785
+
719
786
  @staticmethod
720
787
  async def test_routing():
721
788
  """Test routing functionality."""
722
789
  gateway = APIGatewayExample()
723
-
790
+
724
791
  # Test routing rule finding
725
792
  rule = gateway._find_routing_rule("/api/v1/users")
726
793
  assert rule is not None
727
794
  assert rule["service"] == "user-service"
728
-
795
+
729
796
  rule = gateway._find_routing_rule("/api/v1/orders")
730
797
  assert rule is not None
731
798
  assert rule["service"] == "order-service"
732
-
799
+
733
800
  rule = gateway._find_routing_rule("/unknown/path")
734
801
  assert rule is None
735
-
802
+
736
803
  print("✅ Routing test passed")
737
-
804
+
738
805
  @staticmethod
739
806
  async def test_health_check():
740
807
  """Test health check functionality."""
741
808
  gateway = APIGatewayExample()
742
-
809
+
743
810
  health = await gateway.health_check()
744
811
  assert "status" in health
745
812
  assert "gateway" in health
746
813
  assert health["gateway"] == "api-gateway"
747
-
814
+
748
815
  print("✅ Health check test passed")
749
-
816
+
750
817
  @staticmethod
751
818
  async def test_metrics():
752
819
  """Test metrics functionality."""
753
820
  gateway = APIGatewayExample()
754
-
821
+
755
822
  metrics = await gateway.get_metrics()
756
823
  assert "gateway" in metrics
757
824
  assert "timestamp" in metrics
758
825
  assert metrics["gateway"] == "api-gateway"
759
-
826
+
760
827
  print("✅ Metrics test passed")
761
828
 
762
829
 
@@ -770,30 +837,27 @@ async def main():
770
837
  await APIGatewayExampleTest.test_routing()
771
838
  await APIGatewayExampleTest.test_health_check()
772
839
  await APIGatewayExampleTest.test_metrics()
773
-
840
+
774
841
  # Example usage
775
842
  print("\nExample Usage:")
776
843
  gateway = APIGatewayExample()
777
-
844
+
778
845
  # Route a request
779
846
  request_data = {
780
847
  "path": "/api/v1/users/123",
781
848
  "method": "GET",
782
- "headers": {
783
- "X-API-Key": "client_key_456",
784
- "X-Forwarded-For": "192.168.1.100"
785
- },
849
+ "headers": {"X-API-Key": "client_key_456", "X-Forwarded-For": "192.168.1.100"},
786
850
  "body": {},
787
- "client_ip": "192.168.1.100"
851
+ "client_ip": "192.168.1.100",
788
852
  }
789
-
853
+
790
854
  result = await gateway.route_request(request_data)
791
855
  print(f"Request result: {result}")
792
-
856
+
793
857
  # Get health status
794
858
  health = await gateway.health_check()
795
859
  print(f"Health status: {health}")
796
-
860
+
797
861
  # Get metrics
798
862
  metrics = await gateway.get_metrics()
799
863
  print(f"Metrics: {metrics}")
@@ -801,16 +865,17 @@ async def main():
801
865
 
802
866
  if __name__ == "__main__":
803
867
  asyncio.run(main())
804
-
868
+
805
869
  # Start HTTP server in background for testing
806
870
  print("\nStarting API Gateway HTTP Server in background...")
807
-
871
+
872
+ import json
808
873
  import threading
809
874
  import time
875
+ from http.server import BaseHTTPRequestHandler, HTTPServer
876
+
810
877
  import requests
811
- from http.server import HTTPServer, BaseHTTPRequestHandler
812
- import json
813
-
878
+
814
879
  class GatewayHandler(BaseHTTPRequestHandler):
815
880
  def do_GET(self):
816
881
  if self.path == "/health":
@@ -819,7 +884,7 @@ if __name__ == "__main__":
819
884
  self.end_headers()
820
885
  response = {"status": "healthy", "gateway": "api-gateway"}
821
886
  self.wfile.write(json.dumps(response).encode())
822
-
887
+
823
888
  elif self.path == "/metrics":
824
889
  self.send_response(200)
825
890
  self.send_header("Content-type", "application/json")
@@ -827,10 +892,10 @@ if __name__ == "__main__":
827
892
  response = {
828
893
  "gateway": "api-gateway",
829
894
  "requests_total": 100,
830
- "requests_per_minute": 10
895
+ "requests_per_minute": 10,
831
896
  }
832
897
  self.wfile.write(json.dumps(response).encode())
833
-
898
+
834
899
  elif self.path == "/proxy":
835
900
  api_key = self.headers.get("X-API-Key")
836
901
  if not api_key:
@@ -846,50 +911,52 @@ if __name__ == "__main__":
846
911
  response = {
847
912
  "success": True,
848
913
  "message": "Request proxied successfully",
849
- "api_key": api_key
914
+ "api_key": api_key,
850
915
  }
851
916
  self.wfile.write(json.dumps(response).encode())
852
-
917
+
853
918
  else:
854
919
  self.send_response(404)
855
920
  self.end_headers()
856
-
921
+
857
922
  def log_message(self, format, *args):
858
923
  # Suppress logging
859
924
  pass
860
-
925
+
861
926
  def run_server():
862
927
  """Run the HTTP server."""
863
928
  server = HTTPServer(("0.0.0.0", 8080), GatewayHandler)
864
929
  server.serve_forever()
865
-
930
+
866
931
  # Start server in background thread
867
932
  server_thread = threading.Thread(target=run_server, daemon=True)
868
933
  server_thread.start()
869
-
934
+
870
935
  # Wait for server to start
871
936
  time.sleep(2)
872
-
937
+
873
938
  try:
874
939
  # Test server endpoints
875
940
  print("Testing API Gateway server endpoints...")
876
-
941
+
877
942
  # Test health endpoint
878
943
  response = requests.get("http://localhost:8080/health", timeout=5)
879
944
  print(f"Health endpoint: {response.status_code}")
880
-
945
+
881
946
  # Test metrics endpoint
882
947
  response = requests.get("http://localhost:8080/metrics", timeout=5)
883
948
  print(f"Metrics endpoint: {response.status_code}")
884
-
949
+
885
950
  # Test proxy endpoint with API key
886
951
  headers = {"X-API-Key": "test_key_123"}
887
- response = requests.get("http://localhost:8080/proxy", headers=headers, timeout=5)
952
+ response = requests.get(
953
+ "http://localhost:8080/proxy", headers=headers, timeout=5
954
+ )
888
955
  print(f"Proxy endpoint: {response.status_code}")
889
-
956
+
890
957
  print("✅ API Gateway server testing completed successfully")
891
-
958
+
892
959
  except requests.exceptions.RequestException as e:
893
960
  print(f"⚠️ API Gateway server testing failed: {e}")
894
-
961
+
895
962
  print("API Gateway example completed")