embed-client 1.0.1.1__py3-none-any.whl → 3.1.0.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.
- embed_client/async_client.py +445 -22
- embed_client/auth.py +487 -0
- embed_client/auth_examples.py +248 -0
- embed_client/client_factory.py +396 -0
- embed_client/client_factory_examples.py +353 -0
- embed_client/config.py +592 -0
- embed_client/config_examples.py +197 -0
- embed_client/example_async_usage.py +578 -90
- embed_client/example_async_usage_ru.py +442 -92
- embed_client/ssl_examples.py +329 -0
- embed_client/ssl_manager.py +466 -0
- embed_client-3.1.0.0.dist-info/METADATA +229 -0
- embed_client-3.1.0.0.dist-info/RECORD +16 -0
- embed_client-1.0.1.1.dist-info/METADATA +0 -9
- embed_client-1.0.1.1.dist-info/RECORD +0 -8
- {embed_client-1.0.1.1.dist-info → embed_client-3.1.0.0.dist-info}/WHEEL +0 -0
- {embed_client-1.0.1.1.dist-info → embed_client-3.1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,466 @@
|
|
1
|
+
"""
|
2
|
+
SSL/TLS Manager for embed-client.
|
3
|
+
|
4
|
+
This module provides SSL/TLS management for the embed-client, supporting
|
5
|
+
all security modes including HTTP, HTTPS, and mTLS. It integrates with
|
6
|
+
mcp_security_framework when available and provides fallback implementations.
|
7
|
+
|
8
|
+
Author: Vasiliy Zdanovskiy
|
9
|
+
email: vasilyvz@gmail.com
|
10
|
+
"""
|
11
|
+
|
12
|
+
import logging
|
13
|
+
import ssl
|
14
|
+
import os
|
15
|
+
from pathlib import Path
|
16
|
+
from typing import Any, Dict, List, Optional, Union
|
17
|
+
|
18
|
+
# Try to import mcp_security_framework components
|
19
|
+
try:
|
20
|
+
from mcp_security_framework.core.ssl_manager import SSLManager as FrameworkSSLManager
|
21
|
+
from mcp_security_framework.core.cert_manager import CertificateManager
|
22
|
+
from mcp_security_framework.schemas.config import SSLConfig
|
23
|
+
from mcp_security_framework.schemas.models import CertificateInfo
|
24
|
+
from mcp_security_framework.utils.cert_utils import (
|
25
|
+
extract_certificate_info,
|
26
|
+
get_certificate_expiry,
|
27
|
+
is_certificate_self_signed,
|
28
|
+
parse_certificate,
|
29
|
+
validate_certificate_chain
|
30
|
+
)
|
31
|
+
SECURITY_FRAMEWORK_AVAILABLE = True
|
32
|
+
except ImportError:
|
33
|
+
SECURITY_FRAMEWORK_AVAILABLE = False
|
34
|
+
print("Warning: mcp_security_framework not available. Using fallback SSL/TLS management.")
|
35
|
+
|
36
|
+
# Fallback certificate handling
|
37
|
+
try:
|
38
|
+
from cryptography import x509
|
39
|
+
from cryptography.hazmat.primitives import serialization
|
40
|
+
CRYPTOGRAPHY_AVAILABLE = True
|
41
|
+
except ImportError:
|
42
|
+
CRYPTOGRAPHY_AVAILABLE = False
|
43
|
+
print("Warning: cryptography not available. Limited SSL/TLS functionality.")
|
44
|
+
|
45
|
+
|
46
|
+
class SSLManagerError(Exception):
|
47
|
+
"""Raised when SSL/TLS operations fail."""
|
48
|
+
|
49
|
+
def __init__(self, message: str, error_code: int = -32002):
|
50
|
+
self.message = message
|
51
|
+
self.error_code = error_code
|
52
|
+
super().__init__(self.message)
|
53
|
+
|
54
|
+
|
55
|
+
class ClientSSLManager:
|
56
|
+
"""
|
57
|
+
Client SSL/TLS Manager.
|
58
|
+
|
59
|
+
This class provides SSL/TLS management for the embed-client,
|
60
|
+
supporting all security modes with integration to
|
61
|
+
mcp_security_framework when available.
|
62
|
+
"""
|
63
|
+
|
64
|
+
def __init__(self, config: Dict[str, Any]):
|
65
|
+
"""
|
66
|
+
Initialize SSL/TLS manager.
|
67
|
+
|
68
|
+
Args:
|
69
|
+
config: SSL/TLS configuration dictionary
|
70
|
+
"""
|
71
|
+
self.config = config
|
72
|
+
self.logger = logging.getLogger(__name__)
|
73
|
+
|
74
|
+
# Initialize security framework components if available
|
75
|
+
self.ssl_manager = None
|
76
|
+
self.cert_manager = None
|
77
|
+
|
78
|
+
if SECURITY_FRAMEWORK_AVAILABLE:
|
79
|
+
self._initialize_security_framework()
|
80
|
+
else:
|
81
|
+
self.logger.warning("mcp_security_framework not available, using fallback SSL/TLS management")
|
82
|
+
|
83
|
+
def _initialize_security_framework(self) -> None:
|
84
|
+
"""Initialize mcp_security_framework components."""
|
85
|
+
try:
|
86
|
+
# Create SSL config
|
87
|
+
ssl_config = SSLConfig(
|
88
|
+
enabled=self.config.get("ssl", {}).get("enabled", False),
|
89
|
+
cert_file=self.config.get("ssl", {}).get("cert_file"),
|
90
|
+
key_file=self.config.get("ssl", {}).get("key_file"),
|
91
|
+
ca_cert_file=self.config.get("ssl", {}).get("ca_cert_file"),
|
92
|
+
verify_mode=self.config.get("ssl", {}).get("verify_mode", "CERT_REQUIRED"),
|
93
|
+
check_hostname=self.config.get("ssl", {}).get("check_hostname", True),
|
94
|
+
check_expiry=self.config.get("ssl", {}).get("check_expiry", True)
|
95
|
+
)
|
96
|
+
|
97
|
+
# Initialize managers
|
98
|
+
self.ssl_manager = FrameworkSSLManager(ssl_config)
|
99
|
+
|
100
|
+
self.logger.info("Security framework SSL/TLS components initialized successfully")
|
101
|
+
|
102
|
+
except Exception as e:
|
103
|
+
self.logger.warning(f"Failed to initialize security framework SSL/TLS: {e}")
|
104
|
+
self.ssl_manager = None
|
105
|
+
self.cert_manager = None
|
106
|
+
|
107
|
+
def create_client_ssl_context(self) -> Optional[ssl.SSLContext]:
|
108
|
+
"""
|
109
|
+
Create SSL context for client connections.
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
SSL context for client connections or None if SSL is disabled
|
113
|
+
"""
|
114
|
+
# Check if SSL is enabled first
|
115
|
+
if not self.is_ssl_enabled():
|
116
|
+
return None
|
117
|
+
|
118
|
+
try:
|
119
|
+
# Force fallback implementation for CERT_NONE mode
|
120
|
+
ssl_config = self.config.get("ssl", {})
|
121
|
+
if ssl_config.get("verify_mode") == "CERT_NONE":
|
122
|
+
return self._create_client_ssl_context_fallback()
|
123
|
+
elif self.ssl_manager:
|
124
|
+
# Use security framework
|
125
|
+
return self.ssl_manager.create_client_context()
|
126
|
+
else:
|
127
|
+
# Fallback implementation
|
128
|
+
return self._create_client_ssl_context_fallback()
|
129
|
+
|
130
|
+
except Exception as e:
|
131
|
+
self.logger.error(f"Failed to create client SSL context: {e}")
|
132
|
+
raise SSLManagerError(f"Failed to create client SSL context: {e}")
|
133
|
+
|
134
|
+
def _create_client_ssl_context_fallback(self) -> Optional[ssl.SSLContext]:
|
135
|
+
"""Fallback SSL context creation."""
|
136
|
+
ssl_config = self.config.get("ssl", {})
|
137
|
+
|
138
|
+
if not ssl_config.get("enabled", False):
|
139
|
+
return None
|
140
|
+
|
141
|
+
try:
|
142
|
+
# Configure verification
|
143
|
+
verify_mode = ssl_config.get("verify_mode", "CERT_REQUIRED")
|
144
|
+
check_hostname = ssl_config.get("check_hostname", True)
|
145
|
+
|
146
|
+
# Force check_hostname=False for CERT_NONE mode
|
147
|
+
if verify_mode == "CERT_NONE":
|
148
|
+
check_hostname = False
|
149
|
+
|
150
|
+
# Create SSL context based on verification mode
|
151
|
+
if verify_mode == "CERT_NONE":
|
152
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
153
|
+
context.check_hostname = False
|
154
|
+
context.verify_mode = ssl.CERT_NONE
|
155
|
+
else:
|
156
|
+
context = ssl.create_default_context()
|
157
|
+
context.check_hostname = check_hostname
|
158
|
+
if verify_mode == "CERT_OPTIONAL":
|
159
|
+
context.verify_mode = ssl.CERT_OPTIONAL
|
160
|
+
else: # CERT_REQUIRED
|
161
|
+
context.verify_mode = ssl.CERT_REQUIRED
|
162
|
+
|
163
|
+
# Load CA certificate if provided
|
164
|
+
ca_cert_file = ssl_config.get("ca_cert_file")
|
165
|
+
if ca_cert_file and os.path.exists(ca_cert_file):
|
166
|
+
context.load_verify_locations(ca_cert_file)
|
167
|
+
|
168
|
+
# Load client certificate if provided (for mTLS)
|
169
|
+
cert_file = ssl_config.get("cert_file")
|
170
|
+
key_file = ssl_config.get("key_file")
|
171
|
+
if cert_file and key_file and os.path.exists(cert_file) and os.path.exists(key_file):
|
172
|
+
context.load_cert_chain(cert_file, key_file)
|
173
|
+
|
174
|
+
return context
|
175
|
+
|
176
|
+
except Exception as e:
|
177
|
+
raise SSLManagerError(f"Failed to create SSL context: {e}")
|
178
|
+
|
179
|
+
def create_connector(self) -> Optional[Any]:
|
180
|
+
"""
|
181
|
+
Create aiohttp connector with SSL context.
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
aiohttp connector with SSL context or None if SSL is disabled
|
185
|
+
"""
|
186
|
+
try:
|
187
|
+
import aiohttp
|
188
|
+
|
189
|
+
ssl_context = self.create_client_ssl_context()
|
190
|
+
if ssl_context is None:
|
191
|
+
return None
|
192
|
+
|
193
|
+
return aiohttp.TCPConnector(ssl=ssl_context)
|
194
|
+
|
195
|
+
except ImportError:
|
196
|
+
self.logger.error("aiohttp not available for connector creation")
|
197
|
+
return None
|
198
|
+
except Exception as e:
|
199
|
+
self.logger.error(f"Failed to create connector: {e}")
|
200
|
+
return None
|
201
|
+
|
202
|
+
def validate_certificate(self, cert_file: str) -> Dict[str, Any]:
|
203
|
+
"""
|
204
|
+
Validate certificate file.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
cert_file: Path to certificate file
|
208
|
+
|
209
|
+
Returns:
|
210
|
+
Dictionary with validation results
|
211
|
+
"""
|
212
|
+
try:
|
213
|
+
if self.ssl_manager and CRYPTOGRAPHY_AVAILABLE:
|
214
|
+
# Use security framework
|
215
|
+
cert_info = extract_certificate_info(cert_file)
|
216
|
+
return {
|
217
|
+
"valid": True,
|
218
|
+
"subject": str(cert_info.subject),
|
219
|
+
"issuer": str(cert_info.issuer),
|
220
|
+
"not_valid_before": cert_info.not_valid_before.isoformat(),
|
221
|
+
"not_valid_after": cert_info.not_valid_after.isoformat(),
|
222
|
+
"serial_number": str(cert_info.serial_number),
|
223
|
+
"is_self_signed": is_certificate_self_signed(cert_file)
|
224
|
+
}
|
225
|
+
else:
|
226
|
+
# Fallback implementation
|
227
|
+
return self._validate_certificate_fallback(cert_file)
|
228
|
+
|
229
|
+
except Exception as e:
|
230
|
+
self.logger.error(f"Certificate validation failed: {e}")
|
231
|
+
return {
|
232
|
+
"valid": False,
|
233
|
+
"error": str(e)
|
234
|
+
}
|
235
|
+
|
236
|
+
def _validate_certificate_fallback(self, cert_file: str) -> Dict[str, Any]:
|
237
|
+
"""Fallback certificate validation."""
|
238
|
+
if not CRYPTOGRAPHY_AVAILABLE:
|
239
|
+
return {
|
240
|
+
"valid": False,
|
241
|
+
"error": "cryptography not available for certificate validation"
|
242
|
+
}
|
243
|
+
|
244
|
+
try:
|
245
|
+
if not os.path.exists(cert_file):
|
246
|
+
return {
|
247
|
+
"valid": False,
|
248
|
+
"error": "Certificate file not found"
|
249
|
+
}
|
250
|
+
|
251
|
+
# Basic file existence and readability check
|
252
|
+
with open(cert_file, 'rb') as f:
|
253
|
+
cert_data = f.read()
|
254
|
+
|
255
|
+
# Try to parse certificate
|
256
|
+
cert = x509.load_pem_x509_certificate(cert_data)
|
257
|
+
|
258
|
+
return {
|
259
|
+
"valid": True,
|
260
|
+
"subject": str(cert.subject),
|
261
|
+
"issuer": str(cert.issuer),
|
262
|
+
"not_valid_before": cert.not_valid_before.isoformat(),
|
263
|
+
"not_valid_after": cert.not_valid_after.isoformat(),
|
264
|
+
"serial_number": str(cert.serial_number)
|
265
|
+
}
|
266
|
+
|
267
|
+
except Exception as e:
|
268
|
+
return {
|
269
|
+
"valid": False,
|
270
|
+
"error": f"Certificate validation failed: {e}"
|
271
|
+
}
|
272
|
+
|
273
|
+
def get_ssl_config(self) -> Dict[str, Any]:
|
274
|
+
"""
|
275
|
+
Get current SSL configuration.
|
276
|
+
|
277
|
+
Returns:
|
278
|
+
Dictionary with SSL configuration
|
279
|
+
"""
|
280
|
+
return self.config.get("ssl", {})
|
281
|
+
|
282
|
+
def is_ssl_enabled(self) -> bool:
|
283
|
+
"""
|
284
|
+
Check if SSL/TLS is enabled.
|
285
|
+
|
286
|
+
Returns:
|
287
|
+
True if SSL/TLS is enabled, False otherwise
|
288
|
+
"""
|
289
|
+
return self.config.get("ssl", {}).get("enabled", False)
|
290
|
+
|
291
|
+
def is_mtls_enabled(self) -> bool:
|
292
|
+
"""
|
293
|
+
Check if mTLS (mutual TLS) is enabled.
|
294
|
+
|
295
|
+
Returns:
|
296
|
+
True if mTLS is enabled, False otherwise
|
297
|
+
"""
|
298
|
+
ssl_config = self.config.get("ssl", {})
|
299
|
+
return (ssl_config.get("enabled", False) and
|
300
|
+
bool(ssl_config.get("cert_file")) and
|
301
|
+
bool(ssl_config.get("key_file")))
|
302
|
+
|
303
|
+
def get_certificate_info(self, cert_file: str) -> Optional[Dict[str, Any]]:
|
304
|
+
"""
|
305
|
+
Get certificate information.
|
306
|
+
|
307
|
+
Args:
|
308
|
+
cert_file: Path to certificate file
|
309
|
+
|
310
|
+
Returns:
|
311
|
+
Dictionary with certificate information or None if not available
|
312
|
+
"""
|
313
|
+
try:
|
314
|
+
if self.ssl_manager and CRYPTOGRAPHY_AVAILABLE:
|
315
|
+
# Use security framework
|
316
|
+
cert_info = extract_certificate_info(cert_file)
|
317
|
+
return {
|
318
|
+
"subject": str(cert_info.subject),
|
319
|
+
"issuer": str(cert_info.issuer),
|
320
|
+
"not_valid_before": cert_info.not_valid_before.isoformat(),
|
321
|
+
"not_valid_after": cert_info.not_valid_after.isoformat(),
|
322
|
+
"serial_number": str(cert_info.serial_number),
|
323
|
+
"version": cert_info.version,
|
324
|
+
"signature_algorithm": str(cert_info.signature_algorithm_oid),
|
325
|
+
"is_self_signed": is_certificate_self_signed(cert_file)
|
326
|
+
}
|
327
|
+
else:
|
328
|
+
# Fallback implementation
|
329
|
+
return self._get_certificate_info_fallback(cert_file)
|
330
|
+
|
331
|
+
except Exception as e:
|
332
|
+
self.logger.error(f"Failed to get certificate info: {e}")
|
333
|
+
return None
|
334
|
+
|
335
|
+
def _get_certificate_info_fallback(self, cert_file: str) -> Optional[Dict[str, Any]]:
|
336
|
+
"""Fallback certificate info extraction."""
|
337
|
+
if not CRYPTOGRAPHY_AVAILABLE:
|
338
|
+
return None
|
339
|
+
|
340
|
+
try:
|
341
|
+
if not os.path.exists(cert_file):
|
342
|
+
return None
|
343
|
+
|
344
|
+
with open(cert_file, 'rb') as f:
|
345
|
+
cert_data = f.read()
|
346
|
+
|
347
|
+
cert = x509.load_pem_x509_certificate(cert_data)
|
348
|
+
|
349
|
+
return {
|
350
|
+
"subject": str(cert.subject),
|
351
|
+
"issuer": str(cert.issuer),
|
352
|
+
"not_valid_before": cert.not_valid_before.isoformat(),
|
353
|
+
"not_valid_after": cert.not_valid_after.isoformat(),
|
354
|
+
"serial_number": str(cert.serial_number),
|
355
|
+
"version": cert.version.name,
|
356
|
+
"signature_algorithm": str(cert.signature_algorithm_oid)
|
357
|
+
}
|
358
|
+
|
359
|
+
except Exception as e:
|
360
|
+
self.logger.error(f"Failed to extract certificate info: {e}")
|
361
|
+
return None
|
362
|
+
|
363
|
+
def validate_ssl_config(self) -> List[str]:
|
364
|
+
"""
|
365
|
+
Validate SSL configuration.
|
366
|
+
|
367
|
+
Returns:
|
368
|
+
List of validation errors
|
369
|
+
"""
|
370
|
+
errors = []
|
371
|
+
ssl_config = self.config.get("ssl", {})
|
372
|
+
|
373
|
+
if not ssl_config.get("enabled", False):
|
374
|
+
return errors # SSL disabled, no validation needed
|
375
|
+
|
376
|
+
# Check certificate files if mTLS is configured
|
377
|
+
cert_file = ssl_config.get("cert_file")
|
378
|
+
key_file = ssl_config.get("key_file")
|
379
|
+
|
380
|
+
if cert_file and not os.path.exists(cert_file):
|
381
|
+
errors.append(f"Certificate file not found: {cert_file}")
|
382
|
+
|
383
|
+
if key_file and not os.path.exists(key_file):
|
384
|
+
errors.append(f"Key file not found: {key_file}")
|
385
|
+
|
386
|
+
# Check CA certificate if provided
|
387
|
+
ca_cert_file = ssl_config.get("ca_cert_file")
|
388
|
+
if ca_cert_file and not os.path.exists(ca_cert_file):
|
389
|
+
errors.append(f"CA certificate file not found: {ca_cert_file}")
|
390
|
+
|
391
|
+
# Validate certificate if provided
|
392
|
+
if cert_file and os.path.exists(cert_file):
|
393
|
+
validation_result = self.validate_certificate(cert_file)
|
394
|
+
if not validation_result.get("valid", False):
|
395
|
+
errors.append(f"Certificate validation failed: {validation_result.get('error', 'Unknown error')}")
|
396
|
+
|
397
|
+
return errors
|
398
|
+
|
399
|
+
def get_supported_protocols(self) -> List[str]:
|
400
|
+
"""
|
401
|
+
Get list of supported SSL/TLS protocols.
|
402
|
+
|
403
|
+
Returns:
|
404
|
+
List of supported protocol names
|
405
|
+
"""
|
406
|
+
protocols = []
|
407
|
+
|
408
|
+
try:
|
409
|
+
# Check available protocols
|
410
|
+
if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
|
411
|
+
protocols.append("TLSv1.2")
|
412
|
+
if hasattr(ssl, 'PROTOCOL_TLSv1_3'):
|
413
|
+
protocols.append("TLSv1.3")
|
414
|
+
if hasattr(ssl, 'PROTOCOL_TLS'):
|
415
|
+
protocols.append("TLS")
|
416
|
+
|
417
|
+
# Fallback to basic protocols
|
418
|
+
if not protocols:
|
419
|
+
protocols = ["SSLv23", "TLS"]
|
420
|
+
|
421
|
+
except Exception as e:
|
422
|
+
self.logger.warning(f"Failed to detect supported protocols: {e}")
|
423
|
+
protocols = ["TLS"] # Basic fallback
|
424
|
+
|
425
|
+
return protocols
|
426
|
+
|
427
|
+
|
428
|
+
def create_ssl_manager(config: Dict[str, Any]) -> ClientSSLManager:
|
429
|
+
"""
|
430
|
+
Create SSL/TLS manager from configuration.
|
431
|
+
|
432
|
+
Args:
|
433
|
+
config: Configuration dictionary
|
434
|
+
|
435
|
+
Returns:
|
436
|
+
ClientSSLManager instance
|
437
|
+
"""
|
438
|
+
return ClientSSLManager(config)
|
439
|
+
|
440
|
+
|
441
|
+
def create_ssl_context(config: Dict[str, Any]) -> Optional[ssl.SSLContext]:
|
442
|
+
"""
|
443
|
+
Create SSL context from configuration.
|
444
|
+
|
445
|
+
Args:
|
446
|
+
config: Configuration dictionary
|
447
|
+
|
448
|
+
Returns:
|
449
|
+
SSL context or None if SSL is disabled
|
450
|
+
"""
|
451
|
+
ssl_manager = ClientSSLManager(config)
|
452
|
+
return ssl_manager.create_client_ssl_context()
|
453
|
+
|
454
|
+
|
455
|
+
def create_connector(config: Dict[str, Any]) -> Optional[Any]:
|
456
|
+
"""
|
457
|
+
Create aiohttp connector from configuration.
|
458
|
+
|
459
|
+
Args:
|
460
|
+
config: Configuration dictionary
|
461
|
+
|
462
|
+
Returns:
|
463
|
+
aiohttp connector or None if SSL is disabled
|
464
|
+
"""
|
465
|
+
ssl_manager = ClientSSLManager(config)
|
466
|
+
return ssl_manager.create_connector()
|
@@ -0,0 +1,229 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: embed-client
|
3
|
+
Version: 3.1.0.0
|
4
|
+
Summary: Async client for Embedding Service API with comprehensive authentication, SSL/TLS, and mTLS support
|
5
|
+
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
6
|
+
Requires-Python: >=3.8
|
7
|
+
Description-Content-Type: text/markdown
|
8
|
+
Requires-Dist: aiohttp
|
9
|
+
Requires-Dist: PyJWT>=2.0.0
|
10
|
+
Requires-Dist: cryptography>=3.0.0
|
11
|
+
Requires-Dist: pydantic>=2.0.0
|
12
|
+
Provides-Extra: test
|
13
|
+
Requires-Dist: pytest; extra == "test"
|
14
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
15
|
+
|
16
|
+
# embed-client
|
17
|
+
|
18
|
+
Асинхронный клиент для Embedding Service API с поддержкой всех режимов безопасности.
|
19
|
+
|
20
|
+
## Возможности
|
21
|
+
|
22
|
+
- ✅ **Асинхронный API** - полная поддержка async/await
|
23
|
+
- ✅ **Все режимы безопасности** - HTTP, HTTPS, mTLS
|
24
|
+
- ✅ **Аутентификация** - API Key, JWT, Basic Auth, Certificate
|
25
|
+
- ✅ **SSL/TLS поддержка** - полная интеграция с mcp_security_framework
|
26
|
+
- ✅ **Конфигурация** - файлы конфигурации, переменные окружения, аргументы
|
27
|
+
- ✅ **Обратная совместимость** - API формат не изменился, добавлена только безопасность
|
28
|
+
- ✅ **Типизация** - 100% type-annotated код
|
29
|
+
- ✅ **Тестирование** - 84+ тестов с полным покрытием
|
30
|
+
|
31
|
+
## Quick Start: Примеры запуска
|
32
|
+
|
33
|
+
### Базовое использование
|
34
|
+
|
35
|
+
**Вариант 1: через аргументы командной строки**
|
36
|
+
|
37
|
+
```sh
|
38
|
+
# HTTP без аутентификации
|
39
|
+
python embed_client/example_async_usage.py --base-url http://localhost --port 8001
|
40
|
+
|
41
|
+
# HTTP с API ключом
|
42
|
+
python embed_client/example_async_usage.py --base-url http://localhost --port 8001 \
|
43
|
+
--auth-method api_key --api-key admin_key_123
|
44
|
+
|
45
|
+
# HTTPS с SSL
|
46
|
+
python embed_client/example_async_usage.py --base-url https://localhost --port 9443 \
|
47
|
+
--ssl-verify-mode CERT_REQUIRED
|
48
|
+
|
49
|
+
# mTLS с сертификатами
|
50
|
+
python embed_client/example_async_usage.py --base-url https://localhost --port 9443 \
|
51
|
+
--cert-file certs/client.crt --key-file keys/client.key --ca-cert-file certs/ca.crt
|
52
|
+
```
|
53
|
+
|
54
|
+
**Вариант 2: через переменные окружения**
|
55
|
+
|
56
|
+
```sh
|
57
|
+
export EMBED_CLIENT_BASE_URL=http://localhost
|
58
|
+
export EMBED_CLIENT_PORT=8001
|
59
|
+
export EMBED_CLIENT_AUTH_METHOD=api_key
|
60
|
+
export EMBED_CLIENT_API_KEY=admin_key_123
|
61
|
+
python embed_client/example_async_usage.py
|
62
|
+
```
|
63
|
+
|
64
|
+
**Вариант 3: через файл конфигурации**
|
65
|
+
|
66
|
+
```sh
|
67
|
+
python embed_client/example_async_usage.py --config configs/https_token.json
|
68
|
+
```
|
69
|
+
|
70
|
+
### Режимы безопасности
|
71
|
+
|
72
|
+
#### 1. HTTP (без аутентификации)
|
73
|
+
```python
|
74
|
+
from embed_client.async_client import EmbeddingServiceAsyncClient
|
75
|
+
|
76
|
+
client = EmbeddingServiceAsyncClient(
|
77
|
+
base_url="http://localhost",
|
78
|
+
port=8001
|
79
|
+
)
|
80
|
+
```
|
81
|
+
|
82
|
+
#### 2. HTTP + Token
|
83
|
+
```python
|
84
|
+
from embed_client.config import ClientConfig
|
85
|
+
|
86
|
+
# API Key
|
87
|
+
config = ClientConfig.create_http_token_config(
|
88
|
+
"http://localhost", 8001, {"user": "api_key_123"}
|
89
|
+
)
|
90
|
+
|
91
|
+
# JWT
|
92
|
+
config = ClientConfig.create_http_jwt_config(
|
93
|
+
"http://localhost", 8001, "secret", "username", "password"
|
94
|
+
)
|
95
|
+
|
96
|
+
# Basic Auth
|
97
|
+
config = ClientConfig.create_http_basic_config(
|
98
|
+
"http://localhost", 8001, "username", "password"
|
99
|
+
)
|
100
|
+
```
|
101
|
+
|
102
|
+
#### 3. HTTPS
|
103
|
+
```python
|
104
|
+
config = ClientConfig.create_https_config(
|
105
|
+
"https://localhost", 9443,
|
106
|
+
ca_cert_file="certs/ca.crt"
|
107
|
+
)
|
108
|
+
```
|
109
|
+
|
110
|
+
#### 4. mTLS (взаимная аутентификация)
|
111
|
+
```python
|
112
|
+
config = ClientConfig.create_mtls_config(
|
113
|
+
"https://localhost", 9443,
|
114
|
+
cert_file="certs/client.crt",
|
115
|
+
key_file="keys/client.key",
|
116
|
+
ca_cert_file="certs/ca.crt"
|
117
|
+
)
|
118
|
+
```
|
119
|
+
|
120
|
+
### Программное использование
|
121
|
+
|
122
|
+
```python
|
123
|
+
import asyncio
|
124
|
+
from embed_client.async_client import EmbeddingServiceAsyncClient
|
125
|
+
from embed_client.config import ClientConfig
|
126
|
+
|
127
|
+
async def main():
|
128
|
+
# Создание конфигурации
|
129
|
+
config = ClientConfig.create_http_token_config(
|
130
|
+
"http://localhost", 8001, {"user": "api_key_123"}
|
131
|
+
)
|
132
|
+
|
133
|
+
# Использование клиента
|
134
|
+
async with EmbeddingServiceAsyncClient.from_config(config) as client:
|
135
|
+
# Проверка статуса
|
136
|
+
print(f"Аутентификация: {client.get_auth_method()}")
|
137
|
+
print(f"SSL включен: {client.is_ssl_enabled()}")
|
138
|
+
print(f"mTLS включен: {client.is_mtls_enabled()}")
|
139
|
+
|
140
|
+
# Выполнение запроса
|
141
|
+
result = await client.cmd("embed", params={"texts": ["hello world"]})
|
142
|
+
|
143
|
+
# Извлечение данных
|
144
|
+
embeddings = client.extract_embeddings(result)
|
145
|
+
texts = client.extract_texts(result)
|
146
|
+
tokens = client.extract_tokens(result)
|
147
|
+
bm25_tokens = client.extract_bm25_tokens(result)
|
148
|
+
|
149
|
+
print(f"Эмбеддинги: {embeddings}")
|
150
|
+
print(f"Тексты: {texts}")
|
151
|
+
print(f"Токены: {tokens}")
|
152
|
+
print(f"BM25 токены: {bm25_tokens}")
|
153
|
+
|
154
|
+
if __name__ == "__main__":
|
155
|
+
asyncio.run(main())
|
156
|
+
```
|
157
|
+
|
158
|
+
## Установка
|
159
|
+
|
160
|
+
```bash
|
161
|
+
# Установка из PyPI
|
162
|
+
pip install embed-client
|
163
|
+
|
164
|
+
# Установка в режиме разработки
|
165
|
+
git clone <repository>
|
166
|
+
cd embed-client
|
167
|
+
pip install -e .
|
168
|
+
```
|
169
|
+
|
170
|
+
## Зависимости
|
171
|
+
|
172
|
+
- `aiohttp` - асинхронные HTTP запросы
|
173
|
+
- `PyJWT>=2.0.0` - JWT токены
|
174
|
+
- `cryptography>=3.0.0` - криптография и сертификаты
|
175
|
+
- `pydantic>=2.0.0` - валидация конфигурации
|
176
|
+
|
177
|
+
## Тестирование
|
178
|
+
|
179
|
+
```bash
|
180
|
+
# Запуск всех тестов
|
181
|
+
pytest tests/
|
182
|
+
|
183
|
+
# Запуск тестов с покрытием
|
184
|
+
pytest tests/ --cov=embed_client
|
185
|
+
|
186
|
+
# Запуск конкретных тестов
|
187
|
+
pytest tests/test_async_client.py -v
|
188
|
+
pytest tests/test_config.py -v
|
189
|
+
pytest tests/test_auth.py -v
|
190
|
+
pytest tests/test_ssl_manager.py -v
|
191
|
+
```
|
192
|
+
|
193
|
+
## Документация
|
194
|
+
|
195
|
+
- [Формат API и режимы безопасности](docs/api_format.md)
|
196
|
+
- [Примеры использования](embed_client/example_async_usage.py)
|
197
|
+
- [Примеры на русском](embed_client/example_async_usage_ru.py)
|
198
|
+
|
199
|
+
## Безопасность
|
200
|
+
|
201
|
+
### Рекомендации
|
202
|
+
|
203
|
+
1. **Используйте HTTPS** для продакшена
|
204
|
+
2. **Включите проверку сертификатов** (CERT_REQUIRED)
|
205
|
+
3. **Используйте mTLS** для критически важных систем
|
206
|
+
4. **Регулярно обновляйте сертификаты**
|
207
|
+
5. **Храните приватные ключи в безопасном месте**
|
208
|
+
|
209
|
+
### Поддерживаемые протоколы
|
210
|
+
|
211
|
+
- TLS 1.2
|
212
|
+
- TLS 1.3
|
213
|
+
- SSL 3.0 (устаревший, не рекомендуется)
|
214
|
+
|
215
|
+
## Лицензия
|
216
|
+
|
217
|
+
MIT License
|
218
|
+
|
219
|
+
## Автор
|
220
|
+
|
221
|
+
**Vasiliy Zdanovskiy**
|
222
|
+
Email: vasilyvz@gmail.com
|
223
|
+
|
224
|
+
---
|
225
|
+
|
226
|
+
**Важно:**
|
227
|
+
- Используйте `--base-url` (через дефис), а не `--base_url` (через подчеркивание).
|
228
|
+
- Значение base_url должно содержать `http://` или `https://`.
|
229
|
+
- Аргументы должны быть отдельными (через пробел), а не через `=`.
|