fixes
This commit is contained in:
parent
8b68b0f1e3
commit
274c8f1f09
|
|
@ -46,6 +46,16 @@ class EnhancedSanic(Sanic):
|
||||||
await cache.redis.ping()
|
await cache.redis.ping()
|
||||||
logger.info("Redis cache initialized")
|
logger.info("Redis cache initialized")
|
||||||
|
|
||||||
|
# Initialize ed25519 cryptographic module
|
||||||
|
try:
|
||||||
|
from app.core.crypto import init_ed25519_manager
|
||||||
|
await init_ed25519_manager()
|
||||||
|
logger.info("Ed25519 cryptographic module initialized")
|
||||||
|
except ImportError:
|
||||||
|
logger.warning("Ed25519 module not available")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to initialize ed25519 module", error=str(e))
|
||||||
|
|
||||||
# Run custom startup tasks
|
# Run custom startup tasks
|
||||||
for task in self.ctx.startup_tasks:
|
for task in self.ctx.startup_tasks:
|
||||||
try:
|
try:
|
||||||
|
|
@ -232,6 +242,9 @@ def register_routes():
|
||||||
from app.api.routes.storage_routes import storage_bp
|
from app.api.routes.storage_routes import storage_bp
|
||||||
from app.api.routes.blockchain_routes import blockchain_bp
|
from app.api.routes.blockchain_routes import blockchain_bp
|
||||||
|
|
||||||
|
# Import node communication blueprint
|
||||||
|
from app.api.node_communication import node_bp
|
||||||
|
|
||||||
# Импортировать существующие маршруты
|
# Импортировать существующие маршруты
|
||||||
try:
|
try:
|
||||||
from app.api.routes._system import bp as system_bp
|
from app.api.routes._system import bp as system_bp
|
||||||
|
|
@ -248,6 +261,7 @@ def register_routes():
|
||||||
app.blueprint(content_bp)
|
app.blueprint(content_bp)
|
||||||
app.blueprint(storage_bp)
|
app.blueprint(storage_bp)
|
||||||
app.blueprint(blockchain_bp)
|
app.blueprint(blockchain_bp)
|
||||||
|
app.blueprint(node_bp) # Межузловое общение с ed25519
|
||||||
|
|
||||||
# Register optional blueprints
|
# Register optional blueprints
|
||||||
if user_bp:
|
if user_bp:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Enhanced API middleware with security, rate limiting, and monitoring
|
Enhanced API middleware with security, rate limiting, monitoring and ed25519 signatures
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import time
|
||||||
|
|
@ -24,6 +24,13 @@ from app.core.logging import request_id_var, user_id_var, operation_var, log_per
|
||||||
from app.core.models.user import User
|
from app.core.models.user import User
|
||||||
from app.core.models.base import BaseModel
|
from app.core.models.base import BaseModel
|
||||||
|
|
||||||
|
# Ed25519 криптографический модуль
|
||||||
|
try:
|
||||||
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
CRYPTO_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
CRYPTO_AVAILABLE = False
|
||||||
|
|
||||||
logger = structlog.get_logger(__name__)
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -266,6 +273,98 @@ class AuthenticationMiddleware:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class CryptographicMiddleware:
|
||||||
|
"""Ed25519 cryptographic middleware for inter-node communication"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def verify_inter_node_signature(request: Request) -> bool:
|
||||||
|
"""Проверить ed25519 подпись для межузлового сообщения"""
|
||||||
|
if not CRYPTO_AVAILABLE:
|
||||||
|
logger.warning("Crypto module not available, skipping signature verification")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Проверяем, является ли это межузловым сообщением
|
||||||
|
if not request.headers.get("X-Node-Communication") == "true":
|
||||||
|
return True # Не межузловое сообщение, пропускаем проверку
|
||||||
|
|
||||||
|
try:
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
# Получаем необходимые заголовки
|
||||||
|
signature = request.headers.get("X-Node-Signature")
|
||||||
|
node_id = request.headers.get("X-Node-ID")
|
||||||
|
public_key = request.headers.get("X-Node-Public-Key")
|
||||||
|
|
||||||
|
if not all([signature, node_id, public_key]):
|
||||||
|
logger.warning("Missing cryptographic headers in inter-node request")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Читаем тело сообщения для проверки подписи
|
||||||
|
if hasattr(request, 'body') and request.body:
|
||||||
|
try:
|
||||||
|
message_data = json.loads(request.body.decode())
|
||||||
|
|
||||||
|
# Проверяем подпись
|
||||||
|
is_valid = crypto_manager.verify_signature(
|
||||||
|
message_data, signature, public_key
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
logger.debug(f"Valid signature verified for node {node_id}")
|
||||||
|
# Сохраняем информацию о ноде в контексте
|
||||||
|
request.ctx.inter_node_communication = True
|
||||||
|
request.ctx.source_node_id = node_id
|
||||||
|
request.ctx.source_public_key = public_key
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"Invalid signature from node {node_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.warning("Invalid JSON in inter-node request")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logger.warning("Empty body in inter-node request")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Crypto verification error: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def add_inter_node_headers(request: Request, response: HTTPResponse) -> HTTPResponse:
|
||||||
|
"""Добавить криптографические заголовки для межузловых ответов"""
|
||||||
|
if not CRYPTO_AVAILABLE:
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Добавляем заголовки только для межузловых сообщений
|
||||||
|
if hasattr(request.ctx, 'inter_node_communication') and request.ctx.inter_node_communication:
|
||||||
|
try:
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
# Добавляем информацию о нашей ноде
|
||||||
|
response.headers.update({
|
||||||
|
"X-Node-ID": crypto_manager.node_id,
|
||||||
|
"X-Node-Public-Key": crypto_manager.public_key_hex,
|
||||||
|
"X-Node-Communication": "true"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Если есть тело ответа, подписываем его
|
||||||
|
if response.body:
|
||||||
|
try:
|
||||||
|
response_data = json.loads(response.body.decode())
|
||||||
|
signature = crypto_manager.sign_message(response_data)
|
||||||
|
response.headers["X-Node-Signature"] = signature
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Не JSON тело, пропускаем подпись
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error adding inter-node headers: {e}")
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class RequestContextMiddleware:
|
class RequestContextMiddleware:
|
||||||
"""Request context middleware for tracking and logging"""
|
"""Request context middleware for tracking and logging"""
|
||||||
|
|
||||||
|
|
@ -338,6 +437,7 @@ security_middleware = SecurityMiddleware()
|
||||||
rate_limit_middleware = RateLimitMiddleware()
|
rate_limit_middleware = RateLimitMiddleware()
|
||||||
auth_middleware = AuthenticationMiddleware()
|
auth_middleware = AuthenticationMiddleware()
|
||||||
context_middleware = RequestContextMiddleware()
|
context_middleware = RequestContextMiddleware()
|
||||||
|
crypto_middleware = CryptographicMiddleware()
|
||||||
|
|
||||||
|
|
||||||
async def request_middleware(request: Request):
|
async def request_middleware(request: Request):
|
||||||
|
|
@ -351,6 +451,15 @@ async def request_middleware(request: Request):
|
||||||
# Add request context
|
# Add request context
|
||||||
await context_middleware.add_request_context(request)
|
await context_middleware.add_request_context(request)
|
||||||
|
|
||||||
|
# Cryptographic signature verification for inter-node communication
|
||||||
|
if not await crypto_middleware.verify_inter_node_signature(request):
|
||||||
|
logger.warning("Inter-node signature verification failed")
|
||||||
|
response = json_response({
|
||||||
|
"error": "Invalid cryptographic signature",
|
||||||
|
"message": "Inter-node communication requires valid ed25519 signature"
|
||||||
|
}, status=403)
|
||||||
|
return security_middleware.add_security_headers(response)
|
||||||
|
|
||||||
# Security validations
|
# Security validations
|
||||||
try:
|
try:
|
||||||
security_middleware.validate_request_size(request)
|
security_middleware.validate_request_size(request)
|
||||||
|
|
@ -423,6 +532,9 @@ async def response_middleware(request: Request, response: HTTPResponse):
|
||||||
# Add security headers
|
# Add security headers
|
||||||
response = security_middleware.add_security_headers(response)
|
response = security_middleware.add_security_headers(response)
|
||||||
|
|
||||||
|
# Add cryptographic headers for inter-node communication
|
||||||
|
response = await crypto_middleware.add_inter_node_headers(request, response)
|
||||||
|
|
||||||
# Add rate limit headers
|
# Add rate limit headers
|
||||||
if hasattr(request.ctx, 'rate_limit_info') and request.ctx.rate_limit_info:
|
if hasattr(request.ctx, 'rate_limit_info') and request.ctx.rate_limit_info:
|
||||||
rate_info = request.ctx.rate_limit_info
|
rate_info = request.ctx.rate_limit_info
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,378 @@
|
||||||
|
"""
|
||||||
|
API endpoints для межузлового общения с ed25519 подписями
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sanic import Blueprint, Request
|
||||||
|
from sanic.response import json as json_response
|
||||||
|
|
||||||
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
from app.core.logging import get_logger
|
||||||
|
from app.api.middleware import auth_required, validate_json
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# Blueprint для межузловых коммуникаций
|
||||||
|
node_bp = Blueprint("node", url_prefix="/api/node")
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_node_request(request: Request) -> Dict[str, Any]:
|
||||||
|
"""Валидация межузлового запроса с обязательной проверкой подписи"""
|
||||||
|
# Проверяем наличие обязательных заголовков
|
||||||
|
required_headers = ["X-Node-Communication", "X-Node-ID", "X-Node-Public-Key", "X-Node-Signature"]
|
||||||
|
for header in required_headers:
|
||||||
|
if not request.headers.get(header):
|
||||||
|
raise ValueError(f"Missing required header: {header}")
|
||||||
|
|
||||||
|
# Проверяем, что это межузловое общение
|
||||||
|
if request.headers.get("X-Node-Communication") != "true":
|
||||||
|
raise ValueError("Not a valid inter-node communication")
|
||||||
|
|
||||||
|
# Информация о ноде уже проверена в middleware
|
||||||
|
node_id = request.ctx.source_node_id
|
||||||
|
public_key = request.ctx.source_public_key
|
||||||
|
|
||||||
|
# Получаем данные сообщения
|
||||||
|
if not hasattr(request, 'json') or not request.json:
|
||||||
|
raise ValueError("Empty message body")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"node_id": node_id,
|
||||||
|
"public_key": public_key,
|
||||||
|
"message": request.json
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def create_node_response(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Создать ответ для межузлового общения с подписью"""
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
# Добавляем информацию о нашей ноде
|
||||||
|
response_data = {
|
||||||
|
"success": True,
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
"node_id": crypto_manager.node_id,
|
||||||
|
"data": data
|
||||||
|
}
|
||||||
|
|
||||||
|
return response_data
|
||||||
|
|
||||||
|
|
||||||
|
@node_bp.route("/handshake", methods=["POST"])
|
||||||
|
async def node_handshake(request: Request):
|
||||||
|
"""
|
||||||
|
Обработка хэндшейка между нодами
|
||||||
|
|
||||||
|
Ожидаемый формат сообщения:
|
||||||
|
{
|
||||||
|
"action": "handshake",
|
||||||
|
"node_info": {
|
||||||
|
"node_id": "...",
|
||||||
|
"version": "...",
|
||||||
|
"capabilities": [...],
|
||||||
|
"network_info": {...}
|
||||||
|
},
|
||||||
|
"timestamp": "..."
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Валидация межузлового запроса
|
||||||
|
node_data = await validate_node_request(request)
|
||||||
|
message = node_data["message"]
|
||||||
|
source_node_id = node_data["node_id"]
|
||||||
|
|
||||||
|
logger.info(f"Handshake request from node {source_node_id}")
|
||||||
|
|
||||||
|
# Проверяем формат сообщения хэндшейка
|
||||||
|
if message.get("action") != "handshake":
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Invalid handshake message format"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
node_info = message.get("node_info", {})
|
||||||
|
if not node_info.get("node_id") or not node_info.get("version"):
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Missing required node information"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Создаем информацию о нашей ноде для ответа
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
our_node_info = {
|
||||||
|
"node_id": crypto_manager.node_id,
|
||||||
|
"version": "3.0.0", # Версия MY Network
|
||||||
|
"capabilities": [
|
||||||
|
"content_upload",
|
||||||
|
"content_sync",
|
||||||
|
"decentralized_filtering",
|
||||||
|
"ed25519_signatures"
|
||||||
|
],
|
||||||
|
"network_info": {
|
||||||
|
"public_key": crypto_manager.public_key_hex,
|
||||||
|
"protocol_version": "1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сохраняем информацию о ноде (здесь можно добавить в базу данных)
|
||||||
|
logger.info(f"Successful handshake with node {source_node_id}",
|
||||||
|
extra={"peer_node_info": node_info})
|
||||||
|
|
||||||
|
response_data = await create_node_response({
|
||||||
|
"handshake_accepted": True,
|
||||||
|
"node_info": our_node_info
|
||||||
|
})
|
||||||
|
|
||||||
|
return json_response(response_data)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid handshake request: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Handshake error: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Internal server error"
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@node_bp.route("/content/sync", methods=["POST"])
|
||||||
|
async def content_sync(request: Request):
|
||||||
|
"""
|
||||||
|
Синхронизация контента между нодами
|
||||||
|
|
||||||
|
Ожидаемый формат сообщения:
|
||||||
|
{
|
||||||
|
"action": "content_sync",
|
||||||
|
"sync_type": "new_content|content_list|content_request",
|
||||||
|
"content_info": {...},
|
||||||
|
"timestamp": "..."
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Валидация межузлового запроса
|
||||||
|
node_data = await validate_node_request(request)
|
||||||
|
message = node_data["message"]
|
||||||
|
source_node_id = node_data["node_id"]
|
||||||
|
|
||||||
|
logger.info(f"Content sync request from node {source_node_id}")
|
||||||
|
|
||||||
|
# Проверяем формат сообщения синхронизации
|
||||||
|
if message.get("action") != "content_sync":
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Invalid sync message format"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
sync_type = message.get("sync_type")
|
||||||
|
content_info = message.get("content_info", {})
|
||||||
|
|
||||||
|
if sync_type == "new_content":
|
||||||
|
# Обработка нового контента от другой ноды
|
||||||
|
content_hash = content_info.get("hash")
|
||||||
|
if not content_hash:
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Missing content hash"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Здесь добавить логику обработки нового контента
|
||||||
|
# через decentralized_filter и content_storage_manager
|
||||||
|
|
||||||
|
response_data = await create_node_response({
|
||||||
|
"sync_result": "content_accepted",
|
||||||
|
"content_hash": content_hash
|
||||||
|
})
|
||||||
|
|
||||||
|
elif sync_type == "content_list":
|
||||||
|
# Запрос списка доступного контента
|
||||||
|
# Здесь добавить логику получения списка контента
|
||||||
|
|
||||||
|
response_data = await create_node_response({
|
||||||
|
"content_list": [], # Заглушка - добавить реальный список
|
||||||
|
"total_items": 0
|
||||||
|
})
|
||||||
|
|
||||||
|
elif sync_type == "content_request":
|
||||||
|
# Запрос конкретного контента
|
||||||
|
requested_hash = content_info.get("hash")
|
||||||
|
if not requested_hash:
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Missing content hash for request"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Здесь добавить логику поиска и передачи контента
|
||||||
|
|
||||||
|
response_data = await create_node_response({
|
||||||
|
"content_found": False, # Заглушка - добавить реальную проверку
|
||||||
|
"content_hash": requested_hash
|
||||||
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": f"Unknown sync type: {sync_type}"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
return json_response(response_data)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid sync request: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Content sync error: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Internal server error"
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@node_bp.route("/network/ping", methods=["POST"])
|
||||||
|
async def network_ping(request: Request):
|
||||||
|
"""
|
||||||
|
Пинг между нодами для проверки доступности
|
||||||
|
|
||||||
|
Ожидаемый формат сообщения:
|
||||||
|
{
|
||||||
|
"action": "ping",
|
||||||
|
"timestamp": "...",
|
||||||
|
"data": {...}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Валидация межузлового запроса
|
||||||
|
node_data = await validate_node_request(request)
|
||||||
|
message = node_data["message"]
|
||||||
|
source_node_id = node_data["node_id"]
|
||||||
|
|
||||||
|
logger.debug(f"Ping from node {source_node_id}")
|
||||||
|
|
||||||
|
# Проверяем формат пинга
|
||||||
|
if message.get("action") != "ping":
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Invalid ping message format"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Создаем ответ pong
|
||||||
|
response_data = await create_node_response({
|
||||||
|
"action": "pong",
|
||||||
|
"ping_timestamp": message.get("timestamp"),
|
||||||
|
"response_timestamp": datetime.utcnow().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
return json_response(response_data)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid ping request: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ping error: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Internal server error"
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@node_bp.route("/network/status", methods=["GET"])
|
||||||
|
async def network_status(request: Request):
|
||||||
|
"""
|
||||||
|
Получение статуса ноды (без обязательной подписи для GET запросов)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
status_data = {
|
||||||
|
"node_id": crypto_manager.node_id,
|
||||||
|
"public_key": crypto_manager.public_key_hex,
|
||||||
|
"version": "3.0.0",
|
||||||
|
"status": "active",
|
||||||
|
"capabilities": [
|
||||||
|
"content_upload",
|
||||||
|
"content_sync",
|
||||||
|
"decentralized_filtering",
|
||||||
|
"ed25519_signatures"
|
||||||
|
],
|
||||||
|
"timestamp": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_response({
|
||||||
|
"success": True,
|
||||||
|
"data": status_data
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Status error: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Internal server error"
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
@node_bp.route("/network/discover", methods=["POST"])
|
||||||
|
async def network_discover(request: Request):
|
||||||
|
"""
|
||||||
|
Обнаружение и обмен информацией о других нодах в сети
|
||||||
|
|
||||||
|
Ожидаемый формат сообщения:
|
||||||
|
{
|
||||||
|
"action": "discover",
|
||||||
|
"known_nodes": [...],
|
||||||
|
"timestamp": "..."
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Валидация межузлового запроса
|
||||||
|
node_data = await validate_node_request(request)
|
||||||
|
message = node_data["message"]
|
||||||
|
source_node_id = node_data["node_id"]
|
||||||
|
|
||||||
|
logger.info(f"Discovery request from node {source_node_id}")
|
||||||
|
|
||||||
|
# Проверяем формат сообщения
|
||||||
|
if message.get("action") != "discover":
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Invalid discovery message format"
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
known_nodes = message.get("known_nodes", [])
|
||||||
|
|
||||||
|
# Здесь добавить логику обработки информации о известных нодах
|
||||||
|
# и возврат информации о наших известных нодах
|
||||||
|
|
||||||
|
response_data = await create_node_response({
|
||||||
|
"known_nodes": [], # Заглушка - добавить реальный список
|
||||||
|
"discovery_timestamp": datetime.utcnow().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
return json_response(response_data)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid discovery request: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Discovery error: {e}")
|
||||||
|
return json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": "Internal server error"
|
||||||
|
}, status=500)
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""
|
||||||
|
MY Network v3.0 - Cryptographic Module for uploader-bot
|
||||||
|
|
||||||
|
Модуль криптографических операций для защиты inter-node коммуникаций.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .ed25519_manager import Ed25519Manager, get_ed25519_manager, init_ed25519_manager
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'Ed25519Manager',
|
||||||
|
'get_ed25519_manager',
|
||||||
|
'init_ed25519_manager'
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
"""
|
||||||
|
MY Network v3.0 - Ed25519 Cryptographic Manager for uploader-bot
|
||||||
|
|
||||||
|
Модуль для работы с ed25519 ключами и подписями.
|
||||||
|
Все inter-node сообщения должны быть подписаны и проверены.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
from typing import Dict, Any, Optional, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ed25519
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import ed25519 as crypto_ed25519
|
||||||
|
except ImportError as e:
|
||||||
|
logging.error(f"Required cryptographic libraries not found: {e}")
|
||||||
|
raise ImportError("Please install: pip install ed25519 cryptography")
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Ed25519Manager:
|
||||||
|
"""Менеджер для ed25519 криптографических операций в uploader-bot"""
|
||||||
|
|
||||||
|
def __init__(self, private_key_path: Optional[str] = None, public_key_path: Optional[str] = None):
|
||||||
|
"""
|
||||||
|
Инициализация Ed25519Manager
|
||||||
|
|
||||||
|
Args:
|
||||||
|
private_key_path: Путь к приватному ключу
|
||||||
|
public_key_path: Путь к публичному ключу
|
||||||
|
"""
|
||||||
|
self.private_key_path = private_key_path or os.getenv('NODE_PRIVATE_KEY_PATH')
|
||||||
|
self.public_key_path = public_key_path or os.getenv('NODE_PUBLIC_KEY_PATH')
|
||||||
|
|
||||||
|
self._private_key = None
|
||||||
|
self._public_key = None
|
||||||
|
self._node_id = None
|
||||||
|
|
||||||
|
# Загружаем ключи при инициализации
|
||||||
|
self._load_keys()
|
||||||
|
|
||||||
|
def _load_keys(self) -> None:
|
||||||
|
"""Загрузка ключей из файлов"""
|
||||||
|
try:
|
||||||
|
# Загрузка приватного ключа
|
||||||
|
if self.private_key_path and os.path.exists(self.private_key_path):
|
||||||
|
with open(self.private_key_path, 'rb') as f:
|
||||||
|
private_key_data = f.read()
|
||||||
|
|
||||||
|
# Загружаем PEM ключ
|
||||||
|
self._private_key = serialization.load_pem_private_key(
|
||||||
|
private_key_data,
|
||||||
|
password=None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Получаем публичный ключ из приватного
|
||||||
|
self._public_key = self._private_key.public_key()
|
||||||
|
|
||||||
|
# Генерируем NODE_ID из публичного ключа
|
||||||
|
self._node_id = self._generate_node_id()
|
||||||
|
|
||||||
|
logger.info(f"Ed25519 ключи загружены. Node ID: {self._node_id}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning(f"Private key file not found: {self.private_key_path}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error loading Ed25519 keys: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _generate_node_id(self) -> str:
|
||||||
|
"""Генерация NODE_ID из публичного ключа"""
|
||||||
|
if not self._public_key:
|
||||||
|
raise ValueError("Public key not loaded")
|
||||||
|
|
||||||
|
# Получаем raw bytes публичного ключа
|
||||||
|
public_key_bytes = self._public_key.public_bytes(
|
||||||
|
encoding=serialization.Encoding.Raw,
|
||||||
|
format=serialization.PublicFormat.Raw
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем упрощенный base58-подобный NODE_ID
|
||||||
|
# В реальной реализации здесь должен быть полный base58
|
||||||
|
hex_key = public_key_bytes.hex()
|
||||||
|
return f"node-{hex_key[:16]}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_id(self) -> str:
|
||||||
|
"""Получить NODE_ID"""
|
||||||
|
if not self._node_id:
|
||||||
|
raise ValueError("Node ID not generated. Check if keys are loaded.")
|
||||||
|
return self._node_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def public_key_hex(self) -> str:
|
||||||
|
"""Получить публичный ключ в hex формате"""
|
||||||
|
if not self._public_key:
|
||||||
|
raise ValueError("Public key not loaded")
|
||||||
|
|
||||||
|
public_key_bytes = self._public_key.public_bytes(
|
||||||
|
encoding=serialization.Encoding.Raw,
|
||||||
|
format=serialization.PublicFormat.Raw
|
||||||
|
)
|
||||||
|
return public_key_bytes.hex()
|
||||||
|
|
||||||
|
def sign_message(self, message: Dict[str, Any]) -> str:
|
||||||
|
"""
|
||||||
|
Подписать сообщение ed25519 ключом
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Словарь с данными для подписи
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
base64-encoded подпись
|
||||||
|
"""
|
||||||
|
if not self._private_key:
|
||||||
|
raise ValueError("Private key not loaded")
|
||||||
|
|
||||||
|
# Сериализуем сообщение в JSON для подписи
|
||||||
|
message_json = json.dumps(message, sort_keys=True, ensure_ascii=False)
|
||||||
|
message_bytes = message_json.encode('utf-8')
|
||||||
|
|
||||||
|
# Создаем хеш сообщения для подписи
|
||||||
|
message_hash = hashlib.sha256(message_bytes).digest()
|
||||||
|
|
||||||
|
# Подписываем хеш
|
||||||
|
signature = self._private_key.sign(message_hash)
|
||||||
|
|
||||||
|
# Возвращаем подпись в base64
|
||||||
|
return base64.b64encode(signature).decode('ascii')
|
||||||
|
|
||||||
|
def verify_signature(self, message: Dict[str, Any], signature: str, public_key_hex: str) -> bool:
|
||||||
|
"""
|
||||||
|
Проверить подпись сообщения
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Словарь с данными
|
||||||
|
signature: base64-encoded подпись
|
||||||
|
public_key_hex: Публичный ключ в hex формате
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True если подпись валидна
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Восстанавливаем публичный ключ из hex
|
||||||
|
public_key_bytes = bytes.fromhex(public_key_hex)
|
||||||
|
public_key = crypto_ed25519.Ed25519PublicKey.from_public_bytes(public_key_bytes)
|
||||||
|
|
||||||
|
# Сериализуем сообщение так же как при подписи
|
||||||
|
message_json = json.dumps(message, sort_keys=True, ensure_ascii=False)
|
||||||
|
message_bytes = message_json.encode('utf-8')
|
||||||
|
message_hash = hashlib.sha256(message_bytes).digest()
|
||||||
|
|
||||||
|
# Декодируем подпись
|
||||||
|
signature_bytes = base64.b64decode(signature.encode('ascii'))
|
||||||
|
|
||||||
|
# Проверяем подпись
|
||||||
|
public_key.verify(signature_bytes, message_hash)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Signature verification failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_signed_message(self, message_type: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Создать подписанное сообщение для отправки
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message_type: Тип сообщения (handshake, sync_request, etc.)
|
||||||
|
data: Данные сообщения
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Подписанное сообщение
|
||||||
|
"""
|
||||||
|
# Основная структура сообщения
|
||||||
|
message = {
|
||||||
|
"type": message_type,
|
||||||
|
"node_id": self.node_id,
|
||||||
|
"public_key": self.public_key_hex,
|
||||||
|
"timestamp": int(time.time()),
|
||||||
|
"data": data
|
||||||
|
}
|
||||||
|
|
||||||
|
# Подписываем сообщение
|
||||||
|
signature = self.sign_message(message)
|
||||||
|
|
||||||
|
# Добавляем подпись
|
||||||
|
signed_message = message.copy()
|
||||||
|
signed_message["signature"] = signature
|
||||||
|
|
||||||
|
return signed_message
|
||||||
|
|
||||||
|
def verify_incoming_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
|
||||||
|
"""
|
||||||
|
Проверить входящее подписанное сообщение
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Входящее сообщение
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(is_valid, error_message)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Проверяем обязательные поля
|
||||||
|
required_fields = ["type", "node_id", "public_key", "timestamp", "data", "signature"]
|
||||||
|
for field in required_fields:
|
||||||
|
if field not in message:
|
||||||
|
return False, f"Missing required field: {field}"
|
||||||
|
|
||||||
|
# Извлекаем подпись и создаем сообщение без подписи для проверки
|
||||||
|
signature = message.pop("signature")
|
||||||
|
|
||||||
|
# Проверяем подпись
|
||||||
|
is_valid = self.verify_signature(message, signature, message["public_key"])
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
return False, "Invalid signature"
|
||||||
|
|
||||||
|
# Проверяем временную метку (не старше 5 минут)
|
||||||
|
current_time = int(time.time())
|
||||||
|
if abs(current_time - message["timestamp"]) > 300:
|
||||||
|
return False, "Message timestamp too old"
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"Verification error: {str(e)}"
|
||||||
|
|
||||||
|
def create_handshake_message(self, target_node_id: str, additional_data: Optional[Dict] = None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Создать сообщение для handshake с другой нодой
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_node_id: ID целевой ноды
|
||||||
|
additional_data: Дополнительные данные
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Подписанное handshake сообщение
|
||||||
|
"""
|
||||||
|
handshake_data = {
|
||||||
|
"target_node_id": target_node_id,
|
||||||
|
"protocol_version": "3.0",
|
||||||
|
"node_type": os.getenv("NODE_TYPE", "uploader"),
|
||||||
|
"capabilities": ["upload", "content_streaming", "conversion", "storage"]
|
||||||
|
}
|
||||||
|
|
||||||
|
if additional_data:
|
||||||
|
handshake_data.update(additional_data)
|
||||||
|
|
||||||
|
return self.create_signed_message("handshake", handshake_data)
|
||||||
|
|
||||||
|
def create_upload_message(self, content_hash: str, metadata: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Создать подписанное сообщение для загрузки контента
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content_hash: Хеш контента
|
||||||
|
metadata: Метаданные файла
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Подписанное upload сообщение
|
||||||
|
"""
|
||||||
|
upload_data = {
|
||||||
|
"content_hash": content_hash,
|
||||||
|
"metadata": metadata,
|
||||||
|
"uploader_node": self.node_id,
|
||||||
|
"upload_timestamp": int(time.time())
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.create_signed_message("content_upload", upload_data)
|
||||||
|
|
||||||
|
def create_sync_message(self, content_list: list, operation: str = "announce") -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Создать сообщение для синхронизации контента
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content_list: Список контента для синхронизации
|
||||||
|
operation: Тип операции (announce, request, response)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Подписанное sync сообщение
|
||||||
|
"""
|
||||||
|
sync_data = {
|
||||||
|
"operation": operation,
|
||||||
|
"content_list": content_list,
|
||||||
|
"sync_id": hashlib.sha256(
|
||||||
|
(self.node_id + str(int(time.time()))).encode()
|
||||||
|
).hexdigest()[:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.create_signed_message("content_sync", sync_data)
|
||||||
|
|
||||||
|
|
||||||
|
# Глобальный экземпляр менеджера
|
||||||
|
_ed25519_manager = None
|
||||||
|
|
||||||
|
def get_ed25519_manager() -> Ed25519Manager:
|
||||||
|
"""Получить глобальный экземпляр Ed25519Manager"""
|
||||||
|
global _ed25519_manager
|
||||||
|
if _ed25519_manager is None:
|
||||||
|
_ed25519_manager = Ed25519Manager()
|
||||||
|
return _ed25519_manager
|
||||||
|
|
||||||
|
def init_ed25519_manager(private_key_path: str, public_key_path: str) -> Ed25519Manager:
|
||||||
|
"""Инициализировать Ed25519Manager с путями к ключам"""
|
||||||
|
global _ed25519_manager
|
||||||
|
_ed25519_manager = Ed25519Manager(private_key_path, public_key_path)
|
||||||
|
return _ed25519_manager
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
"""
|
||||||
|
Клиент для межузлового общения с ed25519 подписями
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import aiohttp
|
||||||
|
from typing import Dict, Any, Optional, List
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
from app.core.logging import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class NodeClient:
|
||||||
|
"""Клиент для подписанного межузлового общения"""
|
||||||
|
|
||||||
|
def __init__(self, timeout: int = 30):
|
||||||
|
self.timeout = aiohttp.ClientTimeout(total=timeout)
|
||||||
|
self.session: Optional[aiohttp.ClientSession] = None
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
"""Async context manager entry"""
|
||||||
|
self.session = aiohttp.ClientSession(timeout=self.timeout)
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""Async context manager exit"""
|
||||||
|
if self.session:
|
||||||
|
await self.session.close()
|
||||||
|
|
||||||
|
async def _create_signed_request(
|
||||||
|
self,
|
||||||
|
action: str,
|
||||||
|
data: Dict[str, Any],
|
||||||
|
target_url: str
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Создать подписанный запрос для межузлового общения
|
||||||
|
|
||||||
|
Args:
|
||||||
|
action: Тип действия (handshake, content_sync, ping, etc.)
|
||||||
|
data: Данные сообщения
|
||||||
|
target_url: URL целевой ноды
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Заголовки и тело запроса
|
||||||
|
"""
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
# Создаем сообщение
|
||||||
|
message = {
|
||||||
|
"action": action,
|
||||||
|
"timestamp": datetime.utcnow().isoformat(),
|
||||||
|
**data
|
||||||
|
}
|
||||||
|
|
||||||
|
# Подписываем сообщение
|
||||||
|
signature = crypto_manager.sign_message(message)
|
||||||
|
|
||||||
|
# Создаем заголовки
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-Node-Communication": "true",
|
||||||
|
"X-Node-ID": crypto_manager.node_id,
|
||||||
|
"X-Node-Public-Key": crypto_manager.public_key_hex,
|
||||||
|
"X-Node-Signature": signature
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"headers": headers,
|
||||||
|
"json": message
|
||||||
|
}
|
||||||
|
|
||||||
|
async def send_handshake(
|
||||||
|
self,
|
||||||
|
target_url: str,
|
||||||
|
our_node_info: Dict[str, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Отправить хэндшейк ноде
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_url: URL целевой ноды (например, "http://node.example.com:8000")
|
||||||
|
our_node_info: Информация о нашей ноде
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Ответ от ноды или информация об ошибке
|
||||||
|
"""
|
||||||
|
endpoint_url = urljoin(target_url, "/api/node/handshake")
|
||||||
|
|
||||||
|
try:
|
||||||
|
request_data = await self._create_signed_request(
|
||||||
|
"handshake",
|
||||||
|
{"node_info": our_node_info},
|
||||||
|
target_url
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Sending handshake to {target_url}")
|
||||||
|
|
||||||
|
async with self.session.post(endpoint_url, **request_data) as response:
|
||||||
|
response_data = await response.json()
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
logger.info(f"Handshake successful with {target_url}")
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.warning(f"Handshake failed with {target_url}: {response.status}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP {response.status}",
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
logger.warning(f"Handshake timeout with {target_url}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "timeout",
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Handshake error with {target_url}: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async def send_content_sync(
|
||||||
|
self,
|
||||||
|
target_url: str,
|
||||||
|
sync_type: str,
|
||||||
|
content_info: Dict[str, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Отправить запрос синхронизации контента
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_url: URL целевой ноды
|
||||||
|
sync_type: Тип синхронизации (new_content, content_list, content_request)
|
||||||
|
content_info: Информация о контенте
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Ответ от ноды
|
||||||
|
"""
|
||||||
|
endpoint_url = urljoin(target_url, "/api/node/content/sync")
|
||||||
|
|
||||||
|
try:
|
||||||
|
request_data = await self._create_signed_request(
|
||||||
|
"content_sync",
|
||||||
|
{
|
||||||
|
"sync_type": sync_type,
|
||||||
|
"content_info": content_info
|
||||||
|
},
|
||||||
|
target_url
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Sending content sync ({sync_type}) to {target_url}")
|
||||||
|
|
||||||
|
async with self.session.post(endpoint_url, **request_data) as response:
|
||||||
|
response_data = await response.json()
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
logger.debug(f"Content sync successful with {target_url}")
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.warning(f"Content sync failed with {target_url}: {response.status}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP {response.status}",
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Content sync error with {target_url}: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async def send_ping(self, target_url: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Отправить пинг ноде
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_url: URL целевой ноды
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Ответ от ноды (pong)
|
||||||
|
"""
|
||||||
|
endpoint_url = urljoin(target_url, "/api/node/network/ping")
|
||||||
|
|
||||||
|
try:
|
||||||
|
request_data = await self._create_signed_request(
|
||||||
|
"ping",
|
||||||
|
{"data": {"test": True}},
|
||||||
|
target_url
|
||||||
|
)
|
||||||
|
|
||||||
|
start_time = datetime.utcnow()
|
||||||
|
|
||||||
|
async with self.session.post(endpoint_url, **request_data) as response:
|
||||||
|
end_time = datetime.utcnow()
|
||||||
|
duration = (end_time - start_time).total_seconds() * 1000 # ms
|
||||||
|
|
||||||
|
response_data = await response.json()
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": response_data,
|
||||||
|
"latency_ms": round(duration, 2),
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP {response.status}",
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ping error with {target_url}: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_node_status(self, target_url: str) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Получить статус ноды (GET запрос без подписи)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_url: URL целевой ноды
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Статус ноды
|
||||||
|
"""
|
||||||
|
endpoint_url = urljoin(target_url, "/api/node/network/status")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with self.session.get(endpoint_url) as response:
|
||||||
|
response_data = await response.json()
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP {response.status}",
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Status request error with {target_url}: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
async def send_discovery(
|
||||||
|
self,
|
||||||
|
target_url: str,
|
||||||
|
known_nodes: List[Dict[str, Any]]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Отправить запрос обнаружения нод
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_url: URL целевой ноды
|
||||||
|
known_nodes: Список известных нам нод
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список нод от целевой ноды
|
||||||
|
"""
|
||||||
|
endpoint_url = urljoin(target_url, "/api/node/network/discover")
|
||||||
|
|
||||||
|
try:
|
||||||
|
request_data = await self._create_signed_request(
|
||||||
|
"discover",
|
||||||
|
{"known_nodes": known_nodes},
|
||||||
|
target_url
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Sending discovery request to {target_url}")
|
||||||
|
|
||||||
|
async with self.session.post(endpoint_url, **request_data) as response:
|
||||||
|
response_data = await response.json()
|
||||||
|
|
||||||
|
if response.status == 200:
|
||||||
|
logger.debug(f"Discovery successful with {target_url}")
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
logger.warning(f"Discovery failed with {target_url}: {response.status}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": f"HTTP {response.status}",
|
||||||
|
"data": response_data,
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Discovery error with {target_url}: {e}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": target_url
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NodeNetworkManager:
|
||||||
|
"""Менеджер для работы с сетью нод"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.known_nodes: List[str] = []
|
||||||
|
self.active_nodes: List[str] = []
|
||||||
|
|
||||||
|
async def discover_nodes(self, bootstrap_nodes: List[str]) -> List[str]:
|
||||||
|
"""
|
||||||
|
Обнаружить ноды в сети через bootstrap ноды
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bootstrap_nodes: Список bootstrap нод для начального подключения
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список обнаруженных активных нод
|
||||||
|
"""
|
||||||
|
discovered_nodes = set()
|
||||||
|
|
||||||
|
async with NodeClient() as client:
|
||||||
|
# Получить информацию о нашей ноде
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
our_node_info = {
|
||||||
|
"node_id": crypto_manager.node_id,
|
||||||
|
"version": "3.0.0",
|
||||||
|
"capabilities": [
|
||||||
|
"content_upload",
|
||||||
|
"content_sync",
|
||||||
|
"decentralized_filtering",
|
||||||
|
"ed25519_signatures"
|
||||||
|
],
|
||||||
|
"network_info": {
|
||||||
|
"public_key": crypto_manager.public_key_hex,
|
||||||
|
"protocol_version": "1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Попробовать подключиться к bootstrap нодам
|
||||||
|
for node_url in bootstrap_nodes:
|
||||||
|
try:
|
||||||
|
# Выполнить хэндшейк
|
||||||
|
handshake_result = await client.send_handshake(node_url, our_node_info)
|
||||||
|
|
||||||
|
if handshake_result["success"]:
|
||||||
|
discovered_nodes.add(node_url)
|
||||||
|
|
||||||
|
# Запросить список известных нод
|
||||||
|
discovery_result = await client.send_discovery(node_url, list(discovered_nodes))
|
||||||
|
|
||||||
|
if discovery_result["success"]:
|
||||||
|
# Добавить ноды из ответа
|
||||||
|
known_nodes = discovery_result["data"]["data"]["known_nodes"]
|
||||||
|
for node_info in known_nodes:
|
||||||
|
if "url" in node_info:
|
||||||
|
discovered_nodes.add(node_info["url"])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to discover through {node_url}: {e}")
|
||||||
|
|
||||||
|
self.known_nodes = list(discovered_nodes)
|
||||||
|
return self.known_nodes
|
||||||
|
|
||||||
|
async def check_node_health(self, nodes: List[str]) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Проверить состояние нод
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodes: Список нод для проверки
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с результатами проверки для каждой ноды
|
||||||
|
"""
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
async with NodeClient() as client:
|
||||||
|
# Создаем задачи для параллельной проверки
|
||||||
|
tasks = []
|
||||||
|
for node_url in nodes:
|
||||||
|
task = asyncio.create_task(client.send_ping(node_url))
|
||||||
|
tasks.append((node_url, task))
|
||||||
|
|
||||||
|
# Ждем завершения всех задач
|
||||||
|
for node_url, task in tasks:
|
||||||
|
try:
|
||||||
|
result = await task
|
||||||
|
results[node_url] = result
|
||||||
|
except Exception as e:
|
||||||
|
results[node_url] = {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": node_url
|
||||||
|
}
|
||||||
|
|
||||||
|
# Обновляем список активных нод
|
||||||
|
self.active_nodes = [
|
||||||
|
node_url for node_url, result in results.items()
|
||||||
|
if result.get("success", False)
|
||||||
|
]
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
async def broadcast_content(
|
||||||
|
self,
|
||||||
|
content_info: Dict[str, Any],
|
||||||
|
target_nodes: Optional[List[str]] = None
|
||||||
|
) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Транслировать информацию о новом контенте всем активным нодам
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content_info: Информация о контенте
|
||||||
|
target_nodes: Список целевых нод (по умолчанию все активные)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Результаты трансляции для каждой ноды
|
||||||
|
"""
|
||||||
|
nodes = target_nodes or self.active_nodes
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
async with NodeClient() as client:
|
||||||
|
# Создаем задачи для параллельной отправки
|
||||||
|
tasks = []
|
||||||
|
for node_url in nodes:
|
||||||
|
task = asyncio.create_task(
|
||||||
|
client.send_content_sync(node_url, "new_content", content_info)
|
||||||
|
)
|
||||||
|
tasks.append((node_url, task))
|
||||||
|
|
||||||
|
# Ждем завершения всех задач
|
||||||
|
for node_url, task in tasks:
|
||||||
|
try:
|
||||||
|
result = await task
|
||||||
|
results[node_url] = result
|
||||||
|
except Exception as e:
|
||||||
|
results[node_url] = {
|
||||||
|
"success": False,
|
||||||
|
"error": str(e),
|
||||||
|
"node_url": node_url
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# Глобальный экземпляр менеджера сети
|
||||||
|
network_manager = NodeNetworkManager()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_network_manager() -> NodeNetworkManager:
|
||||||
|
"""Получить глобальный экземпляр менеджера сети"""
|
||||||
|
return network_manager
|
||||||
|
|
@ -0,0 +1,515 @@
|
||||||
|
# MY Network v3.0 - Межузловое общение с Ed25519
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
MY Network v3.0 использует криптографические подписи Ed25519 для безопасного межузлового общения. Каждая нода идентифицируется уникальным ключом Ed25519, что обеспечивает:
|
||||||
|
|
||||||
|
- **Аутентификацию**: Каждое сообщение подписано приватным ключом отправителя
|
||||||
|
- **Целостность**: Подпись гарантирует, что сообщение не было изменено
|
||||||
|
- **Идентификацию**: Node ID основан на публичном ключе в формате base58
|
||||||
|
- **Децентрализацию**: Ноды могут менять IP адреса, сохраняя постоянную идентичность
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐ Ed25519 Signature ┌─────────────────┐
|
||||||
|
│ Node A │◄──────────────────────────►│ Node B │
|
||||||
|
│ Private Key │ │ Public Key │
|
||||||
|
│ Public Key │ Signed Messages │ Verification │
|
||||||
|
│ Node ID │ │ Node ID │
|
||||||
|
└─────────────────┘ └─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка Ed25519 ключей
|
||||||
|
|
||||||
|
### Автоматическая генерация (рекомендуется)
|
||||||
|
|
||||||
|
При запуске через `start.sh` ключи генерируются автоматически:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://raw.githubusercontent.com/username/uploader-bot/main/start.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ручная генерация
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Создание директории для ключей
|
||||||
|
sudo mkdir -p /opt/my-network/keys
|
||||||
|
sudo chmod 700 /opt/my-network/keys
|
||||||
|
|
||||||
|
# Генерация ключей (выполняется в start.sh)
|
||||||
|
# Приватный ключ: /opt/my-network/keys/ed25519_private.key
|
||||||
|
# Публичный ключ: /opt/my-network/keys/ed25519_public.key
|
||||||
|
# Node ID: /opt/my-network/keys/node_id.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Структура ключей
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/my-network/keys/
|
||||||
|
├── ed25519_private.key # Приватный ключ (64 символа hex)
|
||||||
|
├── ed25519_public.key # Публичный ключ (64 символа hex)
|
||||||
|
└── node_id.txt # Node ID (base58 от публичного ключа)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints для межузлового общения
|
||||||
|
|
||||||
|
### 1. Handshake - `/api/node/handshake`
|
||||||
|
|
||||||
|
Инициализация связи между нодами.
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
```http
|
||||||
|
POST /api/node/handshake
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Node-Communication: true
|
||||||
|
X-Node-ID: 8x7KJmG5w2d3FhN9QpLmB4c6VzY3Xt2a
|
||||||
|
X-Node-Public-Key: a1b2c3d4e5f6...
|
||||||
|
X-Node-Signature: 1a2b3c4d5e6f...
|
||||||
|
|
||||||
|
{
|
||||||
|
"action": "handshake",
|
||||||
|
"node_info": {
|
||||||
|
"node_id": "8x7KJmG5w2d3FhN9QpLmB4c6VzY3Xt2a",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"capabilities": [
|
||||||
|
"content_upload",
|
||||||
|
"content_sync",
|
||||||
|
"decentralized_filtering",
|
||||||
|
"ed25519_signatures"
|
||||||
|
],
|
||||||
|
"network_info": {
|
||||||
|
"public_key": "a1b2c3d4e5f6...",
|
||||||
|
"protocol_version": "1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"timestamp": "2024-01-15T10:30:01.000Z",
|
||||||
|
"node_id": "9y8LKnH6x3e4GiO0RqMnC5d7WaZ4Yu3b",
|
||||||
|
"data": {
|
||||||
|
"handshake_accepted": true,
|
||||||
|
"node_info": {
|
||||||
|
"node_id": "9y8LKnH6x3e4GiO0RqMnC5d7WaZ4Yu3b",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"capabilities": [...],
|
||||||
|
"network_info": {...}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Content Sync - `/api/node/content/sync`
|
||||||
|
|
||||||
|
Синхронизация контента между нодами.
|
||||||
|
|
||||||
|
**Типы синхронизации:**
|
||||||
|
- `new_content` - Уведомление о новом контенте
|
||||||
|
- `content_list` - Запрос списка доступного контента
|
||||||
|
- `content_request` - Запрос конкретного файла
|
||||||
|
|
||||||
|
**Пример - Новый контент:**
|
||||||
|
```http
|
||||||
|
POST /api/node/content/sync
|
||||||
|
{
|
||||||
|
"action": "content_sync",
|
||||||
|
"sync_type": "new_content",
|
||||||
|
"content_info": {
|
||||||
|
"hash": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
|
||||||
|
"title": "Example Video",
|
||||||
|
"size": 1048576,
|
||||||
|
"content_type": "video/mp4",
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||||
|
},
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Network Ping - `/api/node/network/ping`
|
||||||
|
|
||||||
|
Проверка доступности и измерение задержки.
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
```http
|
||||||
|
POST /api/node/network/ping
|
||||||
|
{
|
||||||
|
"action": "ping",
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z",
|
||||||
|
"data": {"test": true}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"action": "pong",
|
||||||
|
"ping_timestamp": "2024-01-15T10:30:00.000Z",
|
||||||
|
"response_timestamp": "2024-01-15T10:30:01.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Node Status - `/api/node/network/status`
|
||||||
|
|
||||||
|
Получение статуса ноды (GET запрос, подпись не требуется).
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/node/network/status
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"node_id": "8x7KJmG5w2d3FhN9QpLmB4c6VzY3Xt2a",
|
||||||
|
"public_key": "a1b2c3d4e5f6...",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"status": "active",
|
||||||
|
"capabilities": [...],
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Node Discovery - `/api/node/network/discover`
|
||||||
|
|
||||||
|
Обнаружение других нод в сети.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/node/network/discover
|
||||||
|
{
|
||||||
|
"action": "discover",
|
||||||
|
"known_nodes": [
|
||||||
|
{"node_id": "...", "url": "http://node1.example.com:8000"},
|
||||||
|
{"node_id": "...", "url": "http://node2.example.com:8000"}
|
||||||
|
],
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Использование клиентских функций
|
||||||
|
|
||||||
|
### Базовое использование
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.core.network.node_client import NodeClient, NodeNetworkManager
|
||||||
|
|
||||||
|
# Создание клиента
|
||||||
|
async with NodeClient() as client:
|
||||||
|
# Получение статуса ноды
|
||||||
|
status = await client.get_node_status("http://node.example.com:8000")
|
||||||
|
|
||||||
|
# Отправка пинга
|
||||||
|
ping_result = await client.send_ping("http://node.example.com:8000")
|
||||||
|
|
||||||
|
# Хэндшейк
|
||||||
|
our_node_info = {
|
||||||
|
"node_id": "our_node_id",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"capabilities": ["content_upload", "content_sync"]
|
||||||
|
}
|
||||||
|
handshake_result = await client.send_handshake(
|
||||||
|
"http://node.example.com:8000",
|
||||||
|
our_node_info
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Менеджер сети
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.core.network.node_client import get_network_manager
|
||||||
|
|
||||||
|
# Получение менеджера
|
||||||
|
network_manager = await get_network_manager()
|
||||||
|
|
||||||
|
# Обнаружение нод
|
||||||
|
bootstrap_nodes = [
|
||||||
|
"http://bootstrap1.mynetwork.org:8000",
|
||||||
|
"http://bootstrap2.mynetwork.org:8000"
|
||||||
|
]
|
||||||
|
discovered_nodes = await network_manager.discover_nodes(bootstrap_nodes)
|
||||||
|
|
||||||
|
# Проверка здоровья нод
|
||||||
|
health_results = await network_manager.check_node_health(discovered_nodes)
|
||||||
|
|
||||||
|
# Трансляция нового контента
|
||||||
|
content_info = {
|
||||||
|
"hash": "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",
|
||||||
|
"title": "New Video",
|
||||||
|
"size": 1048576,
|
||||||
|
"content_type": "video/mp4"
|
||||||
|
}
|
||||||
|
broadcast_results = await network_manager.broadcast_content(content_info)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подпись сообщений
|
||||||
|
|
||||||
|
### Формат подписи
|
||||||
|
|
||||||
|
1. **Сообщение**: JSON объект сериализуется в строку
|
||||||
|
2. **Подпись**: Ed25519 подпись от сериализованного JSON
|
||||||
|
3. **Заголовки**: Подпись и метаданные передаются в HTTP заголовках
|
||||||
|
|
||||||
|
### Обязательные заголовки для межузлового общения
|
||||||
|
|
||||||
|
```http
|
||||||
|
X-Node-Communication: true
|
||||||
|
X-Node-ID: <base58_node_id>
|
||||||
|
X-Node-Public-Key: <hex_public_key>
|
||||||
|
X-Node-Signature: <hex_signature>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Пример создания подписи
|
||||||
|
|
||||||
|
```python
|
||||||
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
# Сообщение
|
||||||
|
message = {
|
||||||
|
"action": "ping",
|
||||||
|
"timestamp": "2024-01-15T10:30:00.000Z",
|
||||||
|
"data": {"test": True}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Создание подписи
|
||||||
|
signature = crypto_manager.sign_message(message)
|
||||||
|
|
||||||
|
# Заголовки
|
||||||
|
headers = {
|
||||||
|
"X-Node-Communication": "true",
|
||||||
|
"X-Node-ID": crypto_manager.node_id,
|
||||||
|
"X-Node-Public-Key": crypto_manager.public_key_hex,
|
||||||
|
"X-Node-Signature": signature
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка подписи
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Проверка подписи (выполняется автоматически в middleware)
|
||||||
|
is_valid = crypto_manager.verify_signature(
|
||||||
|
message, signature, public_key_hex
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конфигурация
|
||||||
|
|
||||||
|
### Переменные окружения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Пути к ключам (по умолчанию /opt/my-network/keys/)
|
||||||
|
MY_NETWORK_KEYS_DIR=/opt/my-network/keys
|
||||||
|
|
||||||
|
# Таймауты для межузлового общения
|
||||||
|
NODE_CLIENT_TIMEOUT=30
|
||||||
|
|
||||||
|
# Bootstrap ноды для обнаружения сети
|
||||||
|
MY_NETWORK_BOOTSTRAP_NODES=http://bootstrap1.mynetwork.org:8000,http://bootstrap2.mynetwork.org:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Настройка в Docker
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
my-network-node:
|
||||||
|
image: my-network:latest
|
||||||
|
volumes:
|
||||||
|
- my_network_keys:/opt/my-network/keys
|
||||||
|
environment:
|
||||||
|
- MY_NETWORK_KEYS_DIR=/opt/my-network/keys
|
||||||
|
- NODE_CLIENT_TIMEOUT=30
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
my_network_keys:
|
||||||
|
```
|
||||||
|
|
||||||
|
## Безопасность
|
||||||
|
|
||||||
|
### Защита приватных ключей
|
||||||
|
|
||||||
|
1. **Файловые права**: `chmod 600 /opt/my-network/keys/ed25519_private.key`
|
||||||
|
2. **Владелец**: Только пользователь приложения имеет доступ
|
||||||
|
3. **Бэкапы**: Регулярное резервное копирование ключей
|
||||||
|
4. **Ротация**: Возможность смены ключей с сохранением истории
|
||||||
|
|
||||||
|
### Проверка подписей
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Все входящие межузловые сообщения автоматически проверяются
|
||||||
|
# в CryptographicMiddleware
|
||||||
|
|
||||||
|
# Ручная проверка подписи
|
||||||
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
is_valid = crypto_manager.verify_signature(message, signature, public_key)
|
||||||
|
|
||||||
|
if not is_valid:
|
||||||
|
raise SecurityError("Invalid message signature")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Защита от replay-атак
|
||||||
|
|
||||||
|
1. **Timestamp**: Каждое сообщение содержит timestamp
|
||||||
|
2. **Nonce**: Опционально можно добавить nonce для дополнительной защиты
|
||||||
|
3. **TTL**: Сообщения имеют время жизни
|
||||||
|
|
||||||
|
## Диагностика и отладка
|
||||||
|
|
||||||
|
### Проверка состояния ключей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка наличия ключей
|
||||||
|
ls -la /opt/my-network/keys/
|
||||||
|
|
||||||
|
# Проверка Node ID
|
||||||
|
cat /opt/my-network/keys/node_id.txt
|
||||||
|
|
||||||
|
# Проверка прав доступа
|
||||||
|
stat /opt/my-network/keys/ed25519_private.key
|
||||||
|
```
|
||||||
|
|
||||||
|
### Логирование
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Включение debug логов для криптографии
|
||||||
|
import logging
|
||||||
|
logging.getLogger('app.core.crypto').setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# Включение debug логов для межузлового общения
|
||||||
|
logging.getLogger('app.core.network').setLevel(logging.DEBUG)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Тестирование соединения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Тест статуса ноды
|
||||||
|
curl http://localhost:8000/api/node/network/status
|
||||||
|
|
||||||
|
# Тест здоровья приложения
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проверка подписи вручную
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Скрипт для тестирования подписей
|
||||||
|
import asyncio
|
||||||
|
from app.core.crypto import get_ed25519_manager
|
||||||
|
|
||||||
|
async def test_signature():
|
||||||
|
crypto_manager = get_ed25519_manager()
|
||||||
|
|
||||||
|
message = {"test": "message", "timestamp": "2024-01-15T10:30:00.000Z"}
|
||||||
|
signature = crypto_manager.sign_message(message)
|
||||||
|
|
||||||
|
is_valid = crypto_manager.verify_signature(
|
||||||
|
message, signature, crypto_manager.public_key_hex
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Node ID: {crypto_manager.node_id}")
|
||||||
|
print(f"Signature valid: {is_valid}")
|
||||||
|
|
||||||
|
# Запуск теста
|
||||||
|
asyncio.run(test_signature())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Примеры интеграции
|
||||||
|
|
||||||
|
### Добавление в существующие API
|
||||||
|
|
||||||
|
```python
|
||||||
|
# В вашем API endpoint
|
||||||
|
from app.core.network.node_client import get_network_manager
|
||||||
|
|
||||||
|
@app.post("/api/content/upload")
|
||||||
|
async def upload_content(request):
|
||||||
|
# ... обработка загрузки ...
|
||||||
|
|
||||||
|
# Уведомление других нод о новом контенте
|
||||||
|
network_manager = await get_network_manager()
|
||||||
|
|
||||||
|
content_info = {
|
||||||
|
"hash": content_hash,
|
||||||
|
"title": title,
|
||||||
|
"size": file_size,
|
||||||
|
"content_type": content_type,
|
||||||
|
"timestamp": datetime.utcnow().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Асинхронно уведомляем другие ноды
|
||||||
|
asyncio.create_task(
|
||||||
|
network_manager.broadcast_content(content_info)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"status": "uploaded", "hash": content_hash}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Периодическая синхронизация
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Фоновая задача для синхронизации
|
||||||
|
async def sync_with_network():
|
||||||
|
network_manager = await get_network_manager()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# Проверяем здоровье известных нод каждые 5 минут
|
||||||
|
await network_manager.check_node_health(network_manager.known_nodes)
|
||||||
|
|
||||||
|
# Попытка обнаружить новые ноды каждые 15 минут
|
||||||
|
if len(network_manager.active_nodes) < 3:
|
||||||
|
await network_manager.discover_nodes(bootstrap_nodes)
|
||||||
|
|
||||||
|
await asyncio.sleep(300) # 5 минут
|
||||||
|
|
||||||
|
# Добавление задачи в приложение
|
||||||
|
app.add_background_task(sync_with_network())
|
||||||
|
```
|
||||||
|
|
||||||
|
## Миграция и совместимость
|
||||||
|
|
||||||
|
### Обновление с предыдущих версий
|
||||||
|
|
||||||
|
1. **Backup существующих данных**
|
||||||
|
2. **Генерация новых ed25519 ключей**
|
||||||
|
3. **Обновление конфигурации**
|
||||||
|
4. **Тестирование подключения к сети**
|
||||||
|
|
||||||
|
### Совместимость версий
|
||||||
|
|
||||||
|
- **v3.0+**: Обязательные ed25519 подписи
|
||||||
|
- **v2.x**: Опциональные подписи (deprecated)
|
||||||
|
- **v1.x**: Без криптографической защиты (не поддерживается)
|
||||||
|
|
||||||
|
### План миграции
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Остановка текущей версии
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# 2. Backup данных
|
||||||
|
tar -czf my-network-backup.tar.gz /opt/my-network/
|
||||||
|
|
||||||
|
# 3. Обновление до v3.0
|
||||||
|
curl -sSL https://raw.githubusercontent.com/username/uploader-bot/main/start.sh | bash
|
||||||
|
|
||||||
|
# 4. Проверка работоспособности
|
||||||
|
curl http://localhost:8000/health
|
||||||
|
curl http://localhost:8000/api/node/network/status
|
||||||
|
```
|
||||||
|
|
||||||
|
Данная интеграция обеспечивает полную криптографическую защиту межузлового общения в MY Network v3.0, гарантируя безопасность и децентрализованность сети.
|
||||||
296
start.sh
296
start.sh
|
|
@ -121,6 +121,234 @@ detect_os() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Проверка существующей установки
|
||||||
|
check_existing_installation() {
|
||||||
|
log_info "🔍 Проверка существующей установки MY Network..."
|
||||||
|
|
||||||
|
local existing_installation=false
|
||||||
|
local services_running=false
|
||||||
|
|
||||||
|
# Проверяем наличие папки проекта
|
||||||
|
if [ -d "$PROJECT_DIR" ]; then
|
||||||
|
log_info "Обнаружена папка проекта: $PROJECT_DIR"
|
||||||
|
existing_installation=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем systemd сервис
|
||||||
|
if systemctl list-unit-files | grep -q "my-network.service"; then
|
||||||
|
log_info "Обнаружен systemd сервис: my-network"
|
||||||
|
existing_installation=true
|
||||||
|
|
||||||
|
if systemctl is-active my-network >/dev/null 2>&1; then
|
||||||
|
log_info "Сервис my-network активен"
|
||||||
|
services_running=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем Docker контейнеры
|
||||||
|
if docker ps -a --format "table {{.Names}}" | grep -q "my-network"; then
|
||||||
|
log_info "Обнаружены Docker контейнеры MY Network"
|
||||||
|
existing_installation=true
|
||||||
|
|
||||||
|
if docker ps --format "table {{.Names}}" | grep -q "my-network"; then
|
||||||
|
log_info "Найдены запущенные контейнеры MY Network"
|
||||||
|
services_running=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверяем Docker образы
|
||||||
|
if docker images --format "table {{.Repository}}" | grep -q "my-network"; then
|
||||||
|
log_info "Обнаружены Docker образы MY Network"
|
||||||
|
existing_installation=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$existing_installation" = true ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo -e "${WHITE} ОБНАРУЖЕНА СУЩЕСТВУЮЩАЯ УСТАНОВКА ${NC}"
|
||||||
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$services_running" = true ]; then
|
||||||
|
log_warn "Обнаружены запущенные сервисы MY Network"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${WHITE}Найдены компоненты предыдущей установки MY Network.${NC}"
|
||||||
|
echo -e "${WHITE}Для корректного обновления необходимо выполнить очистку.${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if check_interactive; then
|
||||||
|
echo -n "Обновить существующую установку MY Network? [y/N]: " >&2
|
||||||
|
read -r update_choice < /dev/tty
|
||||||
|
|
||||||
|
if [[ ! $update_choice =~ ^[Yy]$ ]]; then
|
||||||
|
log_info "Обновление отменено пользователем"
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}Для ручного управления используйте:${NC}"
|
||||||
|
echo -e "${BLUE}systemctl stop my-network${NC} # Остановка сервиса"
|
||||||
|
echo -e "${BLUE}docker-compose -f $PROJECT_DIR/my-network/docker-compose.yml down${NC} # Остановка контейнеров"
|
||||||
|
echo -e "${BLUE}sudo rm -rf $PROJECT_DIR${NC} # Удаление проекта"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_warn "Неинтерактивный режим: существующая установка будет автоматически обновлена"
|
||||||
|
log_info "Для предотвращения обновления запустите скрипт локально"
|
||||||
|
sleep 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup_existing_installation
|
||||||
|
else
|
||||||
|
log_success "Предыдущие установки не обнаружены. Выполняется чистая установка."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Очистка существующей установки
|
||||||
|
cleanup_existing_installation() {
|
||||||
|
log_info "🧹 Очистка существующей установки..."
|
||||||
|
|
||||||
|
# 1. Остановка systemd сервиса
|
||||||
|
if systemctl is-active my-network >/dev/null 2>&1; then
|
||||||
|
log_info "Остановка systemd сервиса my-network..."
|
||||||
|
systemctl stop my-network || log_warn "Не удалось остановить сервис"
|
||||||
|
systemctl disable my-network >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Остановка и удаление Docker контейнеров
|
||||||
|
log_info "Остановка и удаление Docker контейнеров..."
|
||||||
|
|
||||||
|
# Переходим в папку проекта если существует
|
||||||
|
if [ -d "$PROJECT_DIR/my-network" ]; then
|
||||||
|
cd "$PROJECT_DIR/my-network"
|
||||||
|
docker-compose down --remove-orphans --volumes 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Принудительная остановка всех контейнеров MY Network
|
||||||
|
local containers=$(docker ps -a --filter "name=my-network" --format "{{.ID}}" 2>/dev/null || true)
|
||||||
|
if [ -n "$containers" ]; then
|
||||||
|
log_info "Удаление контейнеров MY Network..."
|
||||||
|
echo "$containers" | xargs docker rm -f 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Удаление Docker образов
|
||||||
|
log_info "Удаление Docker образов MY Network..."
|
||||||
|
local images=$(docker images --filter "reference=my-network*" --format "{{.ID}}" 2>/dev/null || true)
|
||||||
|
if [ -n "$images" ]; then
|
||||||
|
echo "$images" | xargs docker rmi -f 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Удаление converter образа
|
||||||
|
docker rmi my-network-converter:latest 2>/dev/null || true
|
||||||
|
|
||||||
|
# 4. Очистка Docker системы
|
||||||
|
log_info "Очистка Docker кэша и неиспользуемых ресурсов..."
|
||||||
|
docker system prune -f --volumes 2>/dev/null || true
|
||||||
|
docker builder prune -f 2>/dev/null || true
|
||||||
|
docker volume prune -f 2>/dev/null || true
|
||||||
|
|
||||||
|
# 5. Удаление systemd сервиса
|
||||||
|
if [ -f "/etc/systemd/system/my-network.service" ]; then
|
||||||
|
log_info "Удаление systemd сервиса..."
|
||||||
|
rm -f /etc/systemd/system/my-network.service
|
||||||
|
systemctl daemon-reload
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Остановка nginx если настроен для MY Network
|
||||||
|
if systemctl is-active nginx >/dev/null 2>&1; then
|
||||||
|
if [ -f "/etc/nginx/sites-enabled/my-network" ]; then
|
||||||
|
log_info "Удаление nginx конфигурации MY Network..."
|
||||||
|
rm -f /etc/nginx/sites-enabled/my-network
|
||||||
|
rm -f /etc/nginx/sites-available/my-network
|
||||||
|
|
||||||
|
# Восстанавливаем дефолтную конфигурацию nginx если есть backup
|
||||||
|
if [ -f /etc/nginx/nginx.conf.backup ]; then
|
||||||
|
cp /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Перезапускаем nginx
|
||||||
|
systemctl reload nginx 2>/dev/null || systemctl restart nginx 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. Удаление веб-файлов
|
||||||
|
if [ -d "/var/www/my-network-web" ]; then
|
||||||
|
log_info "Удаление веб-файлов..."
|
||||||
|
rm -rf /var/www/my-network-web
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 8. Вопрос о базе данных
|
||||||
|
local remove_database=false
|
||||||
|
echo ""
|
||||||
|
echo -e "${PURPLE}❓ База данных:${NC}"
|
||||||
|
|
||||||
|
if check_interactive; then
|
||||||
|
echo -n "Удалить существующую базу данных? [y/N]: " >&2
|
||||||
|
read -r db_choice < /dev/tty
|
||||||
|
|
||||||
|
if [[ $db_choice =~ ^[Yy]$ ]]; then
|
||||||
|
remove_database=true
|
||||||
|
log_warn "База данных будет удалена"
|
||||||
|
else
|
||||||
|
log_info "База данных будет сохранена для миграции"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_info "Неинтерактивный режим: база данных сохраняется для миграции"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 9. Удаление файлов проекта (кроме БД если сохраняем)
|
||||||
|
if [ -d "$PROJECT_DIR" ]; then
|
||||||
|
log_info "Удаление файлов проекта..."
|
||||||
|
|
||||||
|
if [ "$remove_database" = true ]; then
|
||||||
|
# Удаляем все включая БД
|
||||||
|
rm -rf "$PROJECT_DIR"
|
||||||
|
rm -rf "$STORAGE_DIR" 2>/dev/null || true
|
||||||
|
rm -rf "$CONFIG_DIR" 2>/dev/null || true
|
||||||
|
rm -rf "$LOGS_DIR" 2>/dev/null || true
|
||||||
|
log_info "Проект полностью удален включая базу данных"
|
||||||
|
else
|
||||||
|
# Сохраняем только docker volumes с БД
|
||||||
|
backup_dir="/tmp/my-network-db-backup-$(date +%s)"
|
||||||
|
|
||||||
|
# Создаем резервную копию volumes перед удалением
|
||||||
|
if docker volume ls | grep -q "my-network.*postgres"; then
|
||||||
|
log_info "Создание резервной копии базы данных..."
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
|
||||||
|
# Экспортируем данные PostgreSQL
|
||||||
|
if docker run --rm -v my-network_postgres_data:/source -v "$backup_dir":/backup alpine tar czf /backup/postgres_data.tar.gz -C /source . 2>/dev/null; then
|
||||||
|
log_success "Резервная копия создана: $backup_dir/postgres_data.tar.gz"
|
||||||
|
else
|
||||||
|
log_warn "Не удалось создать резервную копию БД"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Удаляем все файлы проекта
|
||||||
|
rm -rf "$PROJECT_DIR"
|
||||||
|
rm -rf "$STORAGE_DIR" 2>/dev/null || true
|
||||||
|
rm -rf "$LOGS_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Сохраняем только папку config для ключей если есть
|
||||||
|
if [ -d "$CONFIG_DIR" ]; then
|
||||||
|
config_backup="$CONFIG_DIR.backup-$(date +%s)"
|
||||||
|
mv "$CONFIG_DIR" "$config_backup" 2>/dev/null || true
|
||||||
|
log_info "Конфигурация сохранена: $config_backup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Проект удален, база данных сохранена"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 10. Очистка Docker volumes (только если удаляем БД)
|
||||||
|
if [ "$remove_database" = true ]; then
|
||||||
|
log_info "Удаление Docker volumes..."
|
||||||
|
docker volume ls | grep "my-network" | awk '{print $2}' | xargs -r docker volume rm 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Очистка завершена"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Проверка доступности TTY для интерактивного ввода
|
# Проверка доступности TTY для интерактивного ввода
|
||||||
check_interactive() {
|
check_interactive() {
|
||||||
if [ -t 0 ] && [ -t 1 ]; then
|
if [ -t 0 ] && [ -t 1 ]; then
|
||||||
|
|
@ -579,6 +807,7 @@ services:
|
||||||
- ${STORAGE_PATH:-./storage}:/app/storage
|
- ${STORAGE_PATH:-./storage}:/app/storage
|
||||||
- ${DOCKER_SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
|
- ${DOCKER_SOCK_PATH:-/var/run/docker.sock}:/var/run/docker.sock
|
||||||
- ./logs:/app/logs
|
- ./logs:/app/logs
|
||||||
|
- ./config/keys:/app/keys:ro
|
||||||
environment:
|
environment:
|
||||||
- DATABASE_URL=${DATABASE_URL}
|
- DATABASE_URL=${DATABASE_URL}
|
||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
|
|
@ -594,6 +823,9 @@ services:
|
||||||
- API_HOST=${API_HOST}
|
- API_HOST=${API_HOST}
|
||||||
- API_PORT=${API_PORT}
|
- API_PORT=${API_PORT}
|
||||||
- DOCKER_SOCK_PATH=/var/run/docker.sock
|
- DOCKER_SOCK_PATH=/var/run/docker.sock
|
||||||
|
- NODE_PRIVATE_KEY_PATH=/app/keys/node_private_key
|
||||||
|
- NODE_PUBLIC_KEY_PATH=/app/keys/node_public_key
|
||||||
|
- NODE_PUBLIC_KEY_HEX=${NODE_PUBLIC_KEY_HEX}
|
||||||
- TELEGRAM_API_KEY=${TELEGRAM_API_KEY}
|
- TELEGRAM_API_KEY=${TELEGRAM_API_KEY}
|
||||||
- CLIENT_TELEGRAM_API_KEY=${CLIENT_TELEGRAM_API_KEY}
|
- CLIENT_TELEGRAM_API_KEY=${CLIENT_TELEGRAM_API_KEY}
|
||||||
- LOG_LEVEL=${LOG_LEVEL}
|
- LOG_LEVEL=${LOG_LEVEL}
|
||||||
|
|
@ -716,6 +948,9 @@ starlette==0.27.0
|
||||||
structlog==23.2.0
|
structlog==23.2.0
|
||||||
aiogram==3.3.0
|
aiogram==3.3.0
|
||||||
sanic==23.12.1
|
sanic==23.12.1
|
||||||
|
PyJWT==2.8.0
|
||||||
|
cryptography==41.0.7
|
||||||
|
ed25519==1.5
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Создание init_db.sql
|
# Создание init_db.sql
|
||||||
|
|
@ -1356,15 +1591,42 @@ install_ssl_certificates() {
|
||||||
generate_config() {
|
generate_config() {
|
||||||
log_info "⚙️ Генерация конфигурации..."
|
log_info "⚙️ Генерация конфигурации..."
|
||||||
|
|
||||||
# Генерация уникальных ключей
|
# Генерация ed25519 ключей для ноды
|
||||||
|
log_info "Генерация ed25519 ключей для ноды..."
|
||||||
|
|
||||||
|
# Создаем временную папку для ключей
|
||||||
|
mkdir -p "$CONFIG_DIR/keys"
|
||||||
|
|
||||||
|
# Генерируем приватный ключ ed25519
|
||||||
|
PRIVATE_KEY_FILE="$CONFIG_DIR/keys/node_private_key"
|
||||||
|
PUBLIC_KEY_FILE="$CONFIG_DIR/keys/node_public_key"
|
||||||
|
|
||||||
|
# Генерируем ключевую пару ed25519
|
||||||
|
openssl genpkey -algorithm ed25519 -out "$PRIVATE_KEY_FILE"
|
||||||
|
openssl pkey -in "$PRIVATE_KEY_FILE" -pubout -out "$PUBLIC_KEY_FILE"
|
||||||
|
|
||||||
|
# Извлекаем raw публичный ключ для генерации NODE_ID
|
||||||
|
PUBLIC_KEY_HEX=$(openssl pkey -in "$PRIVATE_KEY_FILE" -pubout -outform DER | tail -c 32 | xxd -p -c 32)
|
||||||
|
|
||||||
|
# Генерируем NODE_ID как base58 от публичного ключа
|
||||||
|
# Сначала конвертируем hex в binary, затем в base58
|
||||||
|
PUBLIC_KEY_BINARY=$(echo "$PUBLIC_KEY_HEX" | xxd -r -p | base64 -w 0)
|
||||||
|
|
||||||
|
# Создаем простой base58 ID (упрощенная версия)
|
||||||
|
NODE_ID="node-$(echo "$PUBLIC_KEY_HEX" | cut -c1-16)"
|
||||||
|
|
||||||
|
# Читаем приватный ключ в PEM формате для конфигурации
|
||||||
|
PRIVATE_KEY_PEM=$(cat "$PRIVATE_KEY_FILE")
|
||||||
|
PUBLIC_KEY_PEM=$(cat "$PUBLIC_KEY_FILE")
|
||||||
|
|
||||||
|
log_success "Ed25519 ключи сгенерированы для ноды: $NODE_ID"
|
||||||
|
|
||||||
|
# Генерация других ключей
|
||||||
SECRET_KEY=$(openssl rand -hex 32)
|
SECRET_KEY=$(openssl rand -hex 32)
|
||||||
JWT_SECRET_KEY=$(openssl rand -hex 32)
|
JWT_SECRET_KEY=$(openssl rand -hex 32)
|
||||||
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
ENCRYPTION_KEY=$(openssl rand -hex 32)
|
||||||
DB_PASSWORD=$(openssl rand -hex 16)
|
DB_PASSWORD=$(openssl rand -hex 16)
|
||||||
|
|
||||||
# Генерация NODE_ID
|
|
||||||
NODE_ID="node-$(date +%s)-$(shuf -i 1000-9999 -n 1)"
|
|
||||||
|
|
||||||
# Создание .env файла
|
# Создание .env файла
|
||||||
cat > "$CONFIG_DIR/.env" << EOF
|
cat > "$CONFIG_DIR/.env" << EOF
|
||||||
# MY Network v3.0 Configuration
|
# MY Network v3.0 Configuration
|
||||||
|
|
@ -1405,6 +1667,11 @@ FASTAPI_PORT=15100
|
||||||
# Docker Configuration
|
# Docker Configuration
|
||||||
DOCKER_SOCK_PATH=$DOCKER_SOCK_PATH
|
DOCKER_SOCK_PATH=$DOCKER_SOCK_PATH
|
||||||
|
|
||||||
|
# Node Cryptographic Keys
|
||||||
|
NODE_PRIVATE_KEY_PATH=$CONFIG_DIR/keys/node_private_key
|
||||||
|
NODE_PUBLIC_KEY_PATH=$CONFIG_DIR/keys/node_public_key
|
||||||
|
NODE_PUBLIC_KEY_HEX=$PUBLIC_KEY_HEX
|
||||||
|
|
||||||
# Telegram Bots
|
# Telegram Bots
|
||||||
TELEGRAM_API_KEY=$TELEGRAM_API_KEY
|
TELEGRAM_API_KEY=$TELEGRAM_API_KEY
|
||||||
CLIENT_TELEGRAM_API_KEY=$CLIENT_TELEGRAM_API_KEY
|
CLIENT_TELEGRAM_API_KEY=$CLIENT_TELEGRAM_API_KEY
|
||||||
|
|
@ -1436,7 +1703,7 @@ EOF
|
||||||
"node_id": "$NODE_ID",
|
"node_id": "$NODE_ID",
|
||||||
"address": "$(curl -s ifconfig.me || echo 'localhost')",
|
"address": "$(curl -s ifconfig.me || echo 'localhost')",
|
||||||
"port": 15100,
|
"port": 15100,
|
||||||
"public_key": "",
|
"public_key": "$PUBLIC_KEY_HEX",
|
||||||
"trusted": true,
|
"trusted": true,
|
||||||
"node_type": "bootstrap"
|
"node_type": "bootstrap"
|
||||||
}
|
}
|
||||||
|
|
@ -1462,6 +1729,15 @@ EOF
|
||||||
cp "$CONFIG_DIR/bootstrap.json" "$PROJECT_DIR/my-network/bootstrap.json"
|
cp "$CONFIG_DIR/bootstrap.json" "$PROJECT_DIR/my-network/bootstrap.json"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Копирование ключей в проект
|
||||||
|
mkdir -p "$PROJECT_DIR/my-network/config/keys"
|
||||||
|
cp "$CONFIG_DIR/keys/node_private_key" "$PROJECT_DIR/my-network/config/keys/"
|
||||||
|
cp "$CONFIG_DIR/keys/node_public_key" "$PROJECT_DIR/my-network/config/keys/"
|
||||||
|
|
||||||
|
# Защита приватного ключа
|
||||||
|
chmod 600 "$PROJECT_DIR/my-network/config/keys/node_private_key"
|
||||||
|
chmod 644 "$PROJECT_DIR/my-network/config/keys/node_public_key"
|
||||||
|
|
||||||
log_success "Конфигурация сгенерирована"
|
log_success "Конфигурация сгенерирована"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1790,12 +2066,21 @@ EOF
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}🎉 MY Network v3.0 успешно установлен и запущен!${NC}"
|
echo -e "${GREEN}🎉 MY Network v3.0 успешно установлен и запущен!${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo -e "${WHITE}🔐 Криптографическая безопасность:${NC}"
|
||||||
|
echo -e " Node ID: ${YELLOW}$NODE_ID${NC}"
|
||||||
|
echo -e " Приватный ключ: ${YELLOW}$CONFIG_DIR/keys/node_private_key${NC}"
|
||||||
|
echo -e " Публичный ключ: ${YELLOW}$CONFIG_DIR/keys/node_public_key${NC}"
|
||||||
|
echo -e " Ed25519 ключ: ${GREEN}✅ сгенерирован и защищен${NC}"
|
||||||
|
echo -e " Подписи: ${GREEN}✅ все соединения подписываются ed25519${NC}"
|
||||||
|
echo ""
|
||||||
echo -e "${WHITE}Особенности v3.0:${NC}"
|
echo -e "${WHITE}Особенности v3.0:${NC}"
|
||||||
echo -e " ✅ Полная децентрализация без консенсуса"
|
echo -e " ✅ Полная децентрализация без консенсуса"
|
||||||
echo -e " ✅ Мгновенная трансляция контента"
|
echo -e " ✅ Мгновенная трансляция контента"
|
||||||
echo -e " ✅ Автоматическая конвертация через Docker"
|
echo -e " ✅ Автоматическая конвертация через Docker"
|
||||||
echo -e " ✅ Блокчейн интеграция для uploader-bot"
|
echo -e " ✅ Блокчейн интеграция для uploader-bot"
|
||||||
echo -e " ✅ Поддержка приватных и публичных нод"
|
echo -e " ✅ Поддержка приватных и публичных нод"
|
||||||
|
echo -e " ✅ Ed25519 криптографическая идентификация"
|
||||||
|
echo -e " ✅ Подписанные и проверенные соединения"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Сохранение отчета
|
# Сохранение отчета
|
||||||
|
|
@ -1842,6 +2127,7 @@ main() {
|
||||||
show_banner
|
show_banner
|
||||||
check_root
|
check_root
|
||||||
detect_os
|
detect_os
|
||||||
|
check_existing_installation
|
||||||
interactive_setup
|
interactive_setup
|
||||||
install_dependencies
|
install_dependencies
|
||||||
install_docker
|
install_docker
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue