GT AI OS Community Edition v2.0.33

Security hardening release addressing CodeQL and Dependabot alerts:

- Fix stack trace exposure in error responses
- Add SSRF protection with DNS resolution checking
- Implement proper URL hostname validation (replaces substring matching)
- Add centralized path sanitization to prevent path traversal
- Fix ReDoS vulnerability in email validation regex
- Improve HTML sanitization in validation utilities
- Fix capability wildcard matching in auth utilities
- Update glob dependency to address CVE
- Add CodeQL suppression comments for verified false positives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
HackWeasel
2025-12-12 17:04:45 -05:00
commit b9dfb86260
746 changed files with 232071 additions and 0 deletions

View File

@@ -0,0 +1,371 @@
"""
Message Bus Client for GT 2.0 Tenant Backend
Handles communication with Admin Cluster via RabbitMQ message queues.
Processes commands from admin and sends responses back.
"""
import json
import logging
import asyncio
import uuid
import hmac
import hashlib
from datetime import datetime
from typing import Dict, Any, Optional, Callable, List
from pydantic import BaseModel, Field
import aio_pika
from aio_pika import connect, Message, DeliveryMode, ExchangeType
from aio_pika.abc import AbstractIncomingMessage
from app.core.config import get_settings
logger = logging.getLogger(__name__)
class AdminCommand(BaseModel):
"""Command received from Admin Cluster"""
command_id: str
command_type: str # TENANT_PROVISION, TENANT_SUSPEND, etc.
target_cluster: str
target_namespace: str
payload: Dict[str, Any]
timestamp: str
signature: str
class TenantResponse(BaseModel):
"""Response sent to Admin Cluster"""
command_id: str
response_type: str # SUCCESS, ERROR, PROCESSING
target_cluster: str = "admin"
source_cluster: str = "tenant"
namespace: str
payload: Dict[str, Any] = Field(default_factory=dict)
timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
signature: Optional[str] = None
class MessageBusClient:
"""
Client for RabbitMQ message bus communication with Admin Cluster.
GT 2.0 Security Principles:
- HMAC signature verification for all messages
- Tenant-scoped command processing
- No cross-tenant message leakage
"""
def __init__(self):
self.settings = get_settings()
self.connection = None
self.channel = None
self.admin_to_tenant_queue = None
self.tenant_to_admin_queue = None
# Message handlers
self.command_handlers: Dict[str, Callable[[AdminCommand], Any]] = {}
# RabbitMQ configuration from admin specification
self.rabbitmq_url = getattr(
self.settings,
'RABBITMQ_URL',
'amqp://gt2_admin:dev_password_change_in_prod@rabbitmq:5672/'
)
# Security
self.secret_key = getattr(self.settings, 'SECRET_KEY', 'production-secret-key')
logger.info("Message bus client initialized")
async def connect(self) -> bool:
"""Connect to RabbitMQ message bus"""
try:
self.connection = await connect(self.rabbitmq_url)
self.channel = await self.connection.channel()
# Declare exchanges and queues matching admin specification
await self.channel.declare_exchange(
'gt2_commands', ExchangeType.DIRECT, durable=True
)
# Admin → Tenant command queue
self.admin_to_tenant_queue = await self.channel.declare_queue(
'admin_to_tenant', durable=True
)
# Tenant → Admin response queue
self.tenant_to_admin_queue = await self.channel.declare_queue(
'tenant_to_admin', durable=True
)
# Start consuming commands
await self.admin_to_tenant_queue.consume(self._handle_admin_command)
logger.info("Connected to RabbitMQ message bus")
return True
except Exception as e:
logger.error(f"Failed to connect to message bus: {e}")
return False
def _verify_signature(self, message_data: Dict[str, Any], signature: str) -> bool:
"""Verify HMAC signature of incoming message"""
try:
# Create message content for signature (excluding signature field)
content = {k: v for k, v in message_data.items() if k != 'signature'}
content_str = json.dumps(content, sort_keys=True)
# Calculate expected signature
expected_signature = hmac.new(
self.secret_key.encode(),
content_str.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_signature, signature)
except Exception as e:
logger.error(f"Signature verification failed: {e}")
return False
def _sign_message(self, message_data: Dict[str, Any]) -> str:
"""Create HMAC signature for outgoing message"""
try:
content_str = json.dumps(message_data, sort_keys=True)
return hmac.new(
self.secret_key.encode(),
content_str.encode(),
hashlib.sha256
).hexdigest()
except Exception as e:
logger.error(f"Message signing failed: {e}")
return ""
async def _handle_admin_command(self, message: AbstractIncomingMessage) -> None:
"""Handle incoming command from Admin Cluster"""
try:
# Parse message
message_data = json.loads(message.body.decode())
command = AdminCommand(**message_data)
logger.info(f"Received admin command: {command.command_type} ({command.command_id})")
# Verify signature
if not self._verify_signature(message_data, command.signature):
logger.error(f"Invalid signature for command {command.command_id}")
await self._send_response(command.command_id, "ERROR", {
"error": "Invalid signature",
"namespace": command.target_namespace
})
return
# Check if we have a handler for this command type
if command.command_type not in self.command_handlers:
logger.warning(f"No handler for command type: {command.command_type}")
await self._send_response(command.command_id, "ERROR", {
"error": f"Unknown command type: {command.command_type}",
"namespace": command.target_namespace
})
return
# Execute command handler
handler = self.command_handlers[command.command_type]
result = await handler(command)
# Send success response
await self._send_response(command.command_id, "SUCCESS", {
"result": result,
"namespace": command.target_namespace
})
# Acknowledge message
message.ack()
except Exception as e:
logger.error(f"Error handling admin command: {e}")
try:
if 'command' in locals():
await self._send_response(command.command_id, "ERROR", {
"error": str(e),
"namespace": getattr(command, 'target_namespace', 'unknown')
})
message.nack(requeue=False)
except:
pass
async def _send_response(self, command_id: str, response_type: str, payload: Dict[str, Any]) -> None:
"""Send response back to Admin Cluster"""
try:
response_data = {
"command_id": command_id,
"response_type": response_type,
"target_cluster": "admin",
"source_cluster": "tenant",
"namespace": payload.get("namespace", "unknown"),
"payload": payload,
"timestamp": datetime.utcnow().isoformat()
}
# Sign the response
response_data["signature"] = self._sign_message(response_data)
# Send message
message = Message(
json.dumps(response_data).encode(),
delivery_mode=DeliveryMode.PERSISTENT
)
await self.channel.default_exchange.publish(
message, routing_key=self.tenant_to_admin_queue.name
)
logger.info(f"Sent response to admin: {response_type} for {command_id}")
except Exception as e:
logger.error(f"Failed to send response to admin: {e}")
def register_handler(self, command_type: str, handler: Callable[[AdminCommand], Any]) -> None:
"""Register handler for specific command type"""
self.command_handlers[command_type] = handler
logger.info(f"Registered handler for command type: {command_type}")
async def send_notification(self, notification_type: str, payload: Dict[str, Any]) -> None:
"""Send notification to Admin Cluster (not in response to a command)"""
try:
notification_data = {
"command_id": str(uuid.uuid4()),
"response_type": f"NOTIFICATION_{notification_type}",
"target_cluster": "admin",
"source_cluster": "tenant",
"namespace": getattr(self.settings, 'TENANT_ID', 'unknown'),
"payload": payload,
"timestamp": datetime.utcnow().isoformat()
}
# Sign the notification
notification_data["signature"] = self._sign_message(notification_data)
# Send message
message = Message(
json.dumps(notification_data).encode(),
delivery_mode=DeliveryMode.PERSISTENT
)
await self.channel.default_exchange.publish(
message, routing_key=self.tenant_to_admin_queue.name
)
logger.info(f"Sent notification to admin: {notification_type}")
except Exception as e:
logger.error(f"Failed to send notification to admin: {e}")
async def disconnect(self) -> None:
"""Disconnect from message bus"""
try:
if self.connection:
await self.connection.close()
logger.info("Disconnected from message bus")
except Exception as e:
logger.error(f"Error disconnecting from message bus: {e}")
# Global instance
message_bus_client = MessageBusClient()
# Command handlers for different admin commands
async def handle_tenant_provision(command: AdminCommand) -> Dict[str, Any]:
"""Handle tenant provisioning command"""
logger.info(f"Processing tenant provision for: {command.target_namespace}")
# TODO: Implement tenant provisioning logic
# - Create tenant directory structure
# - Initialize SQLite database
# - Set up access controls
# - Configure resource quotas
return {
"status": "provisioned",
"namespace": command.target_namespace,
"resources_allocated": command.payload.get("resources", {})
}
async def handle_tenant_suspend(command: AdminCommand) -> Dict[str, Any]:
"""Handle tenant suspension command"""
logger.info(f"Processing tenant suspension for: {command.target_namespace}")
# TODO: Implement tenant suspension logic
# - Disable API access
# - Pause running processes
# - Preserve data integrity
return {
"status": "suspended",
"namespace": command.target_namespace
}
async def handle_tenant_activate(command: AdminCommand) -> Dict[str, Any]:
"""Handle tenant activation command"""
logger.info(f"Processing tenant activation for: {command.target_namespace}")
# TODO: Implement tenant activation logic
# - Restore API access
# - Resume processes
# - Validate system state
return {
"status": "activated",
"namespace": command.target_namespace
}
async def handle_resource_allocate(command: AdminCommand) -> Dict[str, Any]:
"""Handle resource allocation command"""
logger.info(f"Processing resource allocation for: {command.target_namespace}")
# TODO: Implement resource allocation logic
# - Update resource quotas
# - Configure rate limits
# - Enable new capabilities
return {
"status": "allocated",
"namespace": command.target_namespace,
"resources": command.payload.get("resources", {})
}
async def handle_resource_revoke(command: AdminCommand) -> Dict[str, Any]:
"""Handle resource revocation command"""
logger.info(f"Processing resource revocation for: {command.target_namespace}")
# TODO: Implement resource revocation logic
# - Remove resource access
# - Update quotas
# - Gracefully handle running operations
return {
"status": "revoked",
"namespace": command.target_namespace
}
# Register all command handlers
async def initialize_message_bus() -> bool:
"""Initialize message bus with all command handlers"""
try:
# Register command handlers
message_bus_client.register_handler("TENANT_PROVISION", handle_tenant_provision)
message_bus_client.register_handler("TENANT_SUSPEND", handle_tenant_suspend)
message_bus_client.register_handler("TENANT_ACTIVATE", handle_tenant_activate)
message_bus_client.register_handler("RESOURCE_ALLOCATE", handle_resource_allocate)
message_bus_client.register_handler("RESOURCE_REVOKE", handle_resource_revoke)
# Connect to message bus
connected = await message_bus_client.connect()
if connected:
logger.info("Message bus client initialized successfully")
return True
else:
logger.error("Failed to initialize message bus client")
return False
except Exception as e:
logger.error(f"Error initializing message bus: {e}")
return False