fixing aws ssh issue

This commit is contained in:
n0mad1k
2025-05-12 10:50:48 -04:00
parent 381981f954
commit c816a74730
+180 -141
View File
@@ -1258,9 +1258,11 @@ def run_ansible_playbook(playbook, inventory, config, debug=False):
"--extra-vars", "ansible_facts_callback=json" # Get facts in JSON format "--extra-vars", "ansible_facts_callback=json" # Get facts in JSON format
] ]
# Add verbosity if debug mode is enabled # Add verbosity if debug mode is enabled - IMPORTANT CHANGE HERE
if debug: if debug:
cmd.append("-vvv") cmd.append("-vvv")
# Set ANSIBLE_STDOUT_CALLBACK for human-readable output
env["ANSIBLE_STDOUT_CALLBACK"] = "debug"
else: else:
cmd.append("-v") cmd.append("-v")
@@ -1270,38 +1272,77 @@ def run_ansible_playbook(playbook, inventory, config, debug=False):
else: else:
logging.info(f"Running Ansible playbook: {playbook}") logging.info(f"Running Ansible playbook: {playbook}")
# Run the command # Run the command - MODIFIED TO DISPLAY OUTPUT IN REAL-TIME
try: try:
result = subprocess.run( process = subprocess.Popen(
cmd, cmd,
check=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
text=True, text=True,
bufsize=1, # Line buffered
env=env env=env
) )
# Stream output in real-time
stdout_output = []
stderr_output = []
# Create separate threads to read stdout and stderr
def read_output(pipe, output_list, is_stdout=True):
prefix = COLORS['GREEN'] if is_stdout else COLORS['RED']
for line in iter(pipe.readline, ''):
output_list.append(line)
# Only print if debug mode is on or if it's an important message
if debug or ('TASK' in line or 'PLAY' in line or 'changed=' in line or 'ok=' in line or 'failed=' in line):
print(f"{prefix}{line.rstrip()}{COLORS['RESET']}")
pipe.close()
from threading import Thread
stdout_thread = Thread(target=read_output, args=(process.stdout, stdout_output, True))
stderr_thread = Thread(target=read_output, args=(process.stderr, stderr_output, False))
stdout_thread.daemon = True
stderr_thread.daemon = True
stdout_thread.start()
stderr_thread.start()
# Wait for process to complete
return_code = process.wait()
# Wait for output threads to complete
stdout_thread.join()
stderr_thread.join()
# Join the output
stdout_result = ''.join(stdout_output)
stderr_result = ''.join(stderr_output)
# Extract IP addresses from output # Extract IP addresses from output
if "C2 Server IP:" in result.stdout: if "C2 Server IP:" in stdout_result:
ip_match = re.search(r"C2 Server IP: ([0-9.]+)", result.stdout) ip_match = re.search(r"C2 Server IP: ([0-9.]+)", stdout_result)
if ip_match: if ip_match:
config['c2_ip'] = ip_match.group(1) config['c2_ip'] = ip_match.group(1)
logging.info(f"Extracted C2 IP: {config['c2_ip']}") logging.info(f"Extracted C2 IP: {config['c2_ip']}")
if "Redirector IP:" in result.stdout: if "Redirector IP:" in stdout_result:
ip_match = re.search(r"Redirector IP: ([0-9.]+)", result.stdout) ip_match = re.search(r"Redirector IP: ([0-9.]+)", stdout_result)
if ip_match: if ip_match:
config['redirector_ip'] = ip_match.group(1) config['redirector_ip'] = ip_match.group(1)
logging.info(f"Extracted Redirector IP: {config['redirector_ip']}") logging.info(f"Extracted Redirector IP: {config['redirector_ip']}")
if return_code == 0:
logging.info(f"Playbook {playbook} executed successfully") logging.info(f"Playbook {playbook} executed successfully")
return True, result.stdout, result.stderr return True, stdout_result, stderr_result
except subprocess.CalledProcessError as e: else:
logging.error(f"Playbook {playbook} failed with exit code {e.returncode}") logging.error(f"Playbook {playbook} failed with exit code {return_code}")
return False, stdout_result, stderr_result
except Exception as e:
logging.error(f"Error running playbook {playbook}: {e}")
if debug: if debug:
logging.error(f"Command stdout: {e.stdout}") import traceback
logging.error(f"Command stderr: {e.stderr}") logging.error(traceback.format_exc())
return False, e.stdout, e.stderr return False, "", str(e)
def deploy_infrastructure(config): def deploy_infrastructure(config):
"""Deploy infrastructure based on provider and configuration""" """Deploy infrastructure based on provider and configuration"""
@@ -1540,7 +1581,7 @@ def run_tests(config):
def ssh_to_instance(config): def ssh_to_instance(config):
"""SSH into the deployed instance with improved user detection and error handling""" """SSH into the deployed instance with improved AWS key handling"""
logging.info("Connecting to instance via SSH...") logging.info("Connecting to instance via SSH...")
# Determine which IP to use based on deployment type # Determine which IP to use based on deployment type
@@ -1568,6 +1609,14 @@ def ssh_to_instance(config):
# Get SSH key and user # Get SSH key and user
ssh_key = config.get('ssh_key') ssh_key = config.get('ssh_key')
if not ssh_key:
# For AWS, check for deployment_id specific keys with .pem extension
if config.get('provider') == 'aws' and config.get('deployment_id'):
potential_key = os.path.expanduser(f"~/.ssh/c2deploy_{config.get('deployment_id')}.pem")
if os.path.exists(potential_key):
ssh_key = potential_key
logging.info(f"Found AWS key at {ssh_key}")
if not ssh_key: if not ssh_key:
logging.error("No SSH key specified") logging.error("No SSH key specified")
print(f"{COLORS['RED']}No SSH key specified. Cannot SSH.{COLORS['RESET']}") print(f"{COLORS['RED']}No SSH key specified. Cannot SSH.{COLORS['RESET']}")
@@ -1596,7 +1645,7 @@ def ssh_to_instance(config):
# Try each SSH user until one works # Try each SSH user until one works
for ssh_user in ssh_users: for ssh_user in ssh_users:
print(f"\n{COLORS['YELLOW']}Trying SSH with user: {ssh_user}{COLORS['RESET']}") print(f"\n{COLORS['YELLOW']}Trying SSH with user: {ssh_user}{COLORS['RESET']}")
print(f" Manual SSH command: ssh -i {ssh_key} {ssh_user}@{ip} -t tmux") print(f" Manual SSH command: ssh -i {ssh_key} {ssh_user}@{ip}")
# Build SSH command # Build SSH command
ssh_cmd = [ ssh_cmd = [
@@ -1812,6 +1861,30 @@ def teardown_infrastructure(config):
provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize()) provider_dir = PROVIDER_DIRS.get(provider, provider.capitalize())
logging.info(f"Tearing down {provider} infrastructure for deployment ID {deployment_id}...") logging.info(f"Tearing down {provider} infrastructure for deployment ID {deployment_id}...")
# Check for infrastructure state file first
infra_state_file = f"infrastructure_state_{deployment_id}.json"
infra_data = {}
if os.path.exists(infra_state_file):
try:
with open(infra_state_file, 'r') as f:
infra_data = json.load(f)
logging.info(f"Loaded infrastructure state from {infra_state_file}")
# Use the region from the state file
if 'region' in infra_data:
if provider == "aws":
config['aws_region'] = infra_data['region']
logging.info(f"Using region from state file: {infra_data['region']}")
elif provider == "linode":
config['linode_region'] = infra_data['region']
config['region'] = infra_data['region']
logging.info(f"Using region from state file: {infra_data['region']}")
except Exception as e:
logging.warning(f"Failed to load infrastructure state file: {e}")
else:
logging.warning(f"No infrastructure state file found: {infra_state_file}")
# Load credentials from vars.yaml if not provided # Load credentials from vars.yaml if not provided
vars_file = f"{provider_dir}/vars.yaml" vars_file = f"{provider_dir}/vars.yaml"
vars_data = {} vars_data = {}
@@ -1837,37 +1910,19 @@ def teardown_infrastructure(config):
os.environ['AWS_SECRET_ACCESS_KEY'] = vars_data['aws_secret_key'] os.environ['AWS_SECRET_ACCESS_KEY'] = vars_data['aws_secret_key']
config['aws_secret_key'] = vars_data['aws_secret_key'] config['aws_secret_key'] = vars_data['aws_secret_key']
# Set AWS region if not specified
if not config.get('aws_region') and vars_data.get('aws_region_choices'):
config['aws_region'] = vars_data.get('aws_region', vars_data['aws_region_choices'][0])
elif provider == "linode":
if config.get('linode_token'):
os.environ['LINODE_TOKEN'] = config['linode_token']
elif vars_data.get('linode_token'):
os.environ['LINODE_TOKEN'] = vars_data['linode_token']
config['linode_token'] = vars_data['linode_token']
# Set default resource names based on deployment ID # Set default resource names based on deployment ID
config['redirector_name'] = f"r-{deployment_id}" config['redirector_name'] = f"r-{deployment_id}"
config['c2_name'] = f"s-{deployment_id}" config['c2_name'] = f"s-{deployment_id}"
config['tracker_name'] = f"t-{deployment_id}" config['tracker_name'] = f"t-{deployment_id}"
# CRITICAL FIX: Force these parameters to ensure proper teardown
config['confirm_cleanup'] = "false" # String value as Ansible expects
config['force'] = True
if provider == "aws":
# Ensure VPC-related attributes exist to prevent task failures
if 'remaining_vpcs' not in config:
config['remaining_vpcs'] = {'vpcs': []}
elif isinstance(config['remaining_vpcs'], dict) and 'vpcs' not in config['remaining_vpcs']:
config['remaining_vpcs']['vpcs'] = []
print(f"\n{COLORS['YELLOW']}Teardown Operation{COLORS['RESET']}") print(f"\n{COLORS['YELLOW']}Teardown Operation{COLORS['RESET']}")
print(f"{COLORS['YELLOW']}============================={COLORS['RESET']}") print(f"{COLORS['YELLOW']}============================={COLORS['RESET']}")
print(f"Provider: {provider}") print(f"Provider: {provider}")
print(f"Deployment ID: {deployment_id}") print(f"Deployment ID: {deployment_id}")
if provider == "aws":
print(f"Region: {config.get('aws_region', 'Unknown')}")
else:
print(f"Region: {config.get('region', 'Unknown')}")
print(f"Resources:") print(f"Resources:")
print(f" - Redirector: {config['redirector_name']}") print(f" - Redirector: {config['redirector_name']}")
print(f" - C2 Server: {config['c2_name']}") print(f" - C2 Server: {config['c2_name']}")
@@ -1883,131 +1938,88 @@ def teardown_infrastructure(config):
# Run cleanup playbook # Run cleanup playbook
playbook = f"{provider_dir}/cleanup.yml" playbook = f"{provider_dir}/cleanup.yml"
if os.path.exists(playbook): if os.path.exists(playbook):
logging.info(f"Running cleanup playbook: {playbook}") # Create inventory file
inventory_path = create_inventory_file(config, "local") fd, inventory_path = tempfile.mkstemp(prefix="inventory_teardown_", suffix=".ini")
with os.fdopen(fd, 'w') as f:
f.write("[local]\nlocalhost ansible_connection=local\n")
# Create a temporary JSON file with all variables to ensure proper passing # Build the command with all necessary variables
fd, tmp_vars_file = tempfile.mkstemp(suffix='.json')
os.close(fd)
try:
# Write all config variables to the temp file
with open(tmp_vars_file, 'w') as f:
json.dump(config, f)
# Build environment variables specific to each provider
env = os.environ.copy()
if provider == "aws":
env["AWS_ACCESS_KEY_ID"] = config['aws_access_key']
env["AWS_SECRET_ACCESS_KEY"] = config['aws_secret_key']
env["AWS_DEFAULT_REGION"] = config.get('aws_region', 'us-east-1')
elif provider == "linode":
env["LINODE_TOKEN"] = config['linode_token']
# Build extra ansible command line options for clarity
extra_cmd = []
if provider == "aws":
extra_cmd.extend([
"--extra-vars", f"aws_access_key={config['aws_access_key']}",
"--extra-vars", f"aws_secret_key={config['aws_secret_key']}",
"--extra-vars", f"aws_region={config.get('aws_region', 'us-east-1')}",
])
elif provider == "linode":
extra_cmd.extend([
"--extra-vars", f"linode_token={config['linode_token']}",
])
# Always add these critical variables for teardown
extra_cmd.extend([
"--extra-vars", "confirm_cleanup=false",
"--extra-vars", "force=true",
])
# Run the playbook with the complete set of variables
cmd = [ cmd = [
"ansible-playbook", "ansible-playbook",
"-i", inventory_path, "-i", inventory_path,
playbook, playbook,
"-e", f"@{tmp_vars_file}", "-e", f"deployment_id={deployment_id}",
] + extra_cmd "-e", "confirm_cleanup=false",
"-e", "force=true",
"-e", f"redirector_name=r-{deployment_id}",
"-e", f"c2_name=s-{deployment_id}",
"-e", f"tracker_name=t-{deployment_id}",
"-e", "cleanup_redirector=true",
"-e", "cleanup_c2=true",
"-e", "cleanup_tracker=true"
]
# Log the command (with sanitized credentials) # Add region information
sanitized_cmd = [] if provider == "aws":
for i, part in enumerate(cmd): cmd.extend([
if (i > 0 and cmd[i-1] in ["--extra-vars", "-e"] and "-e", f"aws_access_key={config.get('aws_access_key', '')}",
("secret" in part.lower() or "token" in part.lower() or "key" in part.lower())): "-e", f"aws_secret_key={config.get('aws_secret_key', '')}",
sanitized_cmd.append("[REDACTED]") "-e", f"aws_region={config.get('aws_region', 'us-east-1')}"
else: ])
sanitized_cmd.append(part) elif provider == "linode":
cmd.extend([
"-e", f"linode_token={config.get('linode_token', '')}",
"-e", f"region={config.get('region', '')}"
])
print(f"\n{COLORS['CYAN']}Running teardown command: {' '.join(sanitized_cmd)}{COLORS['RESET']}") # Add infra data from state file if available
logging.info(f"Running teardown command: {' '.join(sanitized_cmd)}") if infra_data:
for key, value in infra_data.items():
cmd.extend(["-e", f"{key}={value}"])
# Execute the command # Add verbosity
result = subprocess.run( if debug_mode:
cmd.append("-vvv")
try:
logging.info(f"Running teardown command: {' '.join(cmd)}")
# Use subprocess.Popen for real-time output
process = subprocess.Popen(
cmd, cmd,
env=env,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
text=True text=True,
bufsize=1 # Line buffered
) )
# Process the results # Stream output in real-time
if result.returncode == 0: for line in iter(process.stdout.readline, ''):
print(line.rstrip())
# Wait for completion
return_code = process.wait()
if return_code == 0:
print(f"\n{COLORS['GREEN']}Teardown completed successfully!{COLORS['RESET']}") print(f"\n{COLORS['GREEN']}Teardown completed successfully!{COLORS['RESET']}")
logging.info(f"Successfully tore down {provider} infrastructure for deployment ID {deployment_id}")
# Clean up SSH key files
ssh_key_path = f"~/.ssh/c2deploy_{deployment_id}.pem"
expanded_path = os.path.expanduser(ssh_key_path)
if os.path.exists(expanded_path):
try:
os.remove(expanded_path)
logging.info(f"Removed SSH key: {ssh_key_path}")
except Exception as e:
logging.error(f"Failed to remove SSH key {ssh_key_path}: {e}")
# Also check for public key
pub_key_path = f"{expanded_path}.pub"
if os.path.exists(pub_key_path):
try:
os.remove(pub_key_path)
logging.info(f"Removed SSH public key: {pub_key_path}")
except Exception as e:
logging.error(f"Failed to remove SSH public key {pub_key_path}: {e}")
# Remove infrastructure state file
state_file = f"infrastructure_state_{deployment_id}.json"
if os.path.exists(state_file):
try:
os.remove(state_file)
logging.info(f"Removed infrastructure state file: {state_file}")
except Exception as e:
logging.error(f"Failed to remove state file {state_file}: {e}")
return True return True
else: else:
print(f"\n{COLORS['RED']}Teardown failed with return code {result.returncode}.{COLORS['RESET']}") # Get any remaining error output
print(f"\n{COLORS['YELLOW']}STDOUT:{COLORS['RESET']}\n{result.stdout}") stderr = process.stderr.read()
print(f"\n{COLORS['YELLOW']}STDERR:{COLORS['RESET']}\n{result.stderr}") print(f"\n{COLORS['RED']}Teardown failed with return code {return_code}{COLORS['RESET']}")
logging.error(f"Teardown failed with return code {result.returncode}") if stderr:
logging.error(f"STDOUT: {result.stdout}") print(f"\n{COLORS['RED']}Error output:{COLORS['RESET']}\n{stderr}")
logging.error(f"STDERR: {result.stderr}")
return False return False
except Exception as e: except Exception as e:
print(f"\n{COLORS['RED']}Error during teardown: {str(e)}{COLORS['RESET']}") logging.error(f"Error running teardown command: {e}")
logging.error(f"Teardown failed with error: {str(e)}") print(f"\n{COLORS['RED']}Failed to execute teardown: {e}{COLORS['RESET']}")
return False return False
finally: finally:
# Clean up temporary files
if os.path.exists(tmp_vars_file):
os.unlink(tmp_vars_file)
if os.path.exists(inventory_path): if os.path.exists(inventory_path):
os.unlink(inventory_path) os.unlink(inventory_path)
else: else:
print(f"\n{COLORS['RED']}Cleanup playbook not found at {playbook}{COLORS['RESET']}") print(f"\n{COLORS['RED']}Cleanup playbook not found at {playbook}{COLORS['RESET']}")
logging.error(f"Cleanup playbook not found at {playbook}")
return False return False
def deploy_tracker(config): def deploy_tracker(config):
@@ -2161,11 +2173,38 @@ def generate_deployment_info(config, success=True):
# SSH Information # SSH Information
info.append("SSH INFORMATION") info.append("SSH INFORMATION")
info.append("--------------") info.append("--------------")
# Determine correct SSH key path based on provider
if config.get('provider') == "aws":
# AWS uses .pem extension and c2deploy_[deployment_id].pem naming
ssh_key = f"~/.ssh/c2deploy_{deployment_id}.pem"
else:
# Use the standard key path if defined
ssh_key = config.get('ssh_key', f"~/.ssh/c2deploy_{deployment_id}")
info.append(f"SSH Key: {ssh_key}") info.append(f"SSH Key: {ssh_key}")
# Determine SSH user based on provider
if config.get('ssh_user'):
ssh_user = config.get('ssh_user')
elif config.get('provider') == 'aws':
ssh_user = config.get('ami_ssh_user', 'kali')
elif config.get('provider') == 'linode':
ssh_user = 'root'
else:
ssh_user = DEFAULT_SSH_USER.get(config.get('provider', 'aws'), 'root')
info.append(f"SSH User: {ssh_user}") info.append(f"SSH User: {ssh_user}")
info.append(f"SSH Command for Redirector: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -o 'ConnectTimeout=10' -i {ssh_key} {ssh_user}@{config.get('redirector_ip', 'N/A')}")
info.append(f"SSH Command for C2: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -o 'ConnectTimeout=10' -i {ssh_key} {ssh_user}@{config.get('c2_ip', 'N/A')}") # Add correct SSH commands for each server type
info.append(f"SSH Command for Tracker: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -o 'ConnectTimeout=10' -i {ssh_key} {ssh_user}@{config.get('tracker_ip', 'N/A')}") if not config.get('redirector_only'):
info.append(f"SSH Command for C2: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} {ssh_user}@{config.get('c2_ip', 'N/A')}")
if not config.get('c2_only'):
info.append(f"SSH Command for Redirector: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} {ssh_user}@{config.get('redirector_ip', 'N/A')}")
if config.get('deploy_tracker') and not config.get('integrated_tracker'):
info.append(f"SSH Command for Tracker: ssh -t -o 'StrictHostKeyChecking=no' -o 'UserKnownHostsFile=/dev/null' -o 'IdentitiesOnly=yes' -i {ssh_key} {ssh_user}@{config.get('tracker_ip', 'N/A')}")
if config.get('ssh_port'): if config.get('ssh_port'):
info.append(f"SSH Port: {config.get('ssh_port')}") info.append(f"SSH Port: {config.get('ssh_port')}")
info.append("") info.append("")