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
@@ -24,21 +24,34 @@ License: MIT
|
|
24
24
|
"""
|
25
25
|
|
26
26
|
import logging
|
27
|
-
from
|
27
|
+
from datetime import datetime, timezone
|
28
|
+
from typing import Any, Dict, List, Optional, Union
|
28
29
|
|
29
|
-
from ..schemas.config import
|
30
|
-
|
31
|
-
|
30
|
+
from ..schemas.config import (
|
31
|
+
AuthConfig,
|
32
|
+
CertificateConfig,
|
33
|
+
PermissionConfig,
|
34
|
+
SecurityConfig,
|
35
|
+
)
|
36
|
+
from ..schemas.models import (
|
37
|
+
AuthResult,
|
38
|
+
AuthStatus,
|
39
|
+
CertificateInfo,
|
40
|
+
CertificatePair,
|
41
|
+
ValidationResult,
|
42
|
+
ValidationStatus,
|
43
|
+
)
|
44
|
+
from ..schemas.responses import ResponseStatus, SecurityResponse
|
32
45
|
from .auth_manager import AuthManager
|
33
|
-
from .permission_manager import PermissionManager
|
34
|
-
from .ssl_manager import SSLManager
|
35
46
|
from .cert_manager import CertificateManager
|
47
|
+
from .permission_manager import PermissionManager
|
36
48
|
from .rate_limiter import RateLimiter
|
49
|
+
from .ssl_manager import SSLManager
|
37
50
|
|
38
51
|
|
39
52
|
class SecurityConfigurationError(Exception):
|
40
53
|
"""Raised when security configuration is invalid."""
|
41
|
-
|
54
|
+
|
42
55
|
def __init__(self, message: str, error_code: int = -32001):
|
43
56
|
self.message = message
|
44
57
|
self.error_code = error_code
|
@@ -47,7 +60,7 @@ class SecurityConfigurationError(Exception):
|
|
47
60
|
|
48
61
|
class SecurityValidationError(Exception):
|
49
62
|
"""Raised when security validation fails."""
|
50
|
-
|
63
|
+
|
51
64
|
def __init__(self, message: str, error_code: int = -32002):
|
52
65
|
self.message = message
|
53
66
|
self.error_code = error_code
|
@@ -57,16 +70,16 @@ class SecurityValidationError(Exception):
|
|
57
70
|
class SecurityManager:
|
58
71
|
"""
|
59
72
|
Security Manager Class
|
60
|
-
|
73
|
+
|
61
74
|
This is the main security management class that integrates all core
|
62
75
|
security components into a unified interface. It provides comprehensive
|
63
76
|
security management including authentication, authorization, SSL/TLS
|
64
77
|
management, certificate management, and rate limiting.
|
65
|
-
|
78
|
+
|
66
79
|
The SecurityManager serves as the central point for all security
|
67
80
|
operations and provides factory methods for creating framework-specific
|
68
81
|
middleware components.
|
69
|
-
|
82
|
+
|
70
83
|
Key Responsibilities:
|
71
84
|
- Initialize and manage all core security components
|
72
85
|
- Provide unified interface for security operations
|
@@ -75,7 +88,7 @@ class SecurityManager:
|
|
75
88
|
- Provide factory methods for middleware creation
|
76
89
|
- Coordinate between different security components
|
77
90
|
- Handle security event logging and monitoring
|
78
|
-
|
91
|
+
|
79
92
|
Attributes:
|
80
93
|
config (SecurityConfig): Main security configuration
|
81
94
|
logger (Logger): Logger instance for security operations
|
@@ -86,7 +99,7 @@ class SecurityManager:
|
|
86
99
|
rate_limiter (RateLimiter): Rate limiter instance
|
87
100
|
_component_status (Dict): Status of all security components
|
88
101
|
_security_events (List): Security event log
|
89
|
-
|
102
|
+
|
90
103
|
Example:
|
91
104
|
>>> config = SecurityConfig(
|
92
105
|
... auth=AuthConfig(enabled=True, methods=["api_key"]),
|
@@ -97,27 +110,27 @@ class SecurityManager:
|
|
97
110
|
... "api_key": "user_key_123",
|
98
111
|
... "required_permissions": ["read", "write"]
|
99
112
|
... })
|
100
|
-
|
113
|
+
|
101
114
|
Raises:
|
102
115
|
SecurityConfigurationError: When security configuration is invalid
|
103
116
|
SecurityValidationError: When security validation fails
|
104
117
|
AuthenticationError: When authentication fails
|
105
118
|
PermissionDeniedError: When access is denied
|
106
119
|
"""
|
107
|
-
|
120
|
+
|
108
121
|
def __init__(self, config: SecurityConfig):
|
109
122
|
"""
|
110
123
|
Initialize Security Manager.
|
111
|
-
|
124
|
+
|
112
125
|
Args:
|
113
126
|
config (SecurityConfig): Main security configuration containing
|
114
127
|
all component configurations. Must be a valid SecurityConfig
|
115
128
|
instance with proper settings for all security components.
|
116
|
-
|
129
|
+
|
117
130
|
Raises:
|
118
131
|
SecurityConfigurationError: If configuration is invalid or
|
119
132
|
required components cannot be initialized.
|
120
|
-
|
133
|
+
|
121
134
|
Example:
|
122
135
|
>>> config = SecurityConfig(
|
123
136
|
... auth=AuthConfig(enabled=True),
|
@@ -129,28 +142,29 @@ class SecurityManager:
|
|
129
142
|
self.logger = logging.getLogger(__name__)
|
130
143
|
self._component_status: Dict[str, bool] = {}
|
131
144
|
self._security_events: List[Dict[str, Any]] = []
|
132
|
-
|
145
|
+
self._start_time = datetime.now(timezone.utc)
|
146
|
+
|
133
147
|
# Initialize all core components
|
134
148
|
self._initialize_components()
|
135
|
-
|
149
|
+
|
136
150
|
# Log initialization
|
137
151
|
self.logger.info(
|
138
152
|
"Security Manager initialized successfully",
|
139
153
|
extra={
|
140
154
|
"environment": config.environment,
|
141
155
|
"version": config.version,
|
142
|
-
"debug": config.debug
|
143
|
-
}
|
156
|
+
"debug": config.debug,
|
157
|
+
},
|
144
158
|
)
|
145
|
-
|
159
|
+
|
146
160
|
def _initialize_components(self) -> None:
|
147
161
|
"""
|
148
162
|
Initialize all core security components.
|
149
|
-
|
163
|
+
|
150
164
|
This method initializes all core security components based on
|
151
165
|
the provided configuration. It handles component dependencies
|
152
166
|
and validates that all required components are properly configured.
|
153
|
-
|
167
|
+
|
154
168
|
Raises:
|
155
169
|
SecurityConfigurationError: If any component fails to initialize
|
156
170
|
or required configuration is missing.
|
@@ -159,53 +173,52 @@ class SecurityManager:
|
|
159
173
|
# Initialize PermissionManager first (needed by AuthManager)
|
160
174
|
self.permission_manager = PermissionManager(self.config.permissions)
|
161
175
|
self._component_status["permission_manager"] = True
|
162
|
-
|
176
|
+
|
163
177
|
# Initialize AuthManager (depends on PermissionManager)
|
164
178
|
self.auth_manager = AuthManager(self.config.auth, self.permission_manager)
|
165
179
|
self._component_status["auth_manager"] = True
|
166
|
-
|
180
|
+
|
167
181
|
# Initialize SSLManager
|
168
182
|
self.ssl_manager = SSLManager(self.config.ssl)
|
169
183
|
self._component_status["ssl_manager"] = True
|
170
|
-
|
184
|
+
|
171
185
|
# Initialize CertificateManager
|
172
186
|
self.cert_manager = CertificateManager(self.config.certificates)
|
173
187
|
self._component_status["cert_manager"] = True
|
174
|
-
|
188
|
+
|
175
189
|
# Initialize RateLimiter
|
176
190
|
self.rate_limiter = RateLimiter(self.config.rate_limit)
|
177
191
|
self._component_status["rate_limiter"] = True
|
178
|
-
|
192
|
+
|
179
193
|
self.logger.info("All security components initialized successfully")
|
180
|
-
|
194
|
+
|
181
195
|
except Exception as e:
|
182
196
|
self.logger.error(f"Failed to initialize security components: {str(e)}")
|
183
197
|
raise SecurityConfigurationError(
|
184
|
-
f"Failed to initialize security components: {str(e)}",
|
185
|
-
error_code=-32001
|
198
|
+
f"Failed to initialize security components: {str(e)}", error_code=-32001
|
186
199
|
)
|
187
|
-
|
200
|
+
|
188
201
|
def validate_request(self, request_data: Dict[str, Any]) -> ValidationResult:
|
189
202
|
"""
|
190
203
|
Validate a complete request for security compliance.
|
191
|
-
|
204
|
+
|
192
205
|
This method performs comprehensive security validation of a request
|
193
206
|
including authentication, authorization, rate limiting, and any
|
194
207
|
other security checks configured in the system.
|
195
|
-
|
208
|
+
|
196
209
|
Args:
|
197
210
|
request_data (Dict[str, Any]): Request data containing authentication
|
198
211
|
credentials, required permissions, and other security-related
|
199
212
|
information. Must include authentication method and credentials.
|
200
|
-
|
213
|
+
|
201
214
|
Returns:
|
202
215
|
ValidationResult: Validation result containing success status,
|
203
216
|
error messages, and validation details.
|
204
|
-
|
217
|
+
|
205
218
|
Raises:
|
206
219
|
SecurityValidationError: When request validation fails due to
|
207
220
|
security violations or invalid data.
|
208
|
-
|
221
|
+
|
209
222
|
Example:
|
210
223
|
>>> result = security_manager.validate_request({
|
211
224
|
... "api_key": "user_key_123",
|
@@ -218,26 +231,26 @@ class SecurityManager:
|
|
218
231
|
try:
|
219
232
|
# Extract authentication credentials
|
220
233
|
auth_credentials = self._extract_auth_credentials(request_data)
|
221
|
-
|
234
|
+
|
222
235
|
# Perform authentication
|
223
236
|
auth_result = self.authenticate_user(auth_credentials)
|
224
237
|
if not auth_result.is_valid:
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
238
|
+
return ValidationResult(
|
239
|
+
is_valid=False,
|
240
|
+
status=ValidationStatus.INVALID,
|
241
|
+
error_message=f"Authentication failed: {auth_result.error_message}",
|
242
|
+
error_code=auth_result.error_code,
|
243
|
+
)
|
244
|
+
|
232
245
|
# Check rate limiting
|
233
246
|
if not self._check_rate_limit(request_data):
|
234
247
|
return ValidationResult(
|
235
248
|
is_valid=False,
|
236
249
|
status=ValidationStatus.INVALID,
|
237
250
|
error_message="Rate limit exceeded",
|
238
|
-
error_code=-32003
|
251
|
+
error_code=-32003,
|
239
252
|
)
|
240
|
-
|
253
|
+
|
241
254
|
# Check permissions
|
242
255
|
required_permissions = request_data.get("required_permissions", [])
|
243
256
|
if required_permissions:
|
@@ -249,49 +262,48 @@ class SecurityManager:
|
|
249
262
|
is_valid=False,
|
250
263
|
status=ValidationStatus.INVALID,
|
251
264
|
error_message=f"Permission denied: {permission_result.error_message}",
|
252
|
-
error_code=permission_result.error_code
|
265
|
+
error_code=permission_result.error_code,
|
253
266
|
)
|
254
|
-
|
267
|
+
|
255
268
|
# Log successful validation
|
256
|
-
self._log_security_event(
|
257
|
-
"
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
is_valid=True,
|
264
|
-
status=ValidationStatus.VALID
|
269
|
+
self._log_security_event(
|
270
|
+
"request_validated",
|
271
|
+
{
|
272
|
+
"user": auth_result.username,
|
273
|
+
"roles": auth_result.roles,
|
274
|
+
"permissions": required_permissions,
|
275
|
+
},
|
265
276
|
)
|
266
|
-
|
277
|
+
|
278
|
+
return ValidationResult(is_valid=True, status=ValidationStatus.VALID)
|
279
|
+
|
267
280
|
except Exception as e:
|
268
281
|
self.logger.error(f"Request validation failed: {str(e)}")
|
269
282
|
raise SecurityValidationError(
|
270
|
-
f"Request validation failed: {str(e)}",
|
271
|
-
error_code=-32002
|
283
|
+
f"Request validation failed: {str(e)}", error_code=-32002
|
272
284
|
)
|
273
|
-
|
285
|
+
|
274
286
|
def authenticate_user(self, credentials: Dict[str, Any]) -> AuthResult:
|
275
287
|
"""
|
276
288
|
Authenticate a user with provided credentials.
|
277
|
-
|
289
|
+
|
278
290
|
This method authenticates a user using the configured authentication
|
279
291
|
methods and returns detailed authentication results including user
|
280
292
|
information and roles.
|
281
|
-
|
293
|
+
|
282
294
|
Args:
|
283
295
|
credentials (Dict[str, Any]): User credentials containing
|
284
296
|
authentication method and corresponding credentials.
|
285
297
|
Supported methods: api_key, jwt, certificate.
|
286
|
-
|
298
|
+
|
287
299
|
Returns:
|
288
300
|
AuthResult: Authentication result containing success status,
|
289
301
|
user information, roles, and error details.
|
290
|
-
|
302
|
+
|
291
303
|
Raises:
|
292
304
|
SecurityValidationError: When authentication fails or
|
293
305
|
credentials are invalid.
|
294
|
-
|
306
|
+
|
295
307
|
Example:
|
296
308
|
>>> result = security_manager.authenticate_user({
|
297
309
|
... "method": "api_key",
|
@@ -311,7 +323,7 @@ class SecurityManager:
|
|
311
323
|
status=AuthStatus.INVALID,
|
312
324
|
auth_method="api_key",
|
313
325
|
error_code=-32001,
|
314
|
-
error_message="API key is required"
|
326
|
+
error_message="API key is required",
|
315
327
|
)
|
316
328
|
return self.auth_manager.authenticate_api_key(api_key)
|
317
329
|
|
@@ -323,7 +335,7 @@ class SecurityManager:
|
|
323
335
|
status=AuthStatus.INVALID,
|
324
336
|
auth_method="jwt",
|
325
337
|
error_code=-32001,
|
326
|
-
error_message="JWT token is required"
|
338
|
+
error_message="JWT token is required",
|
327
339
|
)
|
328
340
|
return self.auth_manager.authenticate_jwt_token(token)
|
329
341
|
|
@@ -335,12 +347,14 @@ class SecurityManager:
|
|
335
347
|
status=AuthStatus.INVALID,
|
336
348
|
auth_method="certificate",
|
337
349
|
error_code=-32001,
|
338
|
-
error_message="Certificate is required"
|
350
|
+
error_message="Certificate is required",
|
339
351
|
)
|
340
352
|
return self.auth_manager.authenticate_certificate(cert_pem)
|
341
353
|
|
342
354
|
else:
|
343
|
-
raise SecurityValidationError(
|
355
|
+
raise SecurityValidationError(
|
356
|
+
f"Unsupported authentication method: {auth_method}"
|
357
|
+
)
|
344
358
|
|
345
359
|
except SecurityValidationError:
|
346
360
|
# Re-raise SecurityValidationError
|
@@ -352,24 +366,26 @@ class SecurityManager:
|
|
352
366
|
status=AuthStatus.INVALID,
|
353
367
|
auth_method="api_key", # Use default for exceptions
|
354
368
|
error_code=-32002,
|
355
|
-
error_message=f"Authentication failed: {str(e)}"
|
369
|
+
error_message=f"Authentication failed: {str(e)}",
|
356
370
|
)
|
357
|
-
|
358
|
-
def check_permissions(
|
371
|
+
|
372
|
+
def check_permissions(
|
373
|
+
self, user_roles: List[str], required_permissions: List[str]
|
374
|
+
) -> ValidationResult:
|
359
375
|
"""
|
360
376
|
Check if user has required permissions.
|
361
|
-
|
377
|
+
|
362
378
|
This method validates that the user's roles provide the required
|
363
379
|
permissions for the requested operation.
|
364
|
-
|
380
|
+
|
365
381
|
Args:
|
366
382
|
user_roles (List[str]): List of user roles to check
|
367
383
|
required_permissions (List[str]): List of required permissions
|
368
|
-
|
384
|
+
|
369
385
|
Returns:
|
370
386
|
ValidationResult: Validation result containing success status
|
371
387
|
and error details.
|
372
|
-
|
388
|
+
|
373
389
|
Example:
|
374
390
|
>>> result = security_manager.check_permissions(
|
375
391
|
... ["admin", "user"],
|
@@ -379,33 +395,35 @@ class SecurityManager:
|
|
379
395
|
... print("User has required permissions")
|
380
396
|
"""
|
381
397
|
try:
|
382
|
-
return self.permission_manager.validate_access(
|
398
|
+
return self.permission_manager.validate_access(
|
399
|
+
user_roles, required_permissions
|
400
|
+
)
|
383
401
|
except Exception as e:
|
384
402
|
self.logger.error(f"Permission check failed: {str(e)}")
|
385
403
|
return ValidationResult(
|
386
404
|
is_valid=False,
|
387
405
|
error_message=f"Permission check failed: {str(e)}",
|
388
|
-
error_code=-32004
|
406
|
+
error_code=-32004,
|
389
407
|
)
|
390
|
-
|
408
|
+
|
391
409
|
def create_certificate(self, cert_config: CertificateConfig) -> CertificatePair:
|
392
410
|
"""
|
393
411
|
Create a new certificate using the certificate manager.
|
394
|
-
|
412
|
+
|
395
413
|
This method creates a new certificate based on the provided
|
396
414
|
configuration using the certificate manager.
|
397
|
-
|
415
|
+
|
398
416
|
Args:
|
399
417
|
cert_config (CertificateConfig): Certificate configuration
|
400
418
|
specifying the type and parameters of certificate to create.
|
401
|
-
|
419
|
+
|
402
420
|
Returns:
|
403
421
|
CertificatePair: Created certificate pair containing
|
404
422
|
certificate and private key information.
|
405
|
-
|
423
|
+
|
406
424
|
Raises:
|
407
425
|
SecurityValidationError: When certificate creation fails.
|
408
|
-
|
426
|
+
|
409
427
|
Example:
|
410
428
|
>>> config = CertificateConfig(
|
411
429
|
... cert_type="client",
|
@@ -418,27 +436,26 @@ class SecurityManager:
|
|
418
436
|
except Exception as e:
|
419
437
|
self.logger.error(f"Certificate creation failed: {str(e)}")
|
420
438
|
raise SecurityValidationError(
|
421
|
-
f"Certificate creation failed: {str(e)}",
|
422
|
-
error_code=-32005
|
439
|
+
f"Certificate creation failed: {str(e)}", error_code=-32005
|
423
440
|
)
|
424
|
-
|
441
|
+
|
425
442
|
def revoke_certificate(self, serial_number: str, reason: str) -> bool:
|
426
443
|
"""
|
427
444
|
Revoke a certificate.
|
428
|
-
|
445
|
+
|
429
446
|
This method revokes a certificate with the specified serial number
|
430
447
|
and reason using the certificate manager.
|
431
|
-
|
448
|
+
|
432
449
|
Args:
|
433
450
|
serial_number (str): Certificate serial number to revoke
|
434
451
|
reason (str): Reason for revocation
|
435
|
-
|
452
|
+
|
436
453
|
Returns:
|
437
454
|
bool: True if certificate was successfully revoked
|
438
|
-
|
455
|
+
|
439
456
|
Raises:
|
440
457
|
SecurityValidationError: When certificate revocation fails.
|
441
|
-
|
458
|
+
|
442
459
|
Example:
|
443
460
|
>>> success = security_manager.revoke_certificate(
|
444
461
|
... "1234567890",
|
@@ -450,28 +467,27 @@ class SecurityManager:
|
|
450
467
|
except Exception as e:
|
451
468
|
self.logger.error(f"Certificate revocation failed: {str(e)}")
|
452
469
|
raise SecurityValidationError(
|
453
|
-
f"Certificate revocation failed: {str(e)}",
|
454
|
-
error_code=-32006
|
470
|
+
f"Certificate revocation failed: {str(e)}", error_code=-32006
|
455
471
|
)
|
456
|
-
|
472
|
+
|
457
473
|
def get_certificate_info(self, cert_path: str) -> CertificateInfo:
|
458
474
|
"""
|
459
475
|
Get information about a certificate.
|
460
|
-
|
476
|
+
|
461
477
|
This method retrieves detailed information about a certificate
|
462
478
|
using the SSL manager.
|
463
|
-
|
479
|
+
|
464
480
|
Args:
|
465
481
|
cert_path (str): Path to the certificate file
|
466
|
-
|
482
|
+
|
467
483
|
Returns:
|
468
484
|
CertificateInfo: Certificate information including subject,
|
469
485
|
issuer, validity dates, and other details.
|
470
|
-
|
486
|
+
|
471
487
|
Raises:
|
472
488
|
SecurityValidationError: When certificate information
|
473
489
|
retrieval fails.
|
474
|
-
|
490
|
+
|
475
491
|
Example:
|
476
492
|
>>> info = security_manager.get_certificate_info("server.crt")
|
477
493
|
>>> print(f"Subject: {info.subject}")
|
@@ -481,27 +497,26 @@ class SecurityManager:
|
|
481
497
|
except Exception as e:
|
482
498
|
self.logger.error(f"Certificate info retrieval failed: {str(e)}")
|
483
499
|
raise SecurityValidationError(
|
484
|
-
f"Certificate info retrieval failed: {str(e)}",
|
485
|
-
error_code=-32007
|
500
|
+
f"Certificate info retrieval failed: {str(e)}", error_code=-32007
|
486
501
|
)
|
487
|
-
|
502
|
+
|
488
503
|
def create_ssl_context(self, context_type: str = "server", **kwargs) -> Any:
|
489
504
|
"""
|
490
505
|
Create SSL context for server or client.
|
491
|
-
|
506
|
+
|
492
507
|
This method creates an SSL context for either server or client
|
493
508
|
operations using the SSL manager.
|
494
|
-
|
509
|
+
|
495
510
|
Args:
|
496
511
|
context_type (str): Type of SSL context ("server" or "client")
|
497
512
|
**kwargs: Additional SSL context parameters
|
498
|
-
|
513
|
+
|
499
514
|
Returns:
|
500
515
|
Any: SSL context object
|
501
|
-
|
516
|
+
|
502
517
|
Raises:
|
503
518
|
SecurityValidationError: When SSL context creation fails.
|
504
|
-
|
519
|
+
|
505
520
|
Example:
|
506
521
|
>>> context = security_manager.create_ssl_context(
|
507
522
|
... "server",
|
@@ -519,23 +534,22 @@ class SecurityManager:
|
|
519
534
|
except Exception as e:
|
520
535
|
self.logger.error(f"SSL context creation failed: {str(e)}")
|
521
536
|
raise SecurityValidationError(
|
522
|
-
f"SSL context creation failed: {str(e)}",
|
523
|
-
error_code=-32008
|
537
|
+
f"SSL context creation failed: {str(e)}", error_code=-32008
|
524
538
|
)
|
525
|
-
|
539
|
+
|
526
540
|
def check_rate_limit(self, identifier: str) -> bool:
|
527
541
|
"""
|
528
542
|
Check if rate limit is exceeded for the given identifier.
|
529
|
-
|
543
|
+
|
530
544
|
This method checks if the rate limit is exceeded for the specified
|
531
545
|
identifier using the rate limiter.
|
532
|
-
|
546
|
+
|
533
547
|
Args:
|
534
548
|
identifier (str): Identifier to check (e.g., IP address, user ID)
|
535
|
-
|
549
|
+
|
536
550
|
Returns:
|
537
551
|
bool: True if rate limit is not exceeded, False otherwise
|
538
|
-
|
552
|
+
|
539
553
|
Example:
|
540
554
|
>>> if security_manager.check_rate_limit("192.168.1.100"):
|
541
555
|
... print("Rate limit not exceeded")
|
@@ -545,18 +559,18 @@ class SecurityManager:
|
|
545
559
|
except Exception as e:
|
546
560
|
self.logger.error(f"Rate limit check failed: {str(e)}")
|
547
561
|
return False
|
548
|
-
|
562
|
+
|
549
563
|
def get_security_status(self) -> SecurityResponse:
|
550
564
|
"""
|
551
565
|
Get comprehensive security status information.
|
552
|
-
|
566
|
+
|
553
567
|
This method returns detailed status information about all
|
554
568
|
security components and their current state.
|
555
|
-
|
569
|
+
|
556
570
|
Returns:
|
557
571
|
SecurityResponse: Security status response containing
|
558
572
|
component status, configuration info, and health metrics.
|
559
|
-
|
573
|
+
|
560
574
|
Example:
|
561
575
|
>>> status = security_manager.get_security_status()
|
562
576
|
>>> print(f"SSL enabled: {status.ssl_enabled}")
|
@@ -571,35 +585,35 @@ class SecurityManager:
|
|
571
585
|
component_status=self._component_status,
|
572
586
|
security_events_count=len(self._security_events),
|
573
587
|
environment=self.config.environment,
|
574
|
-
version=self.config.version
|
588
|
+
version=self.config.version,
|
575
589
|
)
|
576
590
|
except Exception as e:
|
577
591
|
self.logger.error(f"Security status retrieval failed: {str(e)}")
|
578
592
|
return SecurityResponse(
|
579
593
|
status=ResponseStatus.ERROR,
|
580
|
-
message=f"Status retrieval failed: {str(e)}"
|
594
|
+
message=f"Status retrieval failed: {str(e)}",
|
581
595
|
)
|
582
|
-
|
596
|
+
|
583
597
|
def _extract_auth_credentials(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
584
598
|
"""
|
585
599
|
Extract authentication credentials from request data.
|
586
|
-
|
600
|
+
|
587
601
|
This method extracts authentication credentials from the request
|
588
602
|
data and determines the authentication method to use.
|
589
|
-
|
603
|
+
|
590
604
|
Args:
|
591
605
|
request_data (Dict[str, Any]): Request data containing
|
592
606
|
authentication information
|
593
|
-
|
607
|
+
|
594
608
|
Returns:
|
595
609
|
Dict[str, Any]: Extracted credentials with authentication method
|
596
|
-
|
610
|
+
|
597
611
|
Raises:
|
598
612
|
SecurityValidationError: When credentials cannot be extracted
|
599
613
|
or are invalid.
|
600
614
|
"""
|
601
615
|
credentials = {}
|
602
|
-
|
616
|
+
|
603
617
|
# Check for API key
|
604
618
|
if "api_key" in request_data:
|
605
619
|
credentials["method"] = "api_key"
|
@@ -623,42 +637,44 @@ class SecurityManager:
|
|
623
637
|
credentials["api_key"] = auth_header[7:]
|
624
638
|
else:
|
625
639
|
raise SecurityValidationError("No authentication credentials found")
|
626
|
-
|
640
|
+
|
627
641
|
return credentials
|
628
|
-
|
642
|
+
|
629
643
|
def _check_rate_limit(self, request_data: Dict[str, Any]) -> bool:
|
630
644
|
"""
|
631
645
|
Check rate limit for the request.
|
632
|
-
|
646
|
+
|
633
647
|
This method determines the appropriate identifier for rate limiting
|
634
648
|
and checks if the rate limit is exceeded.
|
635
|
-
|
649
|
+
|
636
650
|
Args:
|
637
651
|
request_data (Dict[str, Any]): Request data containing
|
638
652
|
client information
|
639
|
-
|
653
|
+
|
640
654
|
Returns:
|
641
655
|
bool: True if rate limit is not exceeded, False otherwise
|
642
656
|
"""
|
643
657
|
# Determine rate limit identifier
|
644
|
-
identifier =
|
645
|
-
|
658
|
+
identifier = (
|
659
|
+
request_data.get("client_ip") or request_data.get("user_id") or "global"
|
660
|
+
)
|
661
|
+
|
646
662
|
# Check rate limit
|
647
663
|
if not self.check_rate_limit(identifier):
|
648
664
|
self._log_security_event("rate_limit_exceeded", {"identifier": identifier})
|
649
665
|
return False
|
650
|
-
|
666
|
+
|
651
667
|
# Increment request count
|
652
668
|
self.rate_limiter.increment_request_count(identifier)
|
653
669
|
return True
|
654
|
-
|
670
|
+
|
655
671
|
def _log_security_event(self, event_type: str, event_data: Dict[str, Any]) -> None:
|
656
672
|
"""
|
657
673
|
Log a security event.
|
658
|
-
|
674
|
+
|
659
675
|
This method logs security events for monitoring and auditing
|
660
676
|
purposes.
|
661
|
-
|
677
|
+
|
662
678
|
Args:
|
663
679
|
event_type (str): Type of security event
|
664
680
|
event_data (Dict[str, Any]): Event data and context
|
@@ -667,110 +683,114 @@ class SecurityManager:
|
|
667
683
|
"timestamp": self._get_current_timestamp(),
|
668
684
|
"event_type": event_type,
|
669
685
|
"event_data": event_data,
|
670
|
-
"environment": self.config.environment
|
686
|
+
"environment": self.config.environment,
|
671
687
|
}
|
672
|
-
|
688
|
+
|
673
689
|
self._security_events.append(event)
|
674
690
|
self.logger.info(f"Security event: {event_type}", extra=event_data)
|
675
|
-
|
691
|
+
|
676
692
|
def _get_current_timestamp(self) -> str:
|
677
693
|
"""Get current timestamp in ISO format."""
|
678
694
|
from datetime import datetime, timezone
|
695
|
+
|
679
696
|
return datetime.now(timezone.utc).isoformat()
|
680
|
-
|
697
|
+
|
681
698
|
# Factory methods for middleware creation
|
682
699
|
def create_fastapi_middleware(self):
|
683
700
|
"""
|
684
701
|
Create FastAPI security middleware.
|
685
|
-
|
702
|
+
|
686
703
|
This method creates and returns a FastAPI-specific security middleware
|
687
704
|
instance configured with the current security settings.
|
688
|
-
|
705
|
+
|
689
706
|
Returns:
|
690
707
|
FastAPISecurityMiddleware: Configured FastAPI security middleware
|
691
|
-
|
708
|
+
|
692
709
|
Raises:
|
693
710
|
SecurityConfigurationError: If middleware creation fails
|
694
711
|
"""
|
695
712
|
try:
|
696
|
-
from mcp_security_framework.middleware.fastapi_middleware import
|
713
|
+
from mcp_security_framework.middleware.fastapi_middleware import (
|
714
|
+
FastAPISecurityMiddleware,
|
715
|
+
)
|
716
|
+
|
697
717
|
return FastAPISecurityMiddleware(self)
|
698
718
|
except ImportError as e:
|
699
719
|
raise SecurityConfigurationError(
|
700
|
-
f"Failed to import FastAPI middleware: {str(e)}",
|
701
|
-
error_code=-32007
|
720
|
+
f"Failed to import FastAPI middleware: {str(e)}", error_code=-32007
|
702
721
|
)
|
703
722
|
except Exception as e:
|
704
723
|
raise SecurityConfigurationError(
|
705
|
-
f"Failed to create FastAPI middleware: {str(e)}",
|
706
|
-
error_code=-32008
|
724
|
+
f"Failed to create FastAPI middleware: {str(e)}", error_code=-32008
|
707
725
|
)
|
708
|
-
|
726
|
+
|
709
727
|
def create_flask_middleware(self):
|
710
728
|
"""
|
711
729
|
Create Flask security middleware.
|
712
|
-
|
730
|
+
|
713
731
|
This method creates and returns a Flask-specific security middleware
|
714
732
|
instance configured with the current security settings.
|
715
|
-
|
733
|
+
|
716
734
|
Returns:
|
717
735
|
FlaskSecurityMiddleware: Configured Flask security middleware
|
718
|
-
|
736
|
+
|
719
737
|
Raises:
|
720
738
|
SecurityConfigurationError: If middleware creation fails
|
721
739
|
"""
|
722
740
|
try:
|
723
|
-
from mcp_security_framework.middleware.flask_middleware import
|
741
|
+
from mcp_security_framework.middleware.flask_middleware import (
|
742
|
+
FlaskSecurityMiddleware,
|
743
|
+
)
|
744
|
+
|
724
745
|
return FlaskSecurityMiddleware(self)
|
725
746
|
except ImportError as e:
|
726
747
|
raise SecurityConfigurationError(
|
727
|
-
f"Failed to import Flask middleware: {str(e)}",
|
728
|
-
error_code=-32009
|
748
|
+
f"Failed to import Flask middleware: {str(e)}", error_code=-32009
|
729
749
|
)
|
730
750
|
except Exception as e:
|
731
751
|
raise SecurityConfigurationError(
|
732
|
-
f"Failed to create Flask middleware: {str(e)}",
|
733
|
-
error_code=-32010
|
752
|
+
f"Failed to create Flask middleware: {str(e)}", error_code=-32010
|
734
753
|
)
|
735
|
-
|
754
|
+
|
736
755
|
def create_django_middleware(self):
|
737
756
|
"""
|
738
757
|
Create Django security middleware.
|
739
|
-
|
758
|
+
|
740
759
|
This method creates and returns a Django-specific security middleware
|
741
760
|
instance configured with the current security settings.
|
742
|
-
|
761
|
+
|
743
762
|
Returns:
|
744
763
|
DjangoSecurityMiddleware: Configured Django security middleware
|
745
|
-
|
764
|
+
|
746
765
|
Raises:
|
747
766
|
SecurityConfigurationError: If middleware creation fails
|
748
767
|
"""
|
749
768
|
try:
|
750
|
-
from mcp_security_framework.middleware.django_middleware import
|
769
|
+
from mcp_security_framework.middleware.django_middleware import (
|
770
|
+
DjangoSecurityMiddleware,
|
771
|
+
)
|
772
|
+
|
751
773
|
return DjangoSecurityMiddleware(self)
|
752
774
|
except ImportError as e:
|
753
775
|
raise SecurityConfigurationError(
|
754
|
-
f"Failed to import Django middleware: {str(e)}",
|
755
|
-
error_code=-32011
|
776
|
+
f"Failed to import Django middleware: {str(e)}", error_code=-32011
|
756
777
|
)
|
757
778
|
except Exception as e:
|
758
779
|
raise SecurityConfigurationError(
|
759
|
-
f"Failed to create Django middleware: {str(e)}",
|
760
|
-
error_code=-32012
|
780
|
+
f"Failed to create Django middleware: {str(e)}", error_code=-32012
|
761
781
|
)
|
762
782
|
|
763
783
|
def perform_security_audit(self) -> Dict[str, Any]:
|
764
784
|
"""
|
765
785
|
Perform comprehensive security audit.
|
766
|
-
|
786
|
+
|
767
787
|
This method performs a comprehensive security audit of all
|
768
788
|
security components and configurations.
|
769
|
-
|
789
|
+
|
770
790
|
Returns:
|
771
791
|
Dict[str, Any]: Audit results containing security status
|
772
792
|
and recommendations for each component.
|
773
|
-
|
793
|
+
|
774
794
|
Example:
|
775
795
|
>>> audit_result = security_manager.perform_security_audit()
|
776
796
|
>>> print(f"Authentication enabled: {audit_result['authentication']['enabled']}")
|
@@ -782,54 +802,56 @@ class SecurityManager:
|
|
782
802
|
"enabled": self.config.auth.enabled,
|
783
803
|
"methods": self.config.auth.methods,
|
784
804
|
"api_keys_count": len(self.config.auth.api_keys),
|
785
|
-
"jwt_enabled": self.config.auth.jwt_secret is not None
|
805
|
+
"jwt_enabled": self.config.auth.jwt_secret is not None,
|
786
806
|
},
|
787
807
|
"authorization": {
|
788
808
|
"enabled": self.config.permissions.enabled,
|
789
809
|
"roles_count": 4, # Default for test - admin, user, readonly, moderator
|
790
|
-
"permissions_count": 10 # Default for test
|
810
|
+
"permissions_count": 10, # Default for test
|
791
811
|
},
|
792
812
|
"rate_limiting": {
|
793
813
|
"enabled": self.config.rate_limit.enabled,
|
794
814
|
"default_requests_per_minute": self.config.rate_limit.default_requests_per_minute,
|
795
|
-
"window_seconds": self.config.rate_limit.window_size_seconds
|
815
|
+
"window_seconds": self.config.rate_limit.window_size_seconds,
|
796
816
|
},
|
797
817
|
"ssl": {
|
798
818
|
"enabled": self.config.ssl.enabled,
|
799
819
|
"min_version": self.config.ssl.min_tls_version,
|
800
|
-
"verify_mode": self.config.ssl.verify_mode
|
820
|
+
"verify_mode": self.config.ssl.verify_mode,
|
801
821
|
},
|
802
822
|
"certificates": {
|
803
823
|
"enabled": self.config.certificates.enabled,
|
804
|
-
"ca_configured": bool(
|
824
|
+
"ca_configured": bool(
|
825
|
+
self.config.certificates.ca_cert_path
|
826
|
+
and self.config.certificates.ca_key_path
|
827
|
+
),
|
805
828
|
},
|
806
829
|
"logging": {
|
807
830
|
"level": self.config.logging.level,
|
808
|
-
"format": self.config.logging.format
|
809
|
-
}
|
831
|
+
"format": self.config.logging.format,
|
832
|
+
},
|
810
833
|
}
|
811
|
-
|
834
|
+
|
812
835
|
self.logger.info("Security audit completed successfully")
|
813
836
|
return audit_result
|
814
|
-
|
837
|
+
|
815
838
|
except Exception as e:
|
816
839
|
self.logger.error(f"Security audit failed: {str(e)}")
|
817
840
|
raise SecurityValidationError(
|
818
|
-
f"Security audit failed: {str(e)}",
|
819
|
-
error_code=-32013
|
841
|
+
f"Security audit failed: {str(e)}", error_code=-32013
|
820
842
|
)
|
821
843
|
|
822
844
|
def validate_configuration(self) -> ValidationResult:
|
823
845
|
"""
|
824
846
|
Validate security configuration.
|
825
|
-
|
847
|
+
|
826
848
|
This method validates the complete security configuration
|
827
849
|
and returns detailed validation results.
|
828
|
-
|
850
|
+
|
829
851
|
Returns:
|
830
852
|
ValidationResult: Validation result containing success status
|
831
853
|
and detailed error information.
|
832
|
-
|
854
|
+
|
833
855
|
Example:
|
834
856
|
>>> result = security_manager.validate_configuration()
|
835
857
|
>>> if result.is_valid:
|
@@ -839,94 +861,129 @@ class SecurityManager:
|
|
839
861
|
"""
|
840
862
|
try:
|
841
863
|
errors = []
|
842
|
-
|
864
|
+
|
843
865
|
# Validate authentication configuration
|
844
866
|
if self.config.auth.enabled:
|
845
867
|
if not self.config.auth.methods:
|
846
868
|
errors.append("Authentication enabled but no methods specified")
|
847
|
-
|
848
|
-
if
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
errors.append(
|
853
|
-
|
869
|
+
|
870
|
+
if (
|
871
|
+
"api_key" in self.config.auth.methods
|
872
|
+
and not self.config.auth.api_keys
|
873
|
+
):
|
874
|
+
errors.append(
|
875
|
+
"API key authentication enabled but no API keys configured"
|
876
|
+
)
|
877
|
+
|
878
|
+
if (
|
879
|
+
"jwt" in self.config.auth.methods
|
880
|
+
and not self.config.auth.jwt_secret
|
881
|
+
):
|
882
|
+
errors.append(
|
883
|
+
"JWT authentication enabled but no JWT secret configured"
|
884
|
+
)
|
885
|
+
|
854
886
|
# Validate authorization configuration
|
855
887
|
if self.config.permissions.enabled:
|
856
|
-
if not self.config.permissions.roles and not hasattr(
|
888
|
+
if not self.config.permissions.roles and not hasattr(
|
889
|
+
self.config.permissions, "roles_file"
|
890
|
+
):
|
857
891
|
errors.append("Authorization enabled but no roles configured")
|
858
|
-
|
892
|
+
|
859
893
|
# Validate rate limiting configuration
|
860
894
|
if self.config.rate_limit.enabled:
|
861
895
|
if self.config.rate_limit.default_requests_per_minute <= 0:
|
862
|
-
errors.append(
|
896
|
+
errors.append(
|
897
|
+
"Rate limiting enabled but invalid requests per minute"
|
898
|
+
)
|
863
899
|
if self.config.rate_limit.window_size_seconds <= 0:
|
864
900
|
errors.append("Rate limiting enabled but invalid window seconds")
|
865
|
-
|
901
|
+
|
866
902
|
# Validate SSL configuration
|
867
903
|
if self.config.ssl.enabled:
|
868
904
|
if not self.config.ssl.cert_file or not self.config.ssl.key_file:
|
869
|
-
errors.append(
|
870
|
-
|
905
|
+
errors.append(
|
906
|
+
"SSL enabled but certificate or key file not specified"
|
907
|
+
)
|
908
|
+
|
871
909
|
# Validate certificate configuration
|
872
910
|
if self.config.certificates.enabled:
|
873
|
-
if
|
874
|
-
|
875
|
-
|
911
|
+
if (
|
912
|
+
not self.config.certificates.ca_cert_path
|
913
|
+
or not self.config.certificates.ca_key_path
|
914
|
+
):
|
915
|
+
errors.append(
|
916
|
+
"Certificate management enabled but CA certificate or key not specified"
|
917
|
+
)
|
918
|
+
|
876
919
|
if errors:
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
920
|
+
return ValidationResult(
|
921
|
+
is_valid=False,
|
922
|
+
status=ValidationStatus.INVALID,
|
923
|
+
error_message="; ".join(errors),
|
924
|
+
error_code=-32014,
|
925
|
+
)
|
883
926
|
else:
|
884
927
|
return ValidationResult(
|
885
928
|
is_valid=True,
|
886
929
|
status=ValidationStatus.VALID,
|
887
930
|
error_message=None,
|
888
|
-
error_code=None
|
931
|
+
error_code=None,
|
889
932
|
)
|
890
|
-
|
933
|
+
|
891
934
|
except Exception as e:
|
892
935
|
self.logger.error(f"Configuration validation failed: {str(e)}")
|
893
936
|
return ValidationResult(
|
894
937
|
is_valid=False,
|
895
938
|
status=ValidationStatus.INVALID,
|
896
939
|
error_message=f"Configuration validation failed: {str(e)}",
|
897
|
-
error_code=-32015
|
940
|
+
error_code=-32015,
|
898
941
|
)
|
899
942
|
|
900
943
|
def get_security_metrics(self) -> Dict[str, Any]:
|
901
944
|
"""
|
902
945
|
Get security metrics and statistics.
|
903
|
-
|
946
|
+
|
904
947
|
This method returns comprehensive security metrics including
|
905
948
|
authentication attempts, permission checks, and rate limiting
|
906
949
|
statistics.
|
907
|
-
|
950
|
+
|
908
951
|
Returns:
|
909
952
|
Dict[str, Any]: Security metrics and statistics
|
910
|
-
|
953
|
+
|
911
954
|
Example:
|
912
955
|
>>> metrics = security_manager.get_security_metrics()
|
913
956
|
>>> print(f"Authentication attempts: {metrics['authentication_attempts']}")
|
914
957
|
"""
|
915
958
|
try:
|
916
959
|
metrics = {
|
917
|
-
"authentication_attempts": getattr(
|
918
|
-
|
919
|
-
|
920
|
-
"
|
921
|
-
|
960
|
+
"authentication_attempts": getattr(
|
961
|
+
self.auth_manager, "_auth_attempts", 0
|
962
|
+
),
|
963
|
+
"successful_authentications": getattr(
|
964
|
+
self.auth_manager, "_successful_auths", 0
|
965
|
+
),
|
966
|
+
"failed_authentications": getattr(
|
967
|
+
self.auth_manager, "_failed_auths", 0
|
968
|
+
),
|
969
|
+
"permission_checks": getattr(
|
970
|
+
self.permission_manager, "_permission_checks", 0
|
971
|
+
),
|
972
|
+
"rate_limit_violations": getattr(
|
973
|
+
self.rate_limiter, "_rate_limit_violations", 0
|
974
|
+
),
|
922
975
|
"security_events": len(self._security_events),
|
923
|
-
"uptime_seconds": (
|
924
|
-
|
925
|
-
|
976
|
+
"uptime_seconds": (
|
977
|
+
datetime.now(timezone.utc) - self._start_time
|
978
|
+
).total_seconds(),
|
979
|
+
"active_sessions": getattr(self.auth_manager, "_active_sessions", 0),
|
980
|
+
"certificate_operations": getattr(
|
981
|
+
self.cert_manager, "_cert_operations", 0
|
982
|
+
),
|
926
983
|
}
|
927
|
-
|
984
|
+
|
928
985
|
return metrics
|
929
|
-
|
986
|
+
|
930
987
|
except Exception as e:
|
931
988
|
self.logger.error(f"Failed to get security metrics: {str(e)}")
|
932
989
|
return {
|
@@ -939,5 +996,5 @@ class SecurityManager:
|
|
939
996
|
"security_events": 0,
|
940
997
|
"uptime_seconds": 0,
|
941
998
|
"active_sessions": 0,
|
942
|
-
"certificate_operations": 0
|
999
|
+
"certificate_operations": 0,
|
943
1000
|
}
|