mcp-proxy-adapter 4.1.1__py3-none-any.whl → 6.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_proxy_adapter/__main__.py +32 -0
- mcp_proxy_adapter/api/app.py +290 -33
- mcp_proxy_adapter/api/handlers.py +32 -6
- mcp_proxy_adapter/api/middleware/__init__.py +38 -32
- 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 +201 -0
- mcp_proxy_adapter/api/middleware/transport_middleware.py +122 -0
- mcp_proxy_adapter/api/middleware/unified_security.py +197 -0
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +158 -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 +8 -1
- 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 +366 -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 +394 -14
- mcp_proxy_adapter/core/app_factory.py +410 -0
- mcp_proxy_adapter/core/app_runner.py +272 -0
- mcp_proxy_adapter/core/auth_validator.py +606 -0
- mcp_proxy_adapter/core/certificate_utils.py +1045 -0
- mcp_proxy_adapter/core/client.py +574 -0
- mcp_proxy_adapter/core/client_manager.py +284 -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 +385 -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 +286 -0
- mcp_proxy_adapter/core/server_adapter.py +282 -0
- mcp_proxy_adapter/core/server_engine.py +270 -0
- mcp_proxy_adapter/core/settings.py +1 -0
- mcp_proxy_adapter/core/ssl_utils.py +234 -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/__init__.py +13 -4
- mcp_proxy_adapter/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/commands/__init__.py +5 -0
- mcp_proxy_adapter/examples/create_certificates_simple.py +550 -0
- mcp_proxy_adapter/examples/debug_request_state.py +112 -0
- mcp_proxy_adapter/examples/debug_role_chain.py +158 -0
- mcp_proxy_adapter/examples/demo_client.py +275 -0
- mcp_proxy_adapter/examples/examples/basic_framework/__init__.py +9 -0
- mcp_proxy_adapter/examples/examples/basic_framework/commands/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/hooks/__init__.py +4 -0
- mcp_proxy_adapter/examples/examples/basic_framework/main.py +44 -0
- mcp_proxy_adapter/examples/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/full_application/__init__.py +12 -0
- mcp_proxy_adapter/examples/full_application/commands/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +80 -0
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +90 -0
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +7 -0
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +75 -0
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +71 -0
- mcp_proxy_adapter/examples/full_application/main.py +173 -0
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +154 -0
- mcp_proxy_adapter/examples/generate_all_certificates.py +362 -0
- mcp_proxy_adapter/examples/generate_certificates.py +177 -0
- mcp_proxy_adapter/examples/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/generate_test_configs.py +331 -0
- mcp_proxy_adapter/examples/proxy_registration_example.py +334 -0
- mcp_proxy_adapter/examples/run_example.py +59 -0
- mcp_proxy_adapter/examples/run_full_test_suite.py +318 -0
- mcp_proxy_adapter/examples/run_proxy_server.py +146 -0
- mcp_proxy_adapter/examples/run_security_tests.py +544 -0
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +247 -0
- mcp_proxy_adapter/examples/scripts/config_generator.py +740 -0
- mcp_proxy_adapter/examples/scripts/create_certificates_simple.py +560 -0
- mcp_proxy_adapter/examples/scripts/generate_certificates_and_tokens.py +369 -0
- mcp_proxy_adapter/examples/security_test_client.py +782 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +328 -0
- mcp_proxy_adapter/examples/test_config.py +148 -0
- mcp_proxy_adapter/examples/test_config_generator.py +86 -0
- mcp_proxy_adapter/examples/test_examples.py +281 -0
- mcp_proxy_adapter/examples/universal_client.py +620 -0
- mcp_proxy_adapter/main.py +93 -0
- mcp_proxy_adapter/utils/config_generator.py +1008 -0
- mcp_proxy_adapter/version.py +5 -2
- mcp_proxy_adapter-6.0.1.dist-info/METADATA +679 -0
- mcp_proxy_adapter-6.0.1.dist-info/RECORD +140 -0
- mcp_proxy_adapter-6.0.1.dist-info/entry_points.txt +2 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.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/README.md +0 -124
- 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.0.1.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-4.1.1.dist-info → mcp_proxy_adapter-6.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,522 @@
|
|
1
|
+
"""
|
2
|
+
Module for proxy registration functionality with security framework integration.
|
3
|
+
|
4
|
+
This module handles automatic registration and unregistration of the server
|
5
|
+
with the MCP proxy server during startup and shutdown, using mcp_security_framework
|
6
|
+
for secure connections and authentication.
|
7
|
+
|
8
|
+
Author: Vasiliy Zdanovskiy
|
9
|
+
email: vasilyvz@gmail.com
|
10
|
+
"""
|
11
|
+
|
12
|
+
import asyncio
|
13
|
+
import json
|
14
|
+
import time
|
15
|
+
import ssl
|
16
|
+
from typing import Dict, Any, Optional, Tuple
|
17
|
+
from urllib.parse import urljoin
|
18
|
+
|
19
|
+
import aiohttp
|
20
|
+
import requests
|
21
|
+
from requests.exceptions import RequestException
|
22
|
+
|
23
|
+
from mcp_proxy_adapter.core.logging import logger
|
24
|
+
from mcp_proxy_adapter.core.client_security import create_client_security_manager
|
25
|
+
|
26
|
+
|
27
|
+
class ProxyRegistrationError(Exception):
|
28
|
+
"""Exception raised when proxy registration fails."""
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class ProxyRegistrationManager:
|
33
|
+
"""
|
34
|
+
Manager for proxy registration functionality with security framework integration.
|
35
|
+
|
36
|
+
Handles automatic registration and unregistration of the server
|
37
|
+
with the MCP proxy server using secure authentication methods.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def __init__(self, config: Dict[str, Any]):
|
41
|
+
"""
|
42
|
+
Initialize the proxy registration manager.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
config: Application configuration
|
46
|
+
"""
|
47
|
+
self.config = config
|
48
|
+
self.registration_config = config.get("registration", {})
|
49
|
+
|
50
|
+
# Basic registration settings
|
51
|
+
self.proxy_url = self.registration_config.get("server_url", "https://proxy-registry.example.com")
|
52
|
+
self.server_id = self.registration_config.get("proxy_info", {}).get("name", "mcp_proxy_adapter")
|
53
|
+
self.server_name = self.registration_config.get("proxy_info", {}).get("name", "MCP Proxy Adapter")
|
54
|
+
self.description = self.registration_config.get("proxy_info", {}).get("description", "JSON-RPC API for interacting with MCP Proxy")
|
55
|
+
|
56
|
+
# Heartbeat settings
|
57
|
+
heartbeat_config = self.registration_config.get("heartbeat", {})
|
58
|
+
self.timeout = heartbeat_config.get("timeout", 30)
|
59
|
+
self.retry_attempts = heartbeat_config.get("retry_attempts", 3)
|
60
|
+
self.retry_delay = heartbeat_config.get("retry_delay", 60)
|
61
|
+
self.heartbeat_interval = heartbeat_config.get("interval", 300)
|
62
|
+
|
63
|
+
# Auto registration settings
|
64
|
+
self.auto_register = self.registration_config.get("enabled", False)
|
65
|
+
self.auto_unregister = True # Always unregister on shutdown
|
66
|
+
|
67
|
+
# Initialize client security manager
|
68
|
+
self.client_security = create_client_security_manager(config)
|
69
|
+
|
70
|
+
# Registration state
|
71
|
+
self.registered = False
|
72
|
+
self.server_key: Optional[str] = None
|
73
|
+
self.server_url: Optional[str] = None
|
74
|
+
self.heartbeat_task: Optional[asyncio.Task] = None
|
75
|
+
|
76
|
+
logger.info("Proxy registration manager initialized with security framework integration")
|
77
|
+
|
78
|
+
def is_enabled(self) -> bool:
|
79
|
+
"""
|
80
|
+
Check if proxy registration is enabled.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
True if registration is enabled, False otherwise.
|
84
|
+
"""
|
85
|
+
return self.registration_config.get("enabled", False)
|
86
|
+
|
87
|
+
def set_server_url(self, server_url: str) -> None:
|
88
|
+
"""
|
89
|
+
Set the server URL for registration.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
server_url: The URL where this server is accessible.
|
93
|
+
"""
|
94
|
+
self.server_url = server_url
|
95
|
+
logger.info(f"Proxy registration server URL set to: {server_url}")
|
96
|
+
|
97
|
+
def _get_auth_headers(self) -> Dict[str, str]:
|
98
|
+
"""
|
99
|
+
Get authentication headers for registration requests.
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
Dictionary of authentication headers
|
103
|
+
"""
|
104
|
+
if not self.client_security:
|
105
|
+
return {"Content-Type": "application/json"}
|
106
|
+
|
107
|
+
auth_method = self.registration_config.get("auth_method", "certificate")
|
108
|
+
|
109
|
+
if auth_method == "certificate":
|
110
|
+
return self.client_security.get_client_auth_headers("certificate")
|
111
|
+
elif auth_method == "token":
|
112
|
+
token_config = self.registration_config.get("token", {})
|
113
|
+
token = token_config.get("token")
|
114
|
+
return self.client_security.get_client_auth_headers("jwt", token=token)
|
115
|
+
elif auth_method == "api_key":
|
116
|
+
api_key_config = self.registration_config.get("api_key", {})
|
117
|
+
api_key = api_key_config.get("key")
|
118
|
+
return self.client_security.get_client_auth_headers("api_key", api_key=api_key)
|
119
|
+
else:
|
120
|
+
return {"Content-Type": "application/json"}
|
121
|
+
|
122
|
+
def _create_ssl_context(self) -> Optional[ssl.SSLContext]:
|
123
|
+
"""
|
124
|
+
Create SSL context for secure connections.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
SSL context or None if SSL not needed
|
128
|
+
"""
|
129
|
+
if not self.client_security:
|
130
|
+
return None
|
131
|
+
|
132
|
+
try:
|
133
|
+
# Check if SSL is enabled for registration
|
134
|
+
cert_config = self.registration_config.get("certificate", {})
|
135
|
+
if cert_config.get("enabled", False):
|
136
|
+
return self.client_security.create_client_ssl_context()
|
137
|
+
|
138
|
+
return None
|
139
|
+
except Exception as e:
|
140
|
+
logger.error(f"Failed to create SSL context: {e}")
|
141
|
+
return None
|
142
|
+
|
143
|
+
async def register_server(self) -> bool:
|
144
|
+
"""
|
145
|
+
Register the server with the proxy using secure authentication.
|
146
|
+
|
147
|
+
Returns:
|
148
|
+
True if registration was successful, False otherwise.
|
149
|
+
"""
|
150
|
+
if not self.is_enabled():
|
151
|
+
logger.info("Proxy registration is disabled in configuration")
|
152
|
+
return False
|
153
|
+
|
154
|
+
if not self.server_url:
|
155
|
+
logger.error("Server URL not set, cannot register with proxy")
|
156
|
+
return False
|
157
|
+
|
158
|
+
# Prepare registration data with proxy info
|
159
|
+
proxy_info = self.registration_config.get("proxy_info", {})
|
160
|
+
registration_data = {
|
161
|
+
"server_id": self.server_id,
|
162
|
+
"server_url": self.server_url,
|
163
|
+
"server_name": self.server_name,
|
164
|
+
"description": self.description,
|
165
|
+
"version": proxy_info.get("version", "1.0.0"),
|
166
|
+
"capabilities": proxy_info.get("capabilities", ["jsonrpc", "rest"]),
|
167
|
+
"endpoints": proxy_info.get("endpoints", {
|
168
|
+
"jsonrpc": "/api/jsonrpc",
|
169
|
+
"rest": "/cmd",
|
170
|
+
"health": "/health"
|
171
|
+
})
|
172
|
+
}
|
173
|
+
|
174
|
+
logger.info(f"Attempting to register server with proxy at {self.proxy_url}")
|
175
|
+
logger.debug(f"Registration data: {registration_data}")
|
176
|
+
|
177
|
+
for attempt in range(self.retry_attempts):
|
178
|
+
try:
|
179
|
+
success, result = await self._make_secure_registration_request(registration_data)
|
180
|
+
|
181
|
+
if success:
|
182
|
+
self.registered = True
|
183
|
+
self.server_key = result.get("server_key")
|
184
|
+
logger.info(f"✅ Successfully registered with proxy. Server key: {self.server_key}")
|
185
|
+
|
186
|
+
# Start heartbeat if enabled
|
187
|
+
if self.registration_config.get("heartbeat", {}).get("enabled", True):
|
188
|
+
await self._start_heartbeat()
|
189
|
+
|
190
|
+
return True
|
191
|
+
else:
|
192
|
+
error_msg = result.get("error", {}).get("message", "Unknown error")
|
193
|
+
logger.warning(f"❌ Registration attempt {attempt + 1} failed: {error_msg}")
|
194
|
+
|
195
|
+
if attempt < self.retry_attempts - 1:
|
196
|
+
logger.info(f"Retrying in {self.retry_delay} seconds...")
|
197
|
+
await asyncio.sleep(self.retry_delay)
|
198
|
+
|
199
|
+
except Exception as e:
|
200
|
+
logger.error(f"❌ Registration attempt {attempt + 1} failed with exception: {e}")
|
201
|
+
|
202
|
+
if attempt < self.retry_attempts - 1:
|
203
|
+
logger.info(f"Retrying in {self.retry_delay} seconds...")
|
204
|
+
await asyncio.sleep(self.retry_delay)
|
205
|
+
|
206
|
+
logger.error(f"❌ Failed to register with proxy after {self.retry_attempts} attempts")
|
207
|
+
return False
|
208
|
+
|
209
|
+
async def unregister_server(self) -> bool:
|
210
|
+
"""
|
211
|
+
Unregister the server from the proxy.
|
212
|
+
|
213
|
+
Returns:
|
214
|
+
True if unregistration was successful, False otherwise.
|
215
|
+
"""
|
216
|
+
if not self.is_enabled():
|
217
|
+
logger.info("Proxy registration is disabled, skipping unregistration")
|
218
|
+
return True
|
219
|
+
|
220
|
+
if not self.registered or not self.server_key:
|
221
|
+
logger.info("Server not registered with proxy, skipping unregistration")
|
222
|
+
return True
|
223
|
+
|
224
|
+
# Stop heartbeat
|
225
|
+
await self._stop_heartbeat()
|
226
|
+
|
227
|
+
# Extract copy_number from server_key (format: server_id_copy_number)
|
228
|
+
try:
|
229
|
+
copy_number = int(self.server_key.split("_")[-1])
|
230
|
+
except (ValueError, IndexError):
|
231
|
+
copy_number = 1
|
232
|
+
|
233
|
+
unregistration_data = {
|
234
|
+
"server_id": self.server_id,
|
235
|
+
"copy_number": copy_number
|
236
|
+
}
|
237
|
+
|
238
|
+
logger.info(f"Attempting to unregister server from proxy at {self.proxy_url}")
|
239
|
+
logger.debug(f"Unregistration data: {unregistration_data}")
|
240
|
+
|
241
|
+
try:
|
242
|
+
success, result = await self._make_secure_unregistration_request(unregistration_data)
|
243
|
+
|
244
|
+
if success:
|
245
|
+
unregistered = result.get("unregistered", False)
|
246
|
+
if unregistered:
|
247
|
+
logger.info("✅ Successfully unregistered from proxy")
|
248
|
+
else:
|
249
|
+
logger.warning("⚠️ Server was not found in proxy registry")
|
250
|
+
|
251
|
+
self.registered = False
|
252
|
+
self.server_key = None
|
253
|
+
return True
|
254
|
+
else:
|
255
|
+
error_msg = result.get("error", {}).get("message", "Unknown error")
|
256
|
+
logger.error(f"❌ Failed to unregister from proxy: {error_msg}")
|
257
|
+
return False
|
258
|
+
|
259
|
+
except Exception as e:
|
260
|
+
logger.error(f"❌ Unregistration failed with exception: {e}")
|
261
|
+
return False
|
262
|
+
|
263
|
+
async def _make_secure_registration_request(self, data: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
|
264
|
+
"""
|
265
|
+
Make secure registration request to proxy using security framework.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
data: Registration data.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
Tuple of (success, result).
|
272
|
+
"""
|
273
|
+
url = urljoin(self.proxy_url, "/register")
|
274
|
+
|
275
|
+
# Get authentication headers
|
276
|
+
headers = self._get_auth_headers()
|
277
|
+
headers["Content-Type"] = "application/json"
|
278
|
+
|
279
|
+
# Create SSL context if needed
|
280
|
+
ssl_context = self._create_ssl_context()
|
281
|
+
|
282
|
+
# Create connector with SSL context
|
283
|
+
connector = None
|
284
|
+
if ssl_context:
|
285
|
+
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
286
|
+
|
287
|
+
try:
|
288
|
+
async with aiohttp.ClientSession(connector=connector) as session:
|
289
|
+
async with session.post(
|
290
|
+
url,
|
291
|
+
json=data,
|
292
|
+
headers=headers,
|
293
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
294
|
+
) as response:
|
295
|
+
result = await response.json()
|
296
|
+
|
297
|
+
# Validate response headers if security framework available
|
298
|
+
if self.client_security:
|
299
|
+
self.client_security.validate_server_response(dict(response.headers))
|
300
|
+
|
301
|
+
return response.status == 200, result
|
302
|
+
finally:
|
303
|
+
if connector:
|
304
|
+
await connector.close()
|
305
|
+
|
306
|
+
async def _make_secure_unregistration_request(self, data: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]:
|
307
|
+
"""
|
308
|
+
Make secure unregistration request to proxy using security framework.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
data: Unregistration data.
|
312
|
+
|
313
|
+
Returns:
|
314
|
+
Tuple of (success, result).
|
315
|
+
"""
|
316
|
+
url = urljoin(self.proxy_url, "/unregister")
|
317
|
+
|
318
|
+
# Get authentication headers
|
319
|
+
headers = self._get_auth_headers()
|
320
|
+
headers["Content-Type"] = "application/json"
|
321
|
+
|
322
|
+
# Create SSL context if needed
|
323
|
+
ssl_context = self._create_ssl_context()
|
324
|
+
|
325
|
+
# Create connector with SSL context
|
326
|
+
connector = None
|
327
|
+
if ssl_context:
|
328
|
+
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
329
|
+
|
330
|
+
try:
|
331
|
+
async with aiohttp.ClientSession(connector=connector) as session:
|
332
|
+
async with session.post(
|
333
|
+
url,
|
334
|
+
json=data,
|
335
|
+
headers=headers,
|
336
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
337
|
+
) as response:
|
338
|
+
result = await response.json()
|
339
|
+
|
340
|
+
# Validate response headers if security framework available
|
341
|
+
if self.client_security:
|
342
|
+
self.client_security.validate_server_response(dict(response.headers))
|
343
|
+
|
344
|
+
return response.status == 200, result
|
345
|
+
finally:
|
346
|
+
if connector:
|
347
|
+
await connector.close()
|
348
|
+
|
349
|
+
async def _start_heartbeat(self) -> None:
|
350
|
+
"""Start heartbeat task for keeping registration alive."""
|
351
|
+
if self.heartbeat_task and not self.heartbeat_task.done():
|
352
|
+
return
|
353
|
+
|
354
|
+
self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
|
355
|
+
logger.info("Heartbeat task started")
|
356
|
+
|
357
|
+
async def _stop_heartbeat(self) -> None:
|
358
|
+
"""Stop heartbeat task."""
|
359
|
+
if self.heartbeat_task and not self.heartbeat_task.done():
|
360
|
+
self.heartbeat_task.cancel()
|
361
|
+
try:
|
362
|
+
await self.heartbeat_task
|
363
|
+
except asyncio.CancelledError:
|
364
|
+
pass
|
365
|
+
logger.info("Heartbeat task stopped")
|
366
|
+
|
367
|
+
async def _heartbeat_loop(self) -> None:
|
368
|
+
"""Heartbeat loop to keep registration alive."""
|
369
|
+
while self.registered:
|
370
|
+
try:
|
371
|
+
await asyncio.sleep(self.heartbeat_interval)
|
372
|
+
|
373
|
+
if not self.registered:
|
374
|
+
break
|
375
|
+
|
376
|
+
# Send heartbeat
|
377
|
+
success = await self._send_heartbeat()
|
378
|
+
if not success:
|
379
|
+
logger.warning("Heartbeat failed, attempting to re-register")
|
380
|
+
await self.register_server()
|
381
|
+
|
382
|
+
except asyncio.CancelledError:
|
383
|
+
break
|
384
|
+
except Exception as e:
|
385
|
+
logger.error(f"Heartbeat error: {e}")
|
386
|
+
|
387
|
+
async def _send_heartbeat(self) -> bool:
|
388
|
+
"""Send heartbeat to proxy server."""
|
389
|
+
if not self.server_key:
|
390
|
+
return False
|
391
|
+
|
392
|
+
heartbeat_data = {
|
393
|
+
"server_id": self.server_id,
|
394
|
+
"server_key": self.server_key,
|
395
|
+
"timestamp": int(time.time())
|
396
|
+
}
|
397
|
+
|
398
|
+
url = urljoin(self.proxy_url, "/heartbeat")
|
399
|
+
|
400
|
+
# Get authentication headers
|
401
|
+
headers = self._get_auth_headers()
|
402
|
+
headers["Content-Type"] = "application/json"
|
403
|
+
|
404
|
+
# Create SSL context if needed
|
405
|
+
ssl_context = self._create_ssl_context()
|
406
|
+
|
407
|
+
# Create connector with SSL context
|
408
|
+
connector = None
|
409
|
+
if ssl_context:
|
410
|
+
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
411
|
+
|
412
|
+
try:
|
413
|
+
async with aiohttp.ClientSession(connector=connector) as session:
|
414
|
+
async with session.post(
|
415
|
+
url,
|
416
|
+
json=heartbeat_data,
|
417
|
+
headers=headers,
|
418
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout)
|
419
|
+
) as response:
|
420
|
+
if response.status == 200:
|
421
|
+
logger.debug("Heartbeat sent successfully")
|
422
|
+
return True
|
423
|
+
else:
|
424
|
+
logger.warning(f"Heartbeat failed with status: {response.status}")
|
425
|
+
return False
|
426
|
+
except Exception as e:
|
427
|
+
logger.error(f"Heartbeat error: {e}")
|
428
|
+
return False
|
429
|
+
finally:
|
430
|
+
if connector:
|
431
|
+
await connector.close()
|
432
|
+
|
433
|
+
def get_registration_status(self) -> Dict[str, Any]:
|
434
|
+
"""
|
435
|
+
Get current registration status.
|
436
|
+
|
437
|
+
Returns:
|
438
|
+
Dictionary with registration status information.
|
439
|
+
"""
|
440
|
+
status = {
|
441
|
+
"enabled": self.is_enabled(),
|
442
|
+
"registered": self.registered,
|
443
|
+
"server_key": self.server_key,
|
444
|
+
"server_url": self.server_url,
|
445
|
+
"proxy_url": self.proxy_url,
|
446
|
+
"server_id": self.server_id,
|
447
|
+
"heartbeat_active": self.heartbeat_task is not None and not self.heartbeat_task.done()
|
448
|
+
}
|
449
|
+
|
450
|
+
# Add security information if available
|
451
|
+
if self.client_security:
|
452
|
+
status["security_enabled"] = True
|
453
|
+
status["ssl_enabled"] = self.client_security.is_ssl_enabled()
|
454
|
+
status["auth_methods"] = self.client_security.get_supported_auth_methods()
|
455
|
+
|
456
|
+
cert_info = self.client_security.get_client_certificate_info()
|
457
|
+
if cert_info:
|
458
|
+
status["client_certificate"] = cert_info
|
459
|
+
else:
|
460
|
+
status["security_enabled"] = False
|
461
|
+
|
462
|
+
return status
|
463
|
+
|
464
|
+
|
465
|
+
# Global proxy registration manager instance (will be initialized with config)
|
466
|
+
proxy_registration_manager: Optional[ProxyRegistrationManager] = None
|
467
|
+
|
468
|
+
|
469
|
+
def initialize_proxy_registration(config: Dict[str, Any]) -> None:
|
470
|
+
"""
|
471
|
+
Initialize global proxy registration manager.
|
472
|
+
|
473
|
+
Args:
|
474
|
+
config: Application configuration
|
475
|
+
"""
|
476
|
+
global proxy_registration_manager
|
477
|
+
proxy_registration_manager = ProxyRegistrationManager(config)
|
478
|
+
|
479
|
+
|
480
|
+
async def register_with_proxy(server_url: str) -> bool:
|
481
|
+
"""
|
482
|
+
Register the server with the proxy.
|
483
|
+
|
484
|
+
Args:
|
485
|
+
server_url: The URL where this server is accessible.
|
486
|
+
|
487
|
+
Returns:
|
488
|
+
True if registration was successful, False otherwise.
|
489
|
+
"""
|
490
|
+
if not proxy_registration_manager:
|
491
|
+
logger.error("Proxy registration manager not initialized")
|
492
|
+
return False
|
493
|
+
|
494
|
+
proxy_registration_manager.set_server_url(server_url)
|
495
|
+
return await proxy_registration_manager.register_server()
|
496
|
+
|
497
|
+
|
498
|
+
async def unregister_from_proxy() -> bool:
|
499
|
+
"""
|
500
|
+
Unregister the server from the proxy.
|
501
|
+
|
502
|
+
Returns:
|
503
|
+
True if unregistration was successful, False otherwise.
|
504
|
+
"""
|
505
|
+
if not proxy_registration_manager:
|
506
|
+
logger.error("Proxy registration manager not initialized")
|
507
|
+
return False
|
508
|
+
|
509
|
+
return await proxy_registration_manager.unregister_server()
|
510
|
+
|
511
|
+
|
512
|
+
def get_proxy_registration_status() -> Dict[str, Any]:
|
513
|
+
"""
|
514
|
+
Get current proxy registration status.
|
515
|
+
|
516
|
+
Returns:
|
517
|
+
Dictionary with registration status information.
|
518
|
+
"""
|
519
|
+
if not proxy_registration_manager:
|
520
|
+
return {"error": "Proxy registration manager not initialized"}
|
521
|
+
|
522
|
+
return proxy_registration_manager.get_registration_status()
|