#!/usr/bin/env python3 """ Deployment engine for C2ingRed infrastructure deployments """ import os import sys import subprocess import logging import json # Add project root to path sys.path.append(os.path.join(os.path.dirname(__file__), '..')) from utils.common import COLORS, PROVIDER_DIRS _SECRET_KEYS = frozenset({ 'linode_token', 'aws_access_key', 'aws_secret_key', 'flokinet_api_key', 'smtp_auth_pass', 'smtp_password', 'ftp_password', 'api_key', 'password', 'secret', 'token', }) def deploy_infrastructure(config): """ Deploy infrastructure based on the provided configuration. Args: config: Dictionary containing deployment configuration Returns: bool: True if deployment succeeded, False otherwise """ provider = config.get('provider') deployment_id = config.get('deployment_id') if not provider or not deployment_id: logging.error("Missing provider or deployment ID in configuration") return False # Determine which playbook to use playbook = determine_playbook(config) if not playbook: logging.error("Could not determine appropriate playbook for deployment") return False if not os.path.exists(playbook): logging.error(f"Playbook not found: {playbook}") print(f"{COLORS['RED']}Playbook not found: {playbook}{COLORS['RESET']}") return False logging.info(f"Using playbook: {playbook}") # Set provider-specific environment variables set_provider_environment(config) # Execute the deployment return execute_playbook(playbook, config) def determine_playbook(config): """ Determine the correct playbook based on deployment configuration. Args: config: Deployment configuration dictionary Returns: str: Path to the playbook file """ provider = config.get('provider') provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize()) base_path = f"providers/{provider_dir}" # Attack box deployment if config.get('attack_box_deployment'): return f"{base_path}/attack_box.yml" # Redirector-only deployment if config.get('redirector_only'): return f"{base_path}/redirector.yml" # Tracker deployment if config.get('tracker_deployment'): return f"{base_path}/tracker.yml" # Phishing infrastructure deployment if config.get('phishing_deployment'): # Check for provider-specific phishing playbook first provider_specific = f"{base_path}/{provider}_phishing.yml" if os.path.exists(provider_specific): return provider_specific return f"{base_path}/phishing.yml" # Chat server deployment if config.get('chat_deployment'): return f"{base_path}/chat_server.yml" # C2 deployment (default for C2 infrastructure) if config.get('c2_only') or config.get('deploy_c2') or config.get('c2_framework'): return f"{base_path}/c2.yml" # Default to C2 playbook return f"{base_path}/c2.yml" def set_provider_environment(config): """ Set provider-specific environment variables for deployment. Args: config: Deployment configuration dictionary """ provider = config.get('provider') if provider == "aws": if config.get('aws_access_key'): os.environ['AWS_ACCESS_KEY_ID'] = config['aws_access_key'] if config.get('aws_secret_key'): os.environ['AWS_SECRET_ACCESS_KEY'] = config['aws_secret_key'] if config.get('aws_region'): os.environ['AWS_DEFAULT_REGION'] = config['aws_region'] elif provider == "linode": if config.get('linode_token'): os.environ['LINODE_TOKEN'] = config['linode_token'] else: # Try to load from vars file try: from utils.common import load_vars_file vars_data = load_vars_file(provider) if vars_data and vars_data.get('linode_token'): os.environ['LINODE_TOKEN'] = vars_data['linode_token'] config['linode_token'] = vars_data['linode_token'] except Exception as e: logging.warning(f"Failed to load Linode token from vars file: {e}") elif provider == "flokinet": if config.get('flokinet_api_key'): os.environ['FLOKINET_API_KEY'] = config['flokinet_api_key'] def execute_playbook(playbook, config): """ Execute an Ansible playbook with the provided configuration. Args: playbook: Path to the Ansible playbook config: Deployment configuration dictionary Returns: bool: True if playbook executed successfully, False otherwise """ try: # Build the ansible-playbook command cmd = [ 'ansible-playbook', playbook, '--extra-vars', create_extra_vars(config) ] # Add verbosity if debug mode is enabled if config.get('debug'): cmd.append('-vvv') logging.info(f"Executing: {' '.join(cmd[:3])} ...") # Get log file path for output log_file = f"logs/deployment_{config['deployment_id']}.log" # Execute the playbook — log file restricted to owner-only (contains Ansible output) log_fd = os.open(log_file, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600) with open(log_fd, 'a') as log_output: # Write command to log log_output.write(f"\n{'='*60}\n") log_output.write(f"Executing playbook: {playbook}\n") log_output.write(f"{'='*60}\n\n") log_output.flush() process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) # Stream output to both console and log file for line in process.stdout: # Write to log file log_output.write(line) log_output.flush() # Print select lines to console for user feedback if should_print_line(line): print(line.rstrip()) process.wait() if process.returncode == 0: logging.info("Playbook executed successfully") # Save deployment info save_deployment_info(config) return True else: logging.error(f"Playbook failed with return code {process.returncode}") print(f"\n{COLORS['RED']}Deployment failed. Check logs for details: {log_file}{COLORS['RESET']}") return False except FileNotFoundError: logging.error("ansible-playbook command not found. Is Ansible installed?") print(f"{COLORS['RED']}Error: ansible-playbook not found. Please install Ansible.{COLORS['RESET']}") return False except Exception as e: logging.error(f"Error executing playbook: {e}") print(f"{COLORS['RED']}Error executing playbook: {e}{COLORS['RESET']}") return False def should_print_line(line): """ Determine if a line should be printed to console. Filters out verbose Ansible output to show only key information. Args: line: Output line from Ansible Returns: bool: True if line should be printed """ # Always show task names and important messages important_patterns = [ 'TASK [', 'PLAY [', 'ok:', 'changed:', 'failed:', 'fatal:', 'skipping:', 'PLAY RECAP', '=> {', 'msg:', 'IP Address:', 'Instance ID:', ] for pattern in important_patterns: if pattern in line: return True return False def create_extra_vars(config): """ Create JSON string of extra variables for Ansible. Args: config: Deployment configuration dictionary Returns: str: JSON string of variables """ # Credentials are passed via environment variables (set_provider_environment). # Strip them here so they never appear in --extra-vars or process listings. extra_vars = {} for key, value in config.items(): if value is None: continue if any(secret in key.lower() for secret in _SECRET_KEYS): continue if isinstance(value, bool): extra_vars[key] = value elif isinstance(value, (str, int, float, list, dict)): extra_vars[key] = value return json.dumps(extra_vars) def save_deployment_info(config): """ Save deployment information to a file for later reference/cleanup. Args: config: Deployment configuration dictionary """ try: deployment_id = config.get('deployment_id') info_file = f"logs/deployment_info_{deployment_id}.txt" with open(info_file, 'w') as f: f.write(f"Deployment Information\n") f.write(f"{'='*50}\n") f.write(f"Deployment ID: {deployment_id}\n") f.write(f"Provider: {config.get('provider')}\n") f.write(f"Deployment Type: {config.get('deployment_type', 'unknown')}\n") f.write(f"\n") f.write(f"Configuration:\n") f.write(f"{'-'*30}\n") # Write key configuration items keys_to_save = [ 'provider', 'deployment_id', 'deployment_type', 'attack_box_name', 'c2_name', 'redirector_name', 'tracker_name', 'chat_server_name', 'domain', 'c2_subdomain', 'redirector_subdomain', 'c2_framework', 'redirector_type', 'linode_region', 'aws_region', 'setup_vpn', 'setup_tor', 'ssh_key_path', 'matrix_admin_user', 'enable_bot_support' ] for key in keys_to_save: if key in config and config[key] is not None: # Don't save sensitive data if 'password' in key.lower() or 'token' in key.lower() or 'secret' in key.lower(): continue f.write(f"{key}: {config[key]}\n") f.write(f"\n") f.write(f"Access Information:\n") f.write(f"{'-'*30}\n") f.write(f"(Instance IPs will be displayed in Ansible output above)\n") logging.info(f"Deployment info saved to: {info_file}") except Exception as e: logging.warning(f"Failed to save deployment info: {e}") if __name__ == "__main__": print("Deployment engine loaded")