This commit is contained in:
user committed 2025-07-27 03:12:33 +03:00
1 parent 8b68b0f1e3
commit 274c8f1f09
8 files changed
+2126 -6

No files matched your search

+14
View File
@@ -46,6 +46,16 @@ class EnhancedSanic(Sanic):
await cache.redis.ping()
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
for task in self.ctx.startup_tasks:
try:
@@ -232,6 +242,9 @@ def register_routes():
from app.api.routes.storage_routes import storage_bp
from app.api.routes.blockchain_routes import blockchain_bp
# Import node communication blueprint
from app.api.node_communication import node_bp
# Импортировать существующие маршруты
try:
from app.api.routes._system import bp as system_bp
@@ -248,6 +261,7 @@ def register_routes():
app.blueprint(content_bp)
app.blueprint(storage_bp)
app.blueprint(blockchain_bp)
app.blueprint(node_bp) # Межузловое общение с ed25519
# Register optional blueprints
if user_bp:
+113 -1
View File
@@ -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 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.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__)
@@ -266,6 +273,98 @@ class AuthenticationMiddleware:
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:
"""Request context middleware for tracking and logging"""
@@ -338,6 +437,7 @@ security_middleware = SecurityMiddleware()
rate_limit_middleware = RateLimitMiddleware()
auth_middleware = AuthenticationMiddleware()
context_middleware = RequestContextMiddleware()
crypto_middleware = CryptographicMiddleware()
async def request_middleware(request: Request):
@@ -351,6 +451,15 @@ async def request_middleware(request: Request):
# Add request context
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
try:
security_middleware.validate_request_size(request)
@@ -423,6 +532,9 @@ async def response_middleware(request: Request, response: HTTPResponse):
# Add security headers
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
if hasattr(request.ctx, 'rate_limit_info') and request.ctx.rate_limit_info:
rate_info = request.ctx.rate_limit_info
+378
View File
@@ -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)