Skip to main content
Glama
deployment.py22.4 kB
"""Deployment FastMCP tools - scenarios, orchestration, monitoring.""" from typing import Any from fastmcp import FastMCP from ludus_mcp.core.client import LudusAPIClient from ludus_mcp.server.handlers.scenarios import ScenarioHandler from ludus_mcp.server.handlers.deployment import DeploymentHandler from ludus_mcp.server.handlers.orchestration import DeploymentOrchestrator from ludus_mcp.server.handlers.validation import ValidationHandler from ludus_mcp.server.tools.utils import LazyHandlerRegistry, format_tool_response def create_deployment_tools(client: LudusAPIClient) -> FastMCP: """Create deployment operation tools. Args: client: Ludus API client Returns: FastMCP instance with deployment tools registered """ mcp = FastMCP("Deployment Operations") registry = LazyHandlerRegistry(client) # ==================== SCENARIO TOOLS ==================== @mcp.tool() async def list_scenarios() -> dict[str, str]: """List all available scenarios. Returns: Dictionary of scenario keys and descriptions """ handler = registry.get_handler("scenario", ScenarioHandler) result = await handler.list_scenarios() return result @mcp.tool() async def deploy_scenario( scenario_key: str, user_id: str | None = None, ensure_roles: bool = True, siem_type: str = "wazuh", resource_profile: str = "recommended", customize: bool = False, randomize: bool = False, custom_users: list[dict[str, Any]] | None = None, vulnerability_config: dict[str, Any] | None = None, network_customizations: dict[str, Any] | None = None, vm_customizations: dict[str, Any] | None = None, ) -> dict: """Deploy a scenario with optional SIEM integration and customization. IMPORTANT: This tool generates a FRESH configuration for each call. Each deployment builds a new scenario from scratch - no state is reused. NO FILE UPLOAD REQUIRED: This tool automatically generates the configuration from the scenario parameters. You do NOT need to provide a config file or manual configuration. CUSTOMIZATION AND RANDOMIZATION: - Use customize=True with custom parameters to deploy a customized scenario - Use randomize=True to deploy a randomized version with varied users/vulnerabilities - All customization parameters are optional - defaults used if not specified Recommended Workflow: 1. Use preview_scenario() first to verify what will be deployed 2. (Optional) Abort any existing deployment: abort_range_deployment() 3. (Optional) Delete existing range: delete_range(confirm=True) 4. Deploy the scenario: deploy_scenario(scenario_key='redteam-lab-lite') 5. Monitor progress: monitor_deployment() or quick_status() Internal Workflow: 1. Generates fresh scenario configuration based on parameters 2. Applies customizations or randomization if requested 3. Sets the configuration in Ludus (via PUT /range/config) 4. Verifies the configuration was set correctly 5. Starts deployment (via POST /range/deploy) 6. Generates comprehensive walkthrough 7. Returns deployment status, VM details, and walkthrough Available scenarios: - redteam-lab-lite: 5 VMs (DC, 2 workstations, file server, Kali) - redteam-lab-intermediate: 10 VMs (DMZ, internal network, multiple servers) - redteam-lab-advanced: 21 VMs (2 forests with trust, DMZ, secure zone) - blueteam-lab-lite: 6 VMs (SOC with SIEM, detection) - blueteam-lab-intermediate: 11 VMs (SOC with EDR, IDS) - blueteam-lab-advanced: 21 VMs (Enterprise SOC, full stack) - purpleteam-lab-lite: 6 VMs (Red/Blue collaborative) - purpleteam-lab-intermediate: 10 VMs (Purple team with EDR) - purpleteam-lab-advanced: 13 VMs (Full SOC + adversary emulation) - malware-re-lab-lite: 3 VMs (Basic malware analysis) - malware-re-lab-intermediate: 7 VMs (Pro malware lab) - malware-re-lab-advanced: 18 VMs (Enterprise malware research) - wireless-lab: 2 VMs (WiFi pentesting) Args: scenario_key: Scenario identifier (e.g., 'redteam-lab-lite') user_id: Optional user ID (admin only) ensure_roles: Ensure required Ansible roles are installed siem_type: SIEM type to include (wazuh, splunk, elastic, security-onion, none) resource_profile: Resource allocation profile (minimal, recommended, maximum) customize: Enable customization mode (use provided customizations) randomize: Enable randomization mode (generate random customizations) custom_users: List of custom user dicts with keys: username, password, display_name, groups (list), department (optional), title (optional), etc. vulnerability_config: Dict with keys like esc1_enabled, esc2_enabled, open_shares, etc. network_customizations: Dict with vlan_changes, additional_rules, remove_rules, etc. vm_customizations: Dict with vm_count_overrides, additional_vms, remove_vms, etc. Returns: Deployment result with scenario details, VM list, deployment status, and walkthrough Examples: # Simple deployment (default) deploy_scenario(scenario_key='redteam-lab-lite', siem_type='none') # Randomized deployment deploy_scenario(scenario_key='redteam-lab-lite', randomize=True) # Custom users deployment deploy_scenario( scenario_key='redteam-lab-lite', customize=True, custom_users=[ { "username": "admin.user", "password": "CustomPass123!", "display_name": "Admin User", "groups": ["Domain Users", "Domain Admins"], "department": "IT" } ] ) # Custom vulnerabilities deploy_scenario( scenario_key='redteam-lab-lite', customize=True, vulnerability_config={ "esc1_enabled": True, "esc8_enabled": True, "open_shares": True } ) Natural Language Translation: - "Make a custom range using redteam-lab-lite" → customize=True - "Randomize redteam-lab-lite" → randomize=True - "Deploy with different users" → customize=True, custom_users=[...] Note: For automated deployments with validation and monitoring, consider using smart_deploy() instead, which handles the full workflow. """ handler = registry.get_handler("scenario", ScenarioHandler) result = await handler.deploy_scenario(scenario_key, user_id, ensure_roles, siem_type, resource_profile) return result @mcp.tool() async def get_scenario_config( scenario_key: str, siem_type: str = "wazuh" ) -> dict: """Get scenario configuration. Args: scenario_key: Scenario identifier siem_type: SIEM type to include Returns: Scenario configuration """ handler = registry.get_handler("scenario", ScenarioHandler) result = await handler.get_scenario_config(scenario_key, siem_type) return result @mcp.tool() async def get_scenario_yaml( scenario_key: str, siem_type: str = "wazuh" ) -> str: """Get scenario configuration as YAML. Args: scenario_key: Scenario identifier siem_type: SIEM type to include Returns: YAML configuration string """ handler = registry.get_handler("scenario", ScenarioHandler) result = await handler.get_scenario_yaml(scenario_key, siem_type) return result @mcp.tool() async def preview_scenario( scenario_key: str, siem_type: str = "wazuh", resource_profile: str = "recommended" ) -> dict: """Preview a scenario before deployment with detailed information. IMPORTANT: Generates a FRESH configuration preview for the specified scenario. Use this BEFORE deploy_scenario to verify what will be deployed. Shows VMs, network topology, resource requirements, and deployment estimates. Each preview call builds the scenario from scratch with your specified parameters. Args: scenario_key: Scenario identifier (e.g., 'redteam-lab-lite') siem_type: SIEM type to include (wazuh, splunk, elastic, security-onion, none) resource_profile: Resource allocation profile (minimal, recommended, maximum) Returns: Preview with configuration, visualization, and estimates including: - Complete VM list with hostnames, templates, resources - Network rules and VLAN topology - Resource summary (total RAM, CPUs, disk space) - Estimated deployment time - Exact deployment command to use Recommended workflow: 1. preview_scenario('redteam-lab-lite', 'none', 'minimal') 2. Review the VM list and resources 3. deploy_scenario('redteam-lab-lite', 'none', 'minimal') """ handler = registry.get_handler("scenario", ScenarioHandler) result = await handler.preview_scenario(scenario_key, siem_type, resource_profile) return format_tool_response(result) # ==================== DEPLOYMENT STATUS TOOLS ==================== @mcp.tool() async def quick_status(user_id: str | None = None) -> str: """Get one-line deployment status with emoji indicators. Args: user_id: Optional user ID (admin only) Returns: Formatted status string """ handler = registry.get_handler("deployment", DeploymentHandler) result = await handler.quick_status(user_id) return result @mcp.tool() async def get_deployment_status(user_id: str | None = None) -> dict: """Get current deployment status with detailed information. Args: user_id: Optional user ID (admin only) Returns: Detailed deployment status """ handler = registry.get_handler("deployment", DeploymentHandler) result = await handler.get_deployment_status(user_id) return result @mcp.tool() async def get_range_logs(user_id: str | None = None) -> str: """Get deployment logs for the range. Args: user_id: Optional user ID (admin only) Returns: Deployment logs """ result = await client.get_range_logs(user_id) return result # ==================== ORCHESTRATION TOOLS ==================== @mcp.tool() async def smart_deploy( scenario_key: str, siem_type: str = "wazuh", auto_validate: bool = True, auto_snapshot: bool = False, auto_monitor: bool = True, user_id: str | None = None ) -> dict: """Smart deployment with validation, optional snapshot, and auto-monitoring. RECOMMENDED: This is the preferred method for deploying scenarios as it includes validation, error checking, and monitoring guidance. NO FILE UPLOAD REQUIRED: This tool automatically generates the configuration from the scenario parameters. You do NOT need to provide a config file. Workflow: 1. Validates the scenario configuration (if auto_validate=True) 2. Creates snapshot if requested (if auto_snapshot=True) 3. Generates and sets the configuration in Ludus 4. Verifies the configuration was set correctly 5. Starts the deployment 6. Provides monitoring guidance and commands When to use: - Use smart_deploy() for most deployments (recommended) - Use deploy_scenario() if you need more control or don't want validation - Use deploy_range() only if you have a custom configuration dict Args: scenario_key: Scenario to deploy (e.g., 'redteam-lab-lite') siem_type: SIEM type to include (wazuh, splunk, elastic, security-onion, none) auto_validate: Validate configuration before deploying (default: True) auto_snapshot: Create snapshot before deployment (default: False) auto_monitor: Enable auto-monitoring after deployment (default: True) user_id: Optional user ID (admin only) Returns: Smart deployment result with monitoring guidance and status Example: # Recommended: Use smart_deploy for automated deployments smart_deploy( scenario_key='redteam-lab-lite', siem_type='none', auto_validate=True, auto_monitor=True ) """ handler = registry.get_handler("orchestrator", DeploymentOrchestrator) result = await handler.smart_deploy( scenario_key, siem_type, auto_validate, auto_snapshot, auto_monitor, user_id ) return format_tool_response(result) @mcp.tool() async def monitor_deployment( user_id: str | None = None, check_interval: int = 30, max_checks: int = 40 ) -> dict: """Monitor deployment progress with periodic updates. Args: user_id: Optional user ID (admin only) check_interval: Seconds between checks max_checks: Maximum number of checks Returns: Monitoring update with progress information """ handler = registry.get_handler("orchestrator", DeploymentOrchestrator) result = await handler.monitor_deployment_once(user_id) return format_tool_response(result) @mcp.tool() async def deployment_timeline(user_id: str | None = None) -> dict: """Get deployment timeline with progress tracking. Args: user_id: Optional user ID (admin only) Returns: Timeline with steps and progress """ handler = registry.get_handler("orchestrator", DeploymentOrchestrator) result = await handler.get_deployment_timeline(user_id) return format_tool_response(result) # ==================== VALIDATION TOOLS ==================== @mcp.tool() async def validate_config(config: dict[str, Any]) -> dict: """Validate range configuration before deployment. Args: config: Range configuration to validate Returns: Validation result with errors and warnings """ handler = registry.get_handler("validation", ValidationHandler) result = await handler.validate_config(config) return format_tool_response(result) @mcp.tool() async def handle_adws_recovery( wait_minutes: int = 10, auto_retry: bool = True, user_id: str | None = None, ) -> dict: """Handle Active Directory Web Services (ADWS) recovery for stuck deployments. ADWS errors are common during Active Directory deployments. This tool: 1. Checks if deployment is stuck on ADWS errors 2. Waits for ADWS to start (default: 10 minutes) 3. Optionally retries the failed tasks automatically **When to use:** - Deployment failed with "Unable to find a default server with Active Directory Web Services running" - Deployment is stuck on OU configuration tasks - DC VM is deployed but AD services haven't fully started **What this does:** - Checks deployment logs for ADWS errors - Waits for ADWS to initialize (5-15 minutes typical) - Retries failed Ansible tasks (if auto_retry=True) - Provides status updates during wait Args: wait_minutes: Minutes to wait for ADWS to start (default: 10, max: 30) auto_retry: Automatically retry failed tasks after wait (default: True) user_id: Optional user ID (admin only) Returns: Recovery result with status and next steps Example: # Handle ADWS recovery with auto-retry result = await handle_adws_recovery(wait_minutes=10, auto_retry=True) # Just wait and check status (manual retry later) result = await handle_adws_recovery(wait_minutes=15, auto_retry=False) """ import asyncio handler = registry.get_handler("deployment", DeploymentHandler) try: # Check current deployment status range_info = await handler.client.get_range(user_id) range_state = range_info.get("rangeState", "UNKNOWN") # Get logs to check for ADWS errors logs = await handler.get_full_logs(user_id) logs_text = logs.get("logs", "") if isinstance(logs, dict) else str(logs) logs_lower = logs_text.lower() has_adws_error = ( "active directory web services" in logs_lower or "adws" in logs_lower or "unable to find a default server with active directory web services" in logs_lower ) if not has_adws_error and range_state not in ["DEPLOYING", "CONFIGURING"]: return format_tool_response({ "status": "no_action_needed", "message": "No ADWS errors detected. Deployment may have completed or failed for other reasons.", "range_state": range_state, "suggestion": "Check deployment status: quick_status() or get_deployment_status()", }) result = { "status": "recovery_started", "adws_error_detected": has_adws_error, "range_state": range_state, "wait_minutes": min(wait_minutes, 30), # Cap at 30 minutes "auto_retry": auto_retry, "steps": [], } # Wait for ADWS to start wait_seconds = min(wait_minutes, 30) * 60 check_interval = 60 # Check every minute checks = wait_seconds // check_interval result["steps"].append(f"Waiting {wait_minutes} minutes for ADWS to start...") result["steps"].append("ADWS typically starts 5-15 minutes after domain promotion") result["steps"].append(f"Checking deployment status every {check_interval} seconds") # Wait and check periodically for i in range(checks): await asyncio.sleep(check_interval) # Check if deployment completed current_info = await handler.client.get_range(user_id) current_state = current_info.get("rangeState", "UNKNOWN") if current_state == "READY": result["status"] = "recovery_success" result["message"] = "Deployment completed successfully during wait period!" result["steps"].append(f"✓ Deployment completed after {i+1} minutes") return format_tool_response(result) # Check if ADWS errors are gone current_logs = await handler.get_full_logs(user_id) current_logs_text = current_logs.get("logs", "") if isinstance(current_logs, dict) else str(current_logs) if "active directory web services" not in current_logs_text.lower(): result["status"] = "adws_started" result["steps"].append(f"✓ ADWS appears to have started after {i+1} minutes") break # If auto_retry is enabled, retry failed tasks if auto_retry and result["status"] == "adws_started": result["steps"].append("Attempting to retry failed tasks...") try: # Retry deployment with configure tags (OU creation, user creation, etc.) retry_result = await handler.client.deploy_range( config=None, user_id=user_id, tags="configure,user", # Common tags for AD configuration ) result["status"] = "retry_initiated" result["retry_result"] = retry_result result["steps"].append("✓ Retry initiated - deployment should continue") except Exception as e: result["status"] = "retry_failed" result["error"] = str(e) result["steps"].append(f"✗ Retry failed: {e}") result["steps"].append("You may need to manually retry: deploy_range(tags='configure,user')") if result["status"] == "recovery_started": result["status"] = "wait_complete" result["message"] = f"Wait period completed. Check deployment status and retry if needed." result["steps"].append("If ADWS still not started, wait longer or check DC VM status") result["next_steps"] = [ "Check deployment status: quick_status()", "If still failing, wait longer: handle_adws_recovery(wait_minutes=15)", "Or manually retry: deploy_range(tags='configure,user')", "Check DC VM is running: get_range()", ] return format_tool_response(result) except Exception as e: return format_tool_response({ "status": "error", "error": str(e), "message": "Failed to handle ADWS recovery", }) return mcp

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tjnull/Ludus-FastMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server