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