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