Files
2025-08-21 16:25:00 -04:00

225 lines
8.6 KiB
Python

#!/usr/bin/env python3
"""
Common utilities and constants for C2ingRed deployment system
"""
import os
import sys
import random
import string
import logging
import subprocess
import re
from datetime import datetime
# Constants for providers
PROVIDERS = ["aws", "linode", "flokinet"]
DEFAULT_SSH_USER = {
"aws": "kali",
"linode": "root",
"flokinet": "root"
}
# Directory names - maintain correct case for each provider
PROVIDER_DIRS = {
"aws": "AWS",
"linode": "Linode",
"flokinet": "FlokiNET"
}
# Color codes for terminal output
COLORS = {
"RESET": "\033[0m",
"RED": "\033[91m",
"GREEN": "\033[92m",
"YELLOW": "\033[93m",
"BLUE": "\033[94m",
"PURPLE": "\033[95m",
"CYAN": "\033[96m",
"WHITE": "\033[97m",
"GRAY": "\033[90m"
}
def clear_screen():
"""Clear the terminal screen"""
os.system('cls' if os.name == 'nt' else 'clear')
def print_banner():
"""Print the C2ingRed banner"""
banner = f"""
{COLORS['BLUE']}========================================================{COLORS['RESET']}
{COLORS['BLUE']} ██████╗██████╗ ██╗███╗ ██╗ ██████╗ ██████╗ ███████╗██████╗{COLORS['RESET']}
{COLORS['BLUE']} ██╔════╝╚════██╗██║████╗ ██║██╔════╝ ██╔══██╗██╔════╝██╔══██╗{COLORS['RESET']}
{COLORS['BLUE']} ██║ █████╔╝██║██╔██╗ ██║██║ ███╗██████╔╝█████╗ ██║ ██║{COLORS['RESET']}
{COLORS['BLUE']} ██║ ██╔═══╝ ██║██║╚██╗██║██║ ██║██╔══██╗██╔══╝ ██║ ██║{COLORS['RESET']}
{COLORS['BLUE']} ╚██████╗███████╗██║██║ ╚████║╚██████╔╝██║ ██║███████╗██████╔╝{COLORS['RESET']}
{COLORS['BLUE']} ╚═════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═════╝{COLORS['RESET']}
{COLORS['BLUE']} {COLORS['RESET']}
{COLORS['BLUE']} Red Team Infrastructure Deployment Tool {COLORS['RESET']}
{COLORS['BLUE']}========================================================{COLORS['RESET']}
"""
print(banner)
def generate_random_string(length=8):
"""Generate a random string of letters and digits."""
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
def generate_deployment_id():
"""Generate a consistent deployment ID for all resources in this deployment"""
from utils.name_generator import generate_deployment_id as generate_verb_animal_id
return generate_verb_animal_id()
def archive_old_logs(max_logs_to_keep=2):
"""Archive old log files to keep logs directory clean - more aggressive archiving"""
import glob
import shutil
from pathlib import Path
log_dir = "logs"
archive_dir = os.path.join(log_dir, "archive")
if not os.path.exists(log_dir):
return
# Create archive directory if it doesn't exist
os.makedirs(archive_dir, exist_ok=True)
# Get all log files (excluding archive directory) - ANY existing logs should be archived
log_files = []
for pattern in ["deployment_*.log", "teardown_*.log", "20*.log"]:
log_files.extend(glob.glob(os.path.join(log_dir, pattern)))
# Remove duplicates and filter out archive directory
log_files = [f for f in set(log_files) if "archive" not in f]
# Sort by modification time (newest first)
log_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
# Archive ALL deployment logs to keep directory clean for new deployments
if len(log_files) > 0:
print(f"Found {len(log_files)} log files to archive")
archived_count = 0
for log_file in log_files:
try:
filename = os.path.basename(log_file)
# Add timestamp to archived filename to prevent conflicts
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archived_filename = f"{timestamp}_{filename}"
archive_path = os.path.join(archive_dir, archived_filename)
shutil.move(log_file, archive_path)
archived_count += 1
print(f"Archived: {filename} -> archive/{archived_filename}")
except Exception as e:
print(f"Failed to archive {log_file}: {e}")
if archived_count > 0:
print(f"Archived {archived_count} log files to {archive_dir}")
# Also archive old deployment info files
info_files = glob.glob(os.path.join(log_dir, "deployment_info_*.txt"))
if len(info_files) > 0:
print(f"Found {len(info_files)} info files to archive")
for info_file in info_files:
try:
filename = os.path.basename(info_file)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
archived_filename = f"{timestamp}_{filename}"
archive_path = os.path.join(archive_dir, archived_filename)
shutil.move(info_file, archive_path)
print(f"Archived: {filename} -> archive/{archived_filename}")
except Exception as e:
print(f"Failed to archive {info_file}: {e}")
def setup_logging(deployment_id=None, operation_type="deployment"):
"""Set up logging for the deployment or teardown"""
log_dir = "logs"
os.makedirs(log_dir, exist_ok=True)
# Archive old logs before starting new deployment
if operation_type == "deployment":
archive_old_logs()
# Create distinct log files for deployment vs teardown operations
if operation_type == "teardown":
log_file = os.path.join(log_dir, f"teardown_{deployment_id}.log")
else:
log_file = os.path.join(log_dir, f"deployment_{deployment_id}.log")
# Clear any existing handlers
root_logger = logging.getLogger()
root_logger.handlers.clear()
# Configure file handler to log DEBUG and above for full verbosity
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.DEBUG)
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_formatter)
# Add console handler for INFO level and above
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_formatter = logging.Formatter('[%(levelname)s] %(message)s')
console_handler.setFormatter(console_formatter)
# Configure root logger
root_logger.setLevel(logging.DEBUG) # Capture everything at root level
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler)
logging.info(f"{operation_type.capitalize()} operation started")
logging.info(f"Deployment ID: {deployment_id}")
logging.info(f"Full verbose output will be captured in: {log_file}")
return log_file
def load_vars_file(provider):
"""Load vars.yaml for the specified provider"""
if provider not in PROVIDER_DIRS:
return {}
# Use correct case for directory
provider_dir = PROVIDER_DIRS[provider]
vars_file = f"providers/{provider_dir}/vars.yaml"
if os.path.exists(vars_file):
try:
import yaml
with open(vars_file, 'r') as f:
return yaml.safe_load(f) or {}
except Exception as e:
logging.error(f"Failed to load {vars_file}: {e}")
return {}
else:
logging.warning(f"Vars file not found: {vars_file}")
return {}
def validate_ip_address(ip):
"""Validate IP address format"""
pattern = r'^(\d{1,3}\.){3}\d{1,3}$'
return re.match(pattern, ip) is not None
def get_public_ip():
"""Get the user's public IP address"""
try:
import requests
return requests.get('https://api.ipify.org', timeout=5).text.strip()
except:
return None
def confirm_action(message, default=False):
"""Ask for user confirmation with a yes/no prompt"""
prompt = f"{message} ({'Y/n' if default else 'y/N'}): "
response = input(prompt).lower().strip()
if not response:
return default
return response in ['y', 'yes']
def wait_for_input(message="Press Enter to continue..."):
"""Wait for user input before continuing"""
input(f"\n{message}")