mcp-proxy-adapter 6.3.4__py3-none-any.whl → 6.3.6__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_proxy_adapter/__init__.py +9 -5
- mcp_proxy_adapter/__main__.py +1 -1
- mcp_proxy_adapter/api/app.py +227 -176
- mcp_proxy_adapter/api/handlers.py +68 -60
- mcp_proxy_adapter/api/middleware/__init__.py +7 -5
- mcp_proxy_adapter/api/middleware/base.py +19 -16
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +44 -34
- mcp_proxy_adapter/api/middleware/error_handling.py +57 -67
- mcp_proxy_adapter/api/middleware/factory.py +50 -52
- mcp_proxy_adapter/api/middleware/logging.py +46 -30
- mcp_proxy_adapter/api/middleware/performance.py +19 -16
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +80 -50
- mcp_proxy_adapter/api/middleware/transport_middleware.py +26 -24
- mcp_proxy_adapter/api/middleware/unified_security.py +70 -51
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +43 -34
- mcp_proxy_adapter/api/schemas.py +69 -43
- mcp_proxy_adapter/api/tool_integration.py +83 -63
- mcp_proxy_adapter/api/tools.py +60 -50
- mcp_proxy_adapter/commands/__init__.py +15 -6
- mcp_proxy_adapter/commands/auth_validation_command.py +107 -110
- mcp_proxy_adapter/commands/base.py +108 -112
- mcp_proxy_adapter/commands/builtin_commands.py +28 -18
- mcp_proxy_adapter/commands/catalog_manager.py +394 -265
- mcp_proxy_adapter/commands/cert_monitor_command.py +222 -204
- mcp_proxy_adapter/commands/certificate_management_command.py +210 -213
- mcp_proxy_adapter/commands/command_registry.py +275 -226
- mcp_proxy_adapter/commands/config_command.py +48 -33
- mcp_proxy_adapter/commands/dependency_container.py +22 -23
- mcp_proxy_adapter/commands/dependency_manager.py +65 -56
- mcp_proxy_adapter/commands/echo_command.py +15 -15
- mcp_proxy_adapter/commands/health_command.py +31 -29
- mcp_proxy_adapter/commands/help_command.py +97 -61
- mcp_proxy_adapter/commands/hooks.py +65 -49
- mcp_proxy_adapter/commands/key_management_command.py +148 -147
- mcp_proxy_adapter/commands/load_command.py +58 -40
- mcp_proxy_adapter/commands/plugins_command.py +80 -54
- mcp_proxy_adapter/commands/protocol_management_command.py +60 -48
- mcp_proxy_adapter/commands/proxy_registration_command.py +107 -115
- mcp_proxy_adapter/commands/reload_command.py +43 -37
- mcp_proxy_adapter/commands/result.py +26 -33
- mcp_proxy_adapter/commands/role_test_command.py +26 -26
- mcp_proxy_adapter/commands/roles_management_command.py +176 -173
- mcp_proxy_adapter/commands/security_command.py +134 -122
- mcp_proxy_adapter/commands/settings_command.py +47 -56
- mcp_proxy_adapter/commands/ssl_setup_command.py +109 -129
- mcp_proxy_adapter/commands/token_management_command.py +129 -158
- mcp_proxy_adapter/commands/transport_management_command.py +41 -36
- mcp_proxy_adapter/commands/unload_command.py +42 -37
- mcp_proxy_adapter/config.py +36 -35
- mcp_proxy_adapter/core/__init__.py +19 -21
- mcp_proxy_adapter/core/app_factory.py +30 -9
- mcp_proxy_adapter/core/app_runner.py +81 -64
- mcp_proxy_adapter/core/auth_validator.py +176 -182
- mcp_proxy_adapter/core/certificate_utils.py +469 -426
- mcp_proxy_adapter/core/client.py +155 -126
- mcp_proxy_adapter/core/client_manager.py +60 -54
- mcp_proxy_adapter/core/client_security.py +120 -91
- mcp_proxy_adapter/core/config_converter.py +176 -143
- mcp_proxy_adapter/core/config_validator.py +12 -4
- mcp_proxy_adapter/core/crl_utils.py +21 -7
- mcp_proxy_adapter/core/errors.py +64 -20
- mcp_proxy_adapter/core/logging.py +34 -29
- mcp_proxy_adapter/core/mtls_asgi.py +29 -25
- mcp_proxy_adapter/core/mtls_asgi_app.py +66 -54
- mcp_proxy_adapter/core/protocol_manager.py +154 -104
- mcp_proxy_adapter/core/proxy_client.py +202 -144
- mcp_proxy_adapter/core/proxy_registration.py +7 -3
- mcp_proxy_adapter/core/role_utils.py +139 -125
- mcp_proxy_adapter/core/security_adapter.py +88 -77
- mcp_proxy_adapter/core/security_factory.py +50 -44
- mcp_proxy_adapter/core/security_integration.py +72 -24
- mcp_proxy_adapter/core/server_adapter.py +68 -64
- mcp_proxy_adapter/core/server_engine.py +71 -53
- mcp_proxy_adapter/core/settings.py +68 -58
- mcp_proxy_adapter/core/ssl_utils.py +69 -56
- mcp_proxy_adapter/core/transport_manager.py +72 -60
- mcp_proxy_adapter/core/unified_config_adapter.py +201 -150
- mcp_proxy_adapter/core/utils.py +4 -2
- mcp_proxy_adapter/custom_openapi.py +107 -99
- mcp_proxy_adapter/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/commands/__init__.py +1 -1
- mcp_proxy_adapter/examples/create_certificates_simple.py +182 -71
- mcp_proxy_adapter/examples/debug_request_state.py +38 -19
- mcp_proxy_adapter/examples/debug_role_chain.py +53 -20
- mcp_proxy_adapter/examples/demo_client.py +48 -36
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +9 -2
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/full_application/__init__.py +1 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +22 -10
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +24 -17
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +16 -3
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +13 -3
- mcp_proxy_adapter/examples/full_application/main.py +27 -2
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +48 -14
- mcp_proxy_adapter/examples/generate_all_certificates.py +198 -73
- mcp_proxy_adapter/examples/generate_certificates.py +31 -16
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/generate_test_configs.py +68 -91
- mcp_proxy_adapter/examples/proxy_registration_example.py +76 -75
- mcp_proxy_adapter/examples/run_example.py +23 -5
- mcp_proxy_adapter/examples/run_full_test_suite.py +109 -71
- mcp_proxy_adapter/examples/run_proxy_server.py +22 -9
- mcp_proxy_adapter/examples/run_security_tests.py +103 -41
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +72 -36
- mcp_proxy_adapter/examples/scripts/config_generator.py +288 -187
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +185 -72
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +220 -74
- mcp_proxy_adapter/examples/security_test_client.py +196 -127
- mcp_proxy_adapter/examples/setup_test_environment.py +17 -29
- mcp_proxy_adapter/examples/test_config.py +19 -4
- mcp_proxy_adapter/examples/test_config_generator.py +23 -7
- mcp_proxy_adapter/examples/test_examples.py +84 -56
- mcp_proxy_adapter/examples/universal_client.py +119 -62
- mcp_proxy_adapter/openapi.py +108 -115
- mcp_proxy_adapter/utils/config_generator.py +429 -274
- mcp_proxy_adapter/version.py +1 -2
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-6.3.6.dist-info/RECORD +144 -0
- mcp_proxy_adapter-6.3.6.dist-info/top_level.txt +2 -0
- mcp_proxy_adapter_issue_package/demonstrate_issue.py +178 -0
- mcp_proxy_adapter-6.3.4.dist-info/RECORD +0 -143
- mcp_proxy_adapter-6.3.4.dist-info/top_level.txt +0 -1
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/entry_points.txt +0 -0
- {mcp_proxy_adapter-6.3.4.dist-info → mcp_proxy_adapter-6.3.6.dist-info}/licenses/LICENSE +0 -0
@@ -26,17 +26,25 @@ logger = logging.getLogger(__name__)
|
|
26
26
|
class CertMonitorResult:
|
27
27
|
"""
|
28
28
|
Result class for certificate monitoring operations.
|
29
|
-
|
29
|
+
|
30
30
|
Contains monitoring information and operation status.
|
31
31
|
"""
|
32
|
-
|
33
|
-
def __init__(
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
cert_path: str,
|
36
|
+
check_type: str,
|
37
|
+
status: str,
|
38
|
+
expiry_date: Optional[str] = None,
|
39
|
+
days_until_expiry: Optional[int] = None,
|
40
|
+
health_score: Optional[int] = None,
|
41
|
+
alerts: Optional[List[str]] = None,
|
42
|
+
auto_renewal_status: Optional[str] = None,
|
43
|
+
error: Optional[str] = None,
|
44
|
+
):
|
37
45
|
"""
|
38
46
|
Initialize certificate monitor result.
|
39
|
-
|
47
|
+
|
40
48
|
Args:
|
41
49
|
cert_path: Path to certificate file
|
42
50
|
check_type: Type of check performed (expiry, health, alert, auto_renewal)
|
@@ -57,11 +65,11 @@ class CertMonitorResult:
|
|
57
65
|
self.alerts = alerts or []
|
58
66
|
self.auto_renewal_status = auto_renewal_status
|
59
67
|
self.error = error
|
60
|
-
|
68
|
+
|
61
69
|
def to_dict(self) -> Dict[str, Any]:
|
62
70
|
"""
|
63
71
|
Convert to dictionary format.
|
64
|
-
|
72
|
+
|
65
73
|
Returns:
|
66
74
|
Dictionary representation
|
67
75
|
"""
|
@@ -74,9 +82,9 @@ class CertMonitorResult:
|
|
74
82
|
"health_score": self.health_score,
|
75
83
|
"alerts": self.alerts,
|
76
84
|
"auto_renewal_status": self.auto_renewal_status,
|
77
|
-
"error": self.error
|
85
|
+
"error": self.error,
|
78
86
|
}
|
79
|
-
|
87
|
+
|
80
88
|
@classmethod
|
81
89
|
def get_schema(cls) -> Dict[str, Any]:
|
82
90
|
"""
|
@@ -88,31 +96,56 @@ class CertMonitorResult:
|
|
88
96
|
return {
|
89
97
|
"type": "object",
|
90
98
|
"properties": {
|
91
|
-
"cert_path": {
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
"
|
101
|
-
|
102
|
-
|
103
|
-
|
99
|
+
"cert_path": {
|
100
|
+
"type": "string",
|
101
|
+
"description": "Path to certificate file",
|
102
|
+
},
|
103
|
+
"check_type": {
|
104
|
+
"type": "string",
|
105
|
+
"enum": ["expiry", "health", "alert", "auto_renewal"],
|
106
|
+
"description": "Type of check performed",
|
107
|
+
},
|
108
|
+
"status": {
|
109
|
+
"type": "string",
|
110
|
+
"enum": ["healthy", "warning", "critical", "error"],
|
111
|
+
"description": "Overall status",
|
112
|
+
},
|
113
|
+
"expiry_date": {
|
114
|
+
"type": "string",
|
115
|
+
"description": "Certificate expiry date",
|
116
|
+
},
|
117
|
+
"days_until_expiry": {
|
118
|
+
"type": "integer",
|
119
|
+
"description": "Days until certificate expires",
|
120
|
+
},
|
121
|
+
"health_score": {
|
122
|
+
"type": "integer",
|
123
|
+
"minimum": 0,
|
124
|
+
"maximum": 100,
|
125
|
+
"description": "Health score (0-100)",
|
126
|
+
},
|
127
|
+
"alerts": {
|
128
|
+
"type": "array",
|
129
|
+
"items": {"type": "string"},
|
130
|
+
"description": "List of alert messages",
|
131
|
+
},
|
132
|
+
"auto_renewal_status": {
|
133
|
+
"type": "string",
|
134
|
+
"description": "Auto-renewal status",
|
135
|
+
},
|
136
|
+
"error": {"type": "string", "description": "Error message if any"},
|
104
137
|
},
|
105
|
-
"required": ["cert_path", "check_type", "status"]
|
138
|
+
"required": ["cert_path", "check_type", "status"],
|
106
139
|
}
|
107
140
|
|
108
141
|
|
109
142
|
class CertMonitorCommand(Command):
|
110
143
|
"""
|
111
144
|
Command for certificate monitoring.
|
112
|
-
|
145
|
+
|
113
146
|
Provides methods for monitoring certificate expiry, health, alerts, and auto-renewal.
|
114
147
|
"""
|
115
|
-
|
148
|
+
|
116
149
|
# Command metadata
|
117
150
|
name = "cert_monitor"
|
118
151
|
version = "1.0.0"
|
@@ -122,17 +155,17 @@ class CertMonitorCommand(Command):
|
|
122
155
|
email = "team@mcp-proxy-adapter.com"
|
123
156
|
source_url = "https://github.com/mcp-proxy-adapter"
|
124
157
|
result_class = CertMonitorResult
|
125
|
-
|
158
|
+
|
126
159
|
def __init__(self):
|
127
160
|
"""Initialize certificate monitor command."""
|
128
161
|
super().__init__()
|
129
162
|
self.certificate_utils = CertificateUtils()
|
130
163
|
self.auth_validator = AuthValidator()
|
131
|
-
|
164
|
+
|
132
165
|
async def execute(self, **kwargs) -> CommandResult:
|
133
166
|
"""
|
134
167
|
Execute certificate monitor command.
|
135
|
-
|
168
|
+
|
136
169
|
Args:
|
137
170
|
**kwargs: Command parameters including:
|
138
171
|
- action: Action to perform (cert_expiry_check, cert_health_check, cert_alert_setup, cert_auto_renew)
|
@@ -141,12 +174,12 @@ class CertMonitorCommand(Command):
|
|
141
174
|
- critical_days: Days before expiry for critical status
|
142
175
|
- alert_config: Alert configuration for setup
|
143
176
|
- auto_renew_config: Auto-renewal configuration
|
144
|
-
|
177
|
+
|
145
178
|
Returns:
|
146
179
|
CommandResult with monitoring operation status
|
147
180
|
"""
|
148
181
|
action = kwargs.get("action", "cert_expiry_check")
|
149
|
-
|
182
|
+
|
150
183
|
if action == "cert_expiry_check":
|
151
184
|
cert_path = kwargs.get("cert_path")
|
152
185
|
warning_days = kwargs.get("warning_days", 30)
|
@@ -167,50 +200,50 @@ class CertMonitorCommand(Command):
|
|
167
200
|
return ErrorResult(
|
168
201
|
message=f"Unknown action: {action}. Supported actions: cert_expiry_check, cert_health_check, cert_alert_setup, cert_auto_renew"
|
169
202
|
)
|
170
|
-
|
171
|
-
async def cert_expiry_check(
|
203
|
+
|
204
|
+
async def cert_expiry_check(
|
205
|
+
self, cert_path: str, warning_days: int = 30, critical_days: int = 7
|
206
|
+
) -> CommandResult:
|
172
207
|
"""
|
173
208
|
Check certificate expiry date.
|
174
|
-
|
209
|
+
|
175
210
|
Args:
|
176
211
|
cert_path: Path to certificate file
|
177
212
|
warning_days: Days before expiry to start warning
|
178
213
|
critical_days: Days before expiry for critical status
|
179
|
-
|
214
|
+
|
180
215
|
Returns:
|
181
216
|
CommandResult with expiry check results
|
182
217
|
"""
|
183
218
|
try:
|
184
219
|
logger.info(f"Performing certificate expiry check for {cert_path}")
|
185
|
-
|
220
|
+
|
186
221
|
# Check if certificate file exists
|
187
222
|
if not os.path.exists(cert_path):
|
188
|
-
return ErrorResult(
|
189
|
-
|
190
|
-
)
|
191
|
-
|
223
|
+
return ErrorResult(message=f"Certificate file not found: {cert_path}")
|
224
|
+
|
192
225
|
# Get certificate info
|
193
226
|
cert_info = self.certificate_utils.get_certificate_info(cert_path)
|
194
227
|
if not cert_info:
|
195
|
-
return ErrorResult(
|
196
|
-
|
197
|
-
)
|
198
|
-
|
228
|
+
return ErrorResult(message="Could not read certificate information")
|
229
|
+
|
199
230
|
expiry_date = cert_info.get("expiry_date")
|
200
231
|
if not expiry_date:
|
201
232
|
return ErrorResult(
|
202
233
|
message="Could not determine certificate expiry date"
|
203
234
|
)
|
204
|
-
|
235
|
+
|
205
236
|
try:
|
206
237
|
# Calculate days until expiry
|
207
|
-
expiry_datetime = datetime.fromisoformat(
|
208
|
-
|
209
|
-
except ValueError:
|
210
|
-
return ErrorResult(
|
211
|
-
message="Invalid expiry date format"
|
238
|
+
expiry_datetime = datetime.fromisoformat(
|
239
|
+
expiry_date.replace("Z", "+00:00")
|
212
240
|
)
|
213
|
-
|
241
|
+
days_until_expiry = (
|
242
|
+
expiry_datetime - datetime.now(expiry_datetime.tzinfo)
|
243
|
+
).days
|
244
|
+
except ValueError:
|
245
|
+
return ErrorResult(message="Invalid expiry date format")
|
246
|
+
|
214
247
|
# Determine status
|
215
248
|
is_expired = days_until_expiry < 0
|
216
249
|
if is_expired:
|
@@ -221,9 +254,11 @@ class CertMonitorCommand(Command):
|
|
221
254
|
health_status = "warning"
|
222
255
|
else:
|
223
256
|
health_status = "healthy"
|
224
|
-
|
225
|
-
logger.info(
|
226
|
-
|
257
|
+
|
258
|
+
logger.info(
|
259
|
+
f"Certificate expiry check completed: {health_status} ({days_until_expiry} days)"
|
260
|
+
)
|
261
|
+
|
227
262
|
return SuccessResult(
|
228
263
|
data={
|
229
264
|
"monitor_result": {
|
@@ -232,81 +267,87 @@ class CertMonitorCommand(Command):
|
|
232
267
|
"days_until_expiry": days_until_expiry,
|
233
268
|
"expiry_date": expiry_date,
|
234
269
|
"warning_days": warning_days,
|
235
|
-
"critical_days": critical_days
|
270
|
+
"critical_days": critical_days,
|
236
271
|
}
|
237
272
|
}
|
238
273
|
)
|
239
|
-
|
274
|
+
|
240
275
|
except Exception as e:
|
241
276
|
logger.error(f"Certificate expiry check failed: {e}")
|
242
|
-
return ErrorResult(
|
243
|
-
|
244
|
-
)
|
245
|
-
|
277
|
+
return ErrorResult(message=f"Certificate expiry check failed: {str(e)}")
|
278
|
+
|
246
279
|
async def cert_health_check(self, cert_path: str) -> CommandResult:
|
247
280
|
"""
|
248
281
|
Perform comprehensive health check on certificate.
|
249
|
-
|
282
|
+
|
250
283
|
Args:
|
251
284
|
cert_path: Path to certificate file
|
252
|
-
|
285
|
+
|
253
286
|
Returns:
|
254
287
|
CommandResult with health check results
|
255
288
|
"""
|
256
289
|
try:
|
257
290
|
logger.info(f"Performing certificate health check for {cert_path}")
|
258
|
-
|
291
|
+
|
259
292
|
# Check if certificate file exists
|
260
293
|
if not os.path.exists(cert_path):
|
261
|
-
return ErrorResult(
|
262
|
-
|
263
|
-
)
|
264
|
-
|
294
|
+
return ErrorResult(message=f"Certificate file not found: {cert_path}")
|
295
|
+
|
265
296
|
# Get certificate info
|
266
297
|
cert_info = self.certificate_utils.get_certificate_info(cert_path)
|
267
298
|
if not cert_info:
|
268
|
-
return ErrorResult(
|
269
|
-
|
270
|
-
)
|
271
|
-
|
299
|
+
return ErrorResult(message="Could not read certificate information")
|
300
|
+
|
272
301
|
# Validate certificate
|
273
302
|
validation = self.auth_validator.validate_certificate(cert_path)
|
274
|
-
|
303
|
+
|
275
304
|
# Calculate health score
|
276
305
|
health_score = 100
|
277
306
|
alerts = []
|
278
|
-
|
307
|
+
|
279
308
|
# Check if certificate is valid
|
280
309
|
if not validation.is_valid:
|
281
310
|
health_score -= 50
|
282
|
-
alerts.append(
|
283
|
-
|
311
|
+
alerts.append(
|
312
|
+
f"Certificate validation failed: {validation.error_message}"
|
313
|
+
)
|
314
|
+
|
284
315
|
# Check expiry
|
285
316
|
expiry_date = cert_info.get("expiry_date")
|
286
317
|
if expiry_date:
|
287
318
|
try:
|
288
|
-
expiry_datetime = datetime.fromisoformat(
|
289
|
-
|
290
|
-
|
319
|
+
expiry_datetime = datetime.fromisoformat(
|
320
|
+
expiry_date.replace("Z", "+00:00")
|
321
|
+
)
|
322
|
+
days_until_expiry = (
|
323
|
+
expiry_datetime - datetime.now(expiry_datetime.tzinfo)
|
324
|
+
).days
|
325
|
+
|
291
326
|
if days_until_expiry < 0:
|
292
327
|
health_score -= 30
|
293
328
|
alerts.append("Certificate has expired")
|
294
329
|
elif days_until_expiry <= 7:
|
295
330
|
health_score -= 20
|
296
|
-
alerts.append(
|
331
|
+
alerts.append(
|
332
|
+
f"Certificate expires in {days_until_expiry} days"
|
333
|
+
)
|
297
334
|
elif days_until_expiry <= 30:
|
298
335
|
health_score -= 10
|
299
|
-
alerts.append(
|
336
|
+
alerts.append(
|
337
|
+
f"Certificate expires in {days_until_expiry} days"
|
338
|
+
)
|
300
339
|
except ValueError:
|
301
340
|
health_score -= 10
|
302
341
|
alerts.append("Invalid expiry date format")
|
303
|
-
|
342
|
+
|
304
343
|
# Check key strength
|
305
344
|
key_size = cert_info.get("key_size", 0)
|
306
345
|
if key_size < 2048:
|
307
346
|
health_score -= 15
|
308
|
-
alerts.append(
|
309
|
-
|
347
|
+
alerts.append(
|
348
|
+
f"Key size {key_size} bits is below recommended 2048 bits"
|
349
|
+
)
|
350
|
+
|
310
351
|
# Determine overall status
|
311
352
|
if health_score >= 80:
|
312
353
|
overall_status = "healthy"
|
@@ -314,68 +355,60 @@ class CertMonitorCommand(Command):
|
|
314
355
|
overall_status = "warning"
|
315
356
|
else:
|
316
357
|
overall_status = "critical"
|
317
|
-
|
318
|
-
logger.info(
|
319
|
-
|
358
|
+
|
359
|
+
logger.info(
|
360
|
+
f"Certificate health check completed: {overall_status} (score: {health_score})"
|
361
|
+
)
|
362
|
+
|
320
363
|
return SuccessResult(
|
321
364
|
data={
|
322
365
|
"monitor_result": {
|
323
366
|
"health_score": health_score,
|
324
367
|
"alerts": alerts,
|
325
|
-
"expiry_date": expiry_date
|
326
|
-
},
|
327
|
-
"health_checks": {
|
328
|
-
"validation": {
|
329
|
-
"passed": validation.is_valid
|
330
|
-
}
|
368
|
+
"expiry_date": expiry_date,
|
331
369
|
},
|
332
|
-
"
|
370
|
+
"health_checks": {"validation": {"passed": validation.is_valid}},
|
371
|
+
"overall_status": overall_status,
|
333
372
|
}
|
334
373
|
)
|
335
|
-
|
374
|
+
|
336
375
|
except Exception as e:
|
337
376
|
logger.error(f"Certificate health check failed: {e}")
|
338
|
-
return ErrorResult(
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
377
|
+
return ErrorResult(message=f"Certificate health check failed: {str(e)}")
|
378
|
+
|
379
|
+
async def cert_alert_setup(
|
380
|
+
self, cert_path: str, alert_config: Dict[str, Any]
|
381
|
+
) -> CommandResult:
|
343
382
|
"""
|
344
383
|
Set up certificate monitoring alerts.
|
345
|
-
|
384
|
+
|
346
385
|
Args:
|
347
386
|
cert_path: Path to certificate file
|
348
387
|
alert_config: Alert configuration dictionary
|
349
|
-
|
388
|
+
|
350
389
|
Returns:
|
351
390
|
CommandResult with alert setup status
|
352
391
|
"""
|
353
392
|
try:
|
354
393
|
logger.info(f"Setting up certificate monitoring alerts for {cert_path}")
|
355
|
-
|
394
|
+
|
356
395
|
# Check if certificate file exists
|
357
396
|
if not os.path.exists(cert_path):
|
358
|
-
return ErrorResult(
|
359
|
-
|
360
|
-
)
|
361
|
-
|
397
|
+
return ErrorResult(message=f"Certificate file not found: {cert_path}")
|
398
|
+
|
362
399
|
# Validate alert configuration
|
363
400
|
if not isinstance(alert_config, dict):
|
364
|
-
return ErrorResult(
|
365
|
-
|
366
|
-
)
|
367
|
-
|
401
|
+
return ErrorResult(message="Alert configuration must be a dictionary")
|
402
|
+
|
368
403
|
# Check if alerts are disabled
|
369
404
|
if not alert_config.get("enabled", True):
|
370
405
|
return SuccessResult(
|
371
406
|
data={
|
372
|
-
"monitor_result": {
|
373
|
-
|
374
|
-
},
|
375
|
-
"message": "Alerts disabled"
|
407
|
+
"monitor_result": {"alerts_enabled": False},
|
408
|
+
"message": "Alerts disabled",
|
376
409
|
}
|
377
410
|
)
|
378
|
-
|
411
|
+
|
379
412
|
# Validate required fields
|
380
413
|
required_fields = ["warning_days", "critical_days"]
|
381
414
|
for field in required_fields:
|
@@ -383,93 +416,85 @@ class CertMonitorCommand(Command):
|
|
383
416
|
return ErrorResult(
|
384
417
|
message=f"Missing required field in alert config: {field}"
|
385
418
|
)
|
386
|
-
|
419
|
+
|
387
420
|
if alert_config["warning_days"] <= 0 or alert_config["critical_days"] <= 0:
|
388
|
-
return ErrorResult(
|
389
|
-
|
390
|
-
)
|
391
|
-
|
421
|
+
return ErrorResult(message="Warning and critical days must be positive")
|
422
|
+
|
392
423
|
if alert_config["warning_days"] <= alert_config["critical_days"]:
|
393
424
|
return ErrorResult(
|
394
425
|
message="Warning days must be greater than critical days"
|
395
426
|
)
|
396
|
-
|
427
|
+
|
397
428
|
# Check notification channels
|
398
429
|
notification_channels = alert_config.get("notification_channels", [])
|
399
430
|
if not notification_channels:
|
400
431
|
return ErrorResult(
|
401
432
|
message="At least one notification channel must be specified"
|
402
433
|
)
|
403
|
-
|
434
|
+
|
404
435
|
# Test alert configuration
|
405
436
|
test_result = await self._test_alert_config(alert_config)
|
406
437
|
if not test_result["success"]:
|
407
438
|
return ErrorResult(
|
408
439
|
message=f"Alert configuration test failed: {test_result['error']}"
|
409
440
|
)
|
410
|
-
|
441
|
+
|
411
442
|
# Save alert configuration
|
412
443
|
config_path = "/tmp/cert_alert_config.json"
|
413
|
-
with open(config_path,
|
444
|
+
with open(config_path, "w") as f:
|
414
445
|
json.dump(alert_config, f, indent=2)
|
415
|
-
|
446
|
+
|
416
447
|
logger.info(f"Alert configuration saved to {config_path}")
|
417
|
-
|
448
|
+
|
418
449
|
return SuccessResult(
|
419
450
|
data={
|
420
|
-
"monitor_result": {
|
421
|
-
"alerts_enabled": True
|
422
|
-
},
|
451
|
+
"monitor_result": {"alerts_enabled": True},
|
423
452
|
"alert_config": alert_config,
|
424
453
|
"config_path": config_path,
|
425
454
|
"setup_date": datetime.now().isoformat(),
|
426
|
-
"message": "Alerts configured successfully"
|
455
|
+
"message": "Alerts configured successfully",
|
427
456
|
}
|
428
457
|
)
|
429
|
-
|
458
|
+
|
430
459
|
except Exception as e:
|
431
460
|
logger.error(f"Alert setup failed: {e}")
|
432
|
-
return ErrorResult(
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
461
|
+
return ErrorResult(message=f"Alert setup failed: {str(e)}")
|
462
|
+
|
463
|
+
async def cert_auto_renew(
|
464
|
+
self, cert_path: str, auto_renew_config: Dict[str, Any]
|
465
|
+
) -> CommandResult:
|
437
466
|
"""
|
438
467
|
Set up certificate auto-renewal.
|
439
|
-
|
468
|
+
|
440
469
|
Args:
|
441
470
|
cert_path: Path to certificate file
|
442
471
|
auto_renew_config: Auto-renewal configuration dictionary
|
443
|
-
|
472
|
+
|
444
473
|
Returns:
|
445
474
|
CommandResult with auto-renewal setup status
|
446
475
|
"""
|
447
476
|
try:
|
448
477
|
logger.info(f"Setting up certificate auto-renewal for {cert_path}")
|
449
|
-
|
478
|
+
|
450
479
|
# Check if certificate file exists
|
451
480
|
if not os.path.exists(cert_path):
|
452
|
-
return ErrorResult(
|
453
|
-
|
454
|
-
)
|
455
|
-
|
481
|
+
return ErrorResult(message=f"Certificate file not found: {cert_path}")
|
482
|
+
|
456
483
|
# Validate auto-renewal configuration
|
457
484
|
if not isinstance(auto_renew_config, dict):
|
458
485
|
return ErrorResult(
|
459
486
|
message="Auto-renewal configuration must be a dictionary"
|
460
487
|
)
|
461
|
-
|
488
|
+
|
462
489
|
# Check if auto-renewal is disabled
|
463
490
|
if not auto_renew_config.get("enabled", True):
|
464
491
|
return SuccessResult(
|
465
492
|
data={
|
466
|
-
"monitor_result": {
|
467
|
-
|
468
|
-
},
|
469
|
-
"message": "Auto-renewal disabled"
|
493
|
+
"monitor_result": {"auto_renewal_enabled": False},
|
494
|
+
"message": "Auto-renewal disabled",
|
470
495
|
}
|
471
496
|
)
|
472
|
-
|
497
|
+
|
473
498
|
# Validate required fields
|
474
499
|
required_fields = ["renew_before_days", "ca_cert_path", "ca_key_path"]
|
475
500
|
for field in required_fields:
|
@@ -477,91 +502,79 @@ class CertMonitorCommand(Command):
|
|
477
502
|
return ErrorResult(
|
478
503
|
message=f"Missing required field in auto-renewal config: {field}"
|
479
504
|
)
|
480
|
-
|
505
|
+
|
481
506
|
if auto_renew_config["renew_before_days"] <= 0:
|
482
|
-
return ErrorResult(
|
483
|
-
|
484
|
-
)
|
485
|
-
|
507
|
+
return ErrorResult(message="Renew before days must be positive")
|
508
|
+
|
486
509
|
# Check CA files
|
487
510
|
ca_cert_path = auto_renew_config["ca_cert_path"]
|
488
511
|
ca_key_path = auto_renew_config["ca_key_path"]
|
489
|
-
|
512
|
+
|
490
513
|
if not os.path.exists(ca_cert_path):
|
491
|
-
return ErrorResult(
|
492
|
-
|
493
|
-
)
|
494
|
-
|
514
|
+
return ErrorResult(message=f"CA certificate not found: {ca_cert_path}")
|
515
|
+
|
495
516
|
if not os.path.exists(ca_key_path):
|
496
|
-
return ErrorResult(
|
497
|
-
|
498
|
-
)
|
499
|
-
|
517
|
+
return ErrorResult(message=f"CA private key not found: {ca_key_path}")
|
518
|
+
|
500
519
|
# Check output directory
|
501
520
|
output_dir = auto_renew_config.get("output_dir")
|
502
521
|
if not output_dir:
|
503
|
-
return ErrorResult(
|
504
|
-
|
505
|
-
)
|
506
|
-
|
522
|
+
return ErrorResult(message="Output directory must be specified")
|
523
|
+
|
507
524
|
# Test renewal configuration
|
508
525
|
test_result = await self._test_renewal_config(cert_path, auto_renew_config)
|
509
526
|
if not test_result["success"]:
|
510
527
|
return ErrorResult(
|
511
528
|
message=f"Renewal configuration test failed: {test_result['error']}"
|
512
529
|
)
|
513
|
-
|
530
|
+
|
514
531
|
# Save auto-renewal configuration
|
515
532
|
config_path = "/tmp/cert_auto_renew_config.json"
|
516
|
-
with open(config_path,
|
533
|
+
with open(config_path, "w") as f:
|
517
534
|
json.dump(auto_renew_config, f, indent=2)
|
518
|
-
|
535
|
+
|
519
536
|
logger.info(f"Auto-renewal configuration saved to {config_path}")
|
520
|
-
|
537
|
+
|
521
538
|
return SuccessResult(
|
522
539
|
data={
|
523
|
-
"monitor_result": {
|
524
|
-
"auto_renewal_enabled": True
|
525
|
-
},
|
540
|
+
"monitor_result": {"auto_renewal_enabled": True},
|
526
541
|
"auto_renew_config": auto_renew_config,
|
527
542
|
"config_path": config_path,
|
528
543
|
"setup_date": datetime.now().isoformat(),
|
529
|
-
"message": "Auto-renewal configured successfully"
|
544
|
+
"message": "Auto-renewal configured successfully",
|
530
545
|
}
|
531
546
|
)
|
532
|
-
|
547
|
+
|
533
548
|
except Exception as e:
|
534
549
|
logger.error(f"Auto-renewal setup failed: {e}")
|
535
|
-
return ErrorResult(
|
536
|
-
|
537
|
-
)
|
538
|
-
|
550
|
+
return ErrorResult(message=f"Auto-renewal setup failed: {str(e)}")
|
551
|
+
|
539
552
|
def _find_certificates(self, directory: str) -> List[str]:
|
540
553
|
"""
|
541
554
|
Find all certificate files in a directory.
|
542
|
-
|
555
|
+
|
543
556
|
Args:
|
544
557
|
directory: Directory to scan
|
545
|
-
|
558
|
+
|
546
559
|
Returns:
|
547
560
|
List of certificate file paths
|
548
561
|
"""
|
549
562
|
certificates = []
|
550
|
-
cert_extensions = [
|
551
|
-
|
552
|
-
for file_path in Path(directory).rglob(
|
563
|
+
cert_extensions = [".crt", ".pem", ".cer", ".der"]
|
564
|
+
|
565
|
+
for file_path in Path(directory).rglob("*"):
|
553
566
|
if file_path.is_file() and file_path.suffix.lower() in cert_extensions:
|
554
567
|
certificates.append(str(file_path))
|
555
|
-
|
568
|
+
|
556
569
|
return certificates
|
557
|
-
|
570
|
+
|
558
571
|
async def _test_alert_config(self, alert_config: Dict[str, Any]) -> Dict[str, Any]:
|
559
572
|
"""
|
560
573
|
Test alert configuration.
|
561
|
-
|
574
|
+
|
562
575
|
Args:
|
563
576
|
alert_config: Alert configuration to test
|
564
|
-
|
577
|
+
|
565
578
|
Returns:
|
566
579
|
Test result dictionary
|
567
580
|
"""
|
@@ -571,26 +584,28 @@ class CertMonitorCommand(Command):
|
|
571
584
|
recipients = alert_config["email_recipients"]
|
572
585
|
if not isinstance(recipients, list) or not recipients:
|
573
586
|
return {"success": False, "error": "Invalid email recipients"}
|
574
|
-
|
587
|
+
|
575
588
|
# Test webhook configuration if present
|
576
589
|
if "webhook_url" in alert_config:
|
577
590
|
webhook_url = alert_config["webhook_url"]
|
578
591
|
if not isinstance(webhook_url, str) or not webhook_url:
|
579
592
|
return {"success": False, "error": "Invalid webhook URL"}
|
580
|
-
|
593
|
+
|
581
594
|
return {"success": True}
|
582
|
-
|
595
|
+
|
583
596
|
except Exception as e:
|
584
597
|
return {"success": False, "error": str(e)}
|
585
|
-
|
586
|
-
async def _test_renewal_config(
|
598
|
+
|
599
|
+
async def _test_renewal_config(
|
600
|
+
self, cert_path: str, renewal_config: Dict[str, Any]
|
601
|
+
) -> Dict[str, Any]:
|
587
602
|
"""
|
588
603
|
Test renewal configuration.
|
589
|
-
|
604
|
+
|
590
605
|
Args:
|
591
606
|
cert_path: Path to certificate file
|
592
607
|
renewal_config: Renewal configuration to test
|
593
|
-
|
608
|
+
|
594
609
|
Returns:
|
595
610
|
Test result dictionary
|
596
611
|
"""
|
@@ -598,24 +613,27 @@ class CertMonitorCommand(Command):
|
|
598
613
|
# Get certificate info
|
599
614
|
cert_info = self.certificate_utils.get_certificate_info(cert_path)
|
600
615
|
if not cert_info:
|
601
|
-
return {
|
602
|
-
|
616
|
+
return {
|
617
|
+
"success": False,
|
618
|
+
"error": "Could not read certificate information",
|
619
|
+
}
|
620
|
+
|
603
621
|
# Check CA certificate
|
604
622
|
ca_cert_path = renewal_config.get("ca_cert_path")
|
605
623
|
if not ca_cert_path or not os.path.exists(ca_cert_path):
|
606
624
|
return {"success": False, "error": "CA certificate not found"}
|
607
|
-
|
625
|
+
|
608
626
|
# Check CA key
|
609
627
|
ca_key_path = renewal_config.get("ca_key_path")
|
610
628
|
if not ca_key_path or not os.path.exists(ca_key_path):
|
611
629
|
return {"success": False, "error": "CA private key not found"}
|
612
|
-
|
630
|
+
|
613
631
|
# Check output directory
|
614
632
|
output_dir = renewal_config.get("output_dir")
|
615
633
|
if not output_dir or not os.path.exists(output_dir):
|
616
634
|
return {"success": False, "error": "Output directory not found"}
|
617
|
-
|
635
|
+
|
618
636
|
return {"success": True}
|
619
|
-
|
637
|
+
|
620
638
|
except Exception as e:
|
621
|
-
return {"success": False, "error": str(e)}
|
639
|
+
return {"success": False, "error": str(e)}
|