#!/usr/bin/env python3 """ Cleanup and teardown engine for C2ingRed deployments """ import os import sys import subprocess import logging import json import glob # Add project root to path sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..')) from utils.common import COLORS, PROVIDER_DIRS, confirm_action def teardown_by_deployment_id(deployment_id): """Teardown infrastructure by deployment ID""" print(f"\n{COLORS['BLUE']}Teardown Infrastructure by ID: {deployment_id}{COLORS['RESET']}") # Look for deployment logs to determine provider and configuration log_files = glob.glob(f"logs/deployment_{deployment_id}*.log") if not log_files: print(f"{COLORS['RED']}No deployment logs found for ID: {deployment_id}{COLORS['RESET']}") return False # Try to find deployment info files info_files = glob.glob(f"logs/deployment_info_{deployment_id}*.txt") if info_files: config = parse_deployment_info(info_files[0]) if config: return execute_teardown(config) print(f"{COLORS['YELLOW']}Could not determine deployment configuration from logs{COLORS['RESET']}") print(f"{COLORS['YELLOW']}Manual cleanup may be required{COLORS['RESET']}") return False def teardown_all_infrastructure(): """Teardown all deployed infrastructure""" print(f"\n{COLORS['RED']}⚠️ WARNING: This will attempt to destroy ALL infrastructure!{COLORS['RESET']}") if not confirm_action("Are you absolutely sure?", default=False): return False # Find all deployment info files info_files = glob.glob("logs/deployment_info_*.txt") if not info_files: print(f"{COLORS['GREEN']}No deployment info files found - nothing to teardown{COLORS['RESET']}") return True print(f"Found {len(info_files)} deployments to teardown:") success_count = 0 for info_file in info_files: config = parse_deployment_info(info_file) if config: deployment_id = config.get('deployment_id', 'unknown') print(f"\nTearing down deployment: {deployment_id}") if execute_teardown(config): success_count += 1 print(f"\n{COLORS['GREEN']}Successfully tore down {success_count}/{len(info_files)} deployments{COLORS['RESET']}") return success_count == len(info_files) def parse_deployment_info(info_file): """Parse deployment info from file to extract configuration""" try: config = {} with open(info_file, 'r') as f: lines = f.readlines() in_config_section = False for line in lines: line = line.strip() if line == "Configuration:": in_config_section = True continue elif line.startswith("-" * 30): continue elif line.startswith("Access Information:"): break if in_config_section and ": " in line: key, value = line.split(": ", 1) # Convert string values back to appropriate types if value.lower() == 'true': value = True elif value.lower() == 'false': value = False elif value.lower() == 'none': value = None config[key] = value return config except Exception as e: logging.error(f"Failed to parse deployment info from {info_file}: {e}") return None def execute_teardown(config): """Execute teardown based on configuration""" provider = config.get('provider') deployment_id = config.get('deployment_id') if not provider or not deployment_id: print(f"{COLORS['RED']}Missing provider or deployment ID{COLORS['RESET']}") return False print(f"{COLORS['BLUE']}Executing teardown for {provider} deployment {deployment_id}...{COLORS['RESET']}") # Set up logging for teardown from utils.common import setup_logging setup_logging(deployment_id, "teardown") try: # Set provider-specific environment variables set_provider_environment_for_teardown(config) # Get correct provider directory provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize()) # Execute teardown playbook playbook = f"providers/{provider_dir}/cleanup.yml" if not os.path.exists(playbook): print(f"{COLORS['YELLOW']}Teardown playbook not found: {playbook}{COLORS['RESET']}") print(f"{COLORS['YELLOW']}Manual cleanup required{COLORS['RESET']}") return manual_cleanup_guidance(config) success = run_teardown_playbook(playbook, config) if success: # Clean up local SSH keys cleanup_ssh_keys_for_deployment(deployment_id) # Move deployment logs to cleanup folder archive_deployment_logs(deployment_id) print(f"{COLORS['GREEN']}Teardown completed successfully{COLORS['RESET']}") else: print(f"{COLORS['RED']}Teardown failed - manual cleanup may be required{COLORS['RESET']}") return success except Exception as e: logging.error(f"Teardown execution failed: {e}") print(f"{COLORS['RED']}Teardown execution failed: {e}{COLORS['RESET']}") return False def set_provider_environment_for_teardown(config): """Set provider-specific environment variables for teardown""" 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'] elif provider == "linode": # Try to get token from config first, then from vars file if config.get('linode_token'): os.environ['LINODE_TOKEN'] = config['linode_token'] else: # Load token from provider vars file try: from utils.common import load_vars_file, PROVIDER_DIRS provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize()) vars_file = f"providers/{provider_dir}/vars.yaml" vars_data = load_vars_file(vars_file) if vars_data and vars_data.get('linode_token'): os.environ['LINODE_TOKEN'] = vars_data['linode_token'] config['linode_token'] = vars_data['linode_token'] # Add to config for later use else: logging.warning("Linode token not found in vars file - cleanup may fail") except Exception as e: logging.warning(f"Failed to load Linode token from vars file: {e}") def run_teardown_playbook(playbook, config): """Run the teardown Ansible playbook""" try: cmd = [ 'ansible-playbook', playbook, '--extra-vars', create_teardown_vars(config) ] if config.get('debug'): cmd.append('-vvv') logging.info(f"Executing teardown: {' '.join(cmd)}") result = subprocess.run( cmd, capture_output=True, text=True, cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ) if result.returncode == 0: logging.info("Teardown playbook executed successfully") return True else: logging.error(f"Teardown playbook failed with return code {result.returncode}") logging.error(f"STDOUT: {result.stdout}") logging.error(f"STDERR: {result.stderr}") return False except Exception as e: logging.error(f"Error executing teardown playbook: {e}") return False def create_teardown_vars(config): """Create Ansible extra vars for teardown""" teardown_vars = { 'deployment_id': config.get('deployment_id'), 'provider': config.get('provider'), 'operation': 'teardown' } # Add instance names for proper cleanup if config.get('redirector_name'): teardown_vars['redirector_name'] = config['redirector_name'] if config.get('c2_name'): teardown_vars['c2_name'] = config['c2_name'] if config.get('tracker_name'): teardown_vars['tracker_name'] = config['tracker_name'] if config.get('attack_box_name'): teardown_vars['attack_box_name'] = config['attack_box_name'] # Add provider-specific vars if present if config.get('aws_access_key'): teardown_vars['aws_access_key'] = config['aws_access_key'] if config.get('aws_secret_key'): teardown_vars['aws_secret_key'] = config['aws_secret_key'] if config.get('aws_region'): teardown_vars['aws_region'] = config['aws_region'] if config.get('linode_token'): teardown_vars['linode_token'] = config['linode_token'] if config.get('linode_region'): teardown_vars['linode_region'] = config['linode_region'] return json.dumps(teardown_vars) def cleanup_ssh_keys_for_deployment(deployment_id): """Clean up SSH keys for a specific deployment""" ssh_dir = os.path.expanduser("~/.ssh") key_pattern = f"c2deploy_{deployment_id}*" for key_file in glob.glob(os.path.join(ssh_dir, key_pattern)): try: os.remove(key_file) logging.info(f"Removed SSH key: {key_file}") except Exception as e: logging.warning(f"Failed to remove SSH key {key_file}: {e}") def archive_deployment_logs(deployment_id): """Archive deployment logs after successful teardown""" try: # Create archive directory archive_dir = "logs/archive" os.makedirs(archive_dir, exist_ok=True) # Move all files related to this deployment log_patterns = [ f"logs/deployment_{deployment_id}*", f"logs/deployment_info_{deployment_id}*", f"logs/teardown_{deployment_id}*" ] for pattern in log_patterns: for file_path in glob.glob(pattern): archive_path = os.path.join(archive_dir, os.path.basename(file_path)) os.rename(file_path, archive_path) logging.info(f"Archived: {file_path} -> {archive_path}") except Exception as e: logging.warning(f"Failed to archive logs for {deployment_id}: {e}") def manual_cleanup_guidance(config): """Provide manual cleanup guidance when automated teardown isn't available""" provider = config.get('provider') deployment_id = config.get('deployment_id') print(f"\n{COLORS['YELLOW']}Manual Cleanup Required{COLORS['RESET']}") print(f"{COLORS['YELLOW']}========================{COLORS['RESET']}") print(f"Deployment ID: {deployment_id}") print(f"Provider: {provider}") if provider == "aws": print(f"\nAWS Resources to check:") print(f"- EC2 instances with tags containing: {deployment_id}") print(f"- Security groups with names containing: {deployment_id}") print(f"- Key pairs with names containing: {deployment_id}") print(f"- EIPs associated with the deployment") elif provider == "linode": print(f"\nLinode Resources to check:") print(f"- Linodes with labels containing: {deployment_id}") print(f"- NodeBalancers with labels containing: {deployment_id}") print(f"- Firewalls with labels containing: {deployment_id}") elif provider == "flokinet": print(f"\nFlokiNET Resources to check:") print(f"- Check your FlokiNET control panel for resources created") print(f"- Look for servers with the deployment ID: {deployment_id}") print(f"\nLocal cleanup:") print(f"- SSH keys: ~/.ssh/c2deploy_{deployment_id}*") print(f"- Log files: logs/*{deployment_id}*") return False # Manual cleanup required if __name__ == "__main__": print("Cleanup engine loaded")