fixes
This commit is contained in:
1 parent
8b68b0f1e3
commit
274c8f1f09
8 files changed
+2126
-6
No files matched your search
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user