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